Procházet zdrojové kódy

Merge pull request #6932 from weseek/fix/show-footer-and-not-show-alert

fix: Show footer and not show alert
Yuki Takei před 3 roky
rodič
revize
21383291b3

+ 25 - 12
packages/app/src/components/PageEditor.tsx

@@ -4,7 +4,7 @@ import React, {
 
 import EventEmitter from 'events';
 
-import { envUtils, PageGrant } from '@growi/core';
+import { envUtils, IPageHasId, PageGrant } from '@growi/core';
 import detectIndent from 'detect-indent';
 import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
@@ -17,7 +17,7 @@ import { getOptionsToSave } from '~/client/util/editor';
 import { IEditorMethods } from '~/interfaces/editor-methods';
 import {
   useCurrentPathname, useCurrentPageId,
-  useIsEditable, useIsIndentSizeForced, useIsUploadableFile, useIsUploadableImage, useEditingMarkdown,
+  useIsEditable, useIsIndentSizeForced, useIsUploadableFile, useIsUploadableImage, useEditingMarkdown, useIsNotFound,
 } from '~/stores/context';
 import {
   useCurrentIndentSize, useSWRxSlackChannels, useIsSlackEnabled, useIsTextlintEnabled, usePageTagsForEditors,
@@ -54,7 +54,8 @@ const PageEditor = React.memo((): JSX.Element => {
   const { t } = useTranslation();
   const router = useRouter();
 
-  const { data: pageId } = useCurrentPageId();
+  const { data: isNotFound } = useIsNotFound();
+  const { data: pageId, mutate: mutateCurrentPageId } = useCurrentPageId();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPathname } = useCurrentPathname();
   const { data: currentPage, mutate: mutateCurrentPage } = useSWRxCurrentPage();
@@ -69,7 +70,7 @@ const PageEditor = React.memo((): JSX.Element => {
   const { data: isTextlintEnabled } = useIsTextlintEnabled();
   const { data: isIndentSizeForced } = useIsIndentSizeForced();
   const { data: indentSize, mutate: mutateCurrentIndentSize } = useCurrentIndentSize();
-  const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
+  const { data: isEnabledUnsavedWarning, mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const { data: isUploadableFile } = useIsUploadableFile();
   const { data: isUploadableImage } = useIsUploadableImage();
 
@@ -115,7 +116,7 @@ const PageEditor = React.memo((): JSX.Element => {
   }, [setMarkdownWithDebounce]);
 
   // return true if the save succeeds, otherwise false.
-  const save = useCallback(async(opts?: {overwriteScopesOfDescendants: boolean}): Promise<boolean> => {
+  const save = useCallback(async(opts?: {overwriteScopesOfDescendants: boolean}): Promise<IPageHasId | null> => {
     if (grantData == null || isSlackEnabled == null || currentPathname == null) {
       logger.error('Some materials to save are invalid', { grantData, isSlackEnabled, currentPathname });
       throw new Error('Some materials to save are invalid');
@@ -136,9 +137,7 @@ const PageEditor = React.memo((): JSX.Element => {
         markdownToSave.current,
       );
 
-      await router.push(`/${page._id}`);
-      mutateIsEnabledUnsavedWarning(false);
-      return true;
+      return page;
     }
     catch (error) {
       logger.error('failed to save', error);
@@ -151,20 +150,34 @@ const PageEditor = React.memo((): JSX.Element => {
         //   lastUpdateUser: error.data.user,
         // });
       }
-      return false;
+      return null;
     }
 
   // eslint-disable-next-line max-len
-  }, [grantData, isSlackEnabled, currentPathname, slackChannels, pageTags, pageId, currentPagePath, currentRevisionId, mutateCurrentPage, mutateIsEnabledUnsavedWarning]);
+  }, [grantData, isSlackEnabled, currentPathname, slackChannels, pageTags, pageId, currentPagePath, currentRevisionId]);
 
   const saveAndReturnToViewHandler = useCallback(async(opts?: {overwriteScopesOfDescendants: boolean}) => {
     if (editorMode !== EditorMode.Editor) {
       return;
     }
 
-    await save(opts);
+    const page = await save(opts);
+    if (page == null) {
+      return;
+    }
+    // The updateFn should be a promise or asynchronous function to handle the remote mutation
+    // it should return updated data. see: https://swr.vercel.app/docs/mutation#optimistic-updates
+    // Moreover, `async() => false` does not work since it's too fast to be calculated.
+    await mutateIsEnabledUnsavedWarning(new Promise(r => setTimeout(() => r(false), 10)), { optimisticData: () => false });
+    if (isNotFound) {
+      await router.push(`/${page._id}`);
+    }
+    else {
+      await mutateCurrentPageId(page._id);
+      await mutateCurrentPage();
+    }
     mutateEditorMode(EditorMode.View);
-  }, [editorMode, save, mutateEditorMode]);
+  }, [editorMode, save, mutateIsEnabledUnsavedWarning, isNotFound, mutateEditorMode, router, mutateCurrentPageId, mutateCurrentPage]);
 
   const saveWithShortcut = useCallback(async() => {
     if (editorMode !== EditorMode.Editor) {

+ 24 - 18
packages/app/src/components/PageEditorByHackmd.tsx

@@ -13,7 +13,7 @@ import { apiPost } from '~/client/util/apiv1-client';
 import { getOptionsToSave } from '~/client/util/editor';
 import { IResHackmdIntegrated, IResHackmdDiscard } from '~/interfaces/hackmd';
 import {
-  useCurrentPageId, useCurrentPathname, useHackmdUri,
+  useCurrentPageId, useCurrentPathname, useHackmdUri, useIsNotFound,
 } from '~/stores/context';
 import {
   useSWRxSlackChannels, useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning,
@@ -29,6 +29,7 @@ import {
 import loggerFactory from '~/utils/logger';
 
 import HackmdEditor from './PageEditorByHackmd/HackmdEditor';
+import { useRouter } from 'next/router';
 
 const logger = loggerFactory('growi:PageEditorByHackmd');
 
@@ -41,12 +42,15 @@ type HackEditorRef = {
 export const PageEditorByHackmd = (): JSX.Element => {
 
   const { t } = useTranslation();
+  const router = useRouter();
+
+  const { data: isNotFound } = useIsNotFound();
   const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
   const { data: currentPagePath } = useCurrentPagePath();
   const { data: currentPathname } = useCurrentPathname();
   const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
   const { data: isSlackEnabled } = useIsSlackEnabled();
-  const { data: pageId } = useCurrentPageId();
+  const { data: pageId, mutate: mutateCurrentPageId } = useCurrentPageId();
   const { data: pageTags } = usePageTagsForEditors(pageId);
   const { mutate: mutateTagsInfo } = useSWRxTagsInfo(pageId);
   const { data: grant } = useSelectedGrant();
@@ -100,30 +104,32 @@ export const PageEditorByHackmd = (): JSX.Element => {
 
       const markdown = await hackmdEditorRef.current.getValue();
 
-      await saveOrUpdate(optionsToSave, { pageId, path: currentPagePath || currentPathname, revisionId: revision?._id }, markdown);
+      const { page } = await saveOrUpdate(optionsToSave, { pageId, path: currentPagePath || currentPathname, revisionId: revision?._id }, markdown);
       await mutatePageData();
       await mutateTagsInfo();
+
+      if (page == null) {
+        return;
+      }
+      // The updateFn should be a promise or asynchronous function to handle the remote mutation
+      // it should return updated data. see: https://swr.vercel.app/docs/mutation#optimistic-updates
+      // Moreover, `async() => false` does not work since it's too fast to be calculated.
+      await mutateIsEnabledUnsavedWarning(new Promise(r => setTimeout(() => r(false), 10)), { optimisticData: () => false });
+      if (isNotFound) {
+        await router.push(`/${page._id}`);
+      }
+      else {
+        await mutateCurrentPageId(page._id);
+        await mutatePageData();
+      }
       mutateEditorMode(EditorMode.View);
-      mutateIsEnabledUnsavedWarning(false);
     }
     catch (error) {
       logger.error('failed to save', error);
       toastError(error.message);
     }
-  }, [editorMode,
-      isSlackEnabled,
-      currentPathname,
-      slackChannels,
-      grant,
-      revision,
-      pageTags,
-      pageId,
-      currentPagePath,
-      mutatePageData,
-      mutateEditorMode,
-      mutateTagsInfo,
-      mutateIsEnabledUnsavedWarning,
-  ]);
+  }, [editorMode, isSlackEnabled, currentPathname, slackChannels, grant, revision, pageTags, pageId,
+      currentPagePath, mutatePageData, mutateTagsInfo, mutateIsEnabledUnsavedWarning, isNotFound, mutateEditorMode, router, mutateCurrentPageId]);
 
   // set handler to save and reload Page
   useEffect(() => {

+ 0 - 1
packages/app/src/components/SavePageControls.tsx

@@ -13,7 +13,6 @@ import { IPageGrantData } from '~/interfaces/page';
 import {
   useIsEditable, useCurrentPageId, useIsAclEnabled,
 } from '~/stores/context';
-import { useIsEnabledUnsavedWarning } from '~/stores/editor';
 import { useCurrentPagePath } from '~/stores/page';
 import { useSelectedGrant } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';

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

@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect } from 'react';
+import React, { useCallback, useEffect, memo } from 'react';
 
 import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
@@ -56,4 +56,4 @@ const UnsavedAlertDialog = (): JSX.Element => {
   return <></>;
 };
 
-export default UnsavedAlertDialog;
+export default memo(UnsavedAlertDialog);

+ 2 - 2
packages/app/src/stores/editor.tsx

@@ -1,5 +1,5 @@
 import { Nullable, withUtils, SWRResponseWithUtils } from '@growi/core';
-import useSWR, { SWRResponse } from 'swr';
+import useSWR, { MutatorOptions, SWRResponse, useSWRConfig } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
 import { apiGet } from '~/client/util/apiv1-client';
@@ -115,5 +115,5 @@ export const usePageTagsForEditors = (pageId: Nullable<string>): SWRResponse<str
 };
 
 export const useIsEnabledUnsavedWarning = (): SWRResponse<boolean, Error> => {
-  return useStaticSWR<boolean, Error>('isEnabledUnsavedWarning', undefined, { fallbackData: false });
+  return useStaticSWR<boolean, Error>('isEnabledUnsavedWarning');
 };

+ 10 - 2
packages/app/src/stores/page.tsx

@@ -2,6 +2,7 @@ import type {
   IPageInfoForEntity, IPagePopulatedToShowRevision, Nullable,
 } from '@growi/core';
 import { pagePathUtils } from '@growi/core';
+import { useEffect } from 'react';
 import useSWR, { Key, SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
@@ -26,7 +27,7 @@ export const useSWRxPage = (
     shareLinkId?: string,
     initialData?: IPagePopulatedToShowRevision|null,
 ): SWRResponse<IPagePopulatedToShowRevision|null, Error> => {
-  return useSWR<IPagePopulatedToShowRevision|null, Error>(
+  const swrResponse = useSWR<IPagePopulatedToShowRevision|null, Error>(
     pageId != null ? ['/page', pageId, shareLinkId] : null,
     (endpoint, pageId, shareLinkId) => apiv3Get<{ page: IPagePopulatedToShowRevision }>(endpoint, { pageId, shareLinkId })
       .then(result => result.data.page)
@@ -39,8 +40,15 @@ export const useSWRxPage = (
         }
         throw Error('failed to get page');
       }),
-    { fallbackData: initialData },
   );
+
+  useEffect(() => {
+    if (initialData !== undefined) {
+      swrResponse.mutate(initialData);
+    }
+  }, [initialData]);
+
+  return swrResponse;
 };
 
 export const useSWRxPageByPath = (path?: string): SWRResponse<IPagePopulatedToShowRevision, Error> => {