Yuki Takei 3 лет назад
Родитель
Сommit
222e85eab1

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

@@ -1,7 +1,8 @@
 import { SubscriptionStatusType, Nullable } from '@growi/core';
 import urljoin from 'url-join';
 
-import { OptionsToSave } from '~/interfaces/editor-settings';
+import { OptionsToSave } from '~/interfaces/page-operation';
+import { useIsEnabledUnsavedWarning } from '~/stores/editor';
 import loggerFactory from '~/utils/logger';
 
 import { toastError } from '../util/apiNotification';
@@ -118,43 +119,55 @@ type PageInfo= {
   revisionId: Nullable<string>,
 }
 
+type SaveOrUpdateFunction = (markdown: string, pageInfo: PageInfo, optionsToSave?: OptionsToSave) => any;
+
 // TODO: define return type
-export const saveOrUpdate = async(optionsToSave: OptionsToSave, pageInfo: PageInfo, markdown: string) => {
-  const { path, pageId, revisionId } = pageInfo;
-
-  const options = Object.assign({}, optionsToSave);
-
-  /*
-  * Note: variable "markdown" will be received from params
-  * please delete the following code after implemating HackMD editor function
-  */
-  // let markdown;
-  // if (editorMode === EditorMode.HackMD) {
-  // const pageEditorByHackmd = this.appContainer.getComponentInstance('PageEditorByHackmd');
-  // markdown = await pageEditorByHackmd.getMarkdown();
-  // // set option to sync
-  // options.isSyncRevisionToHackmd = true;
-  // revisionId = this.state.revisionIdHackmdSynced;
-  // }
-  // else {
-  // const pageEditor = this.appContainer.getComponentInstance('PageEditor');
-  // const pageEditor = getComponentInstance('PageEditor');
-  // markdown = pageEditor.getMarkdown();
-  // }
-
-  const isNoRevisionPage = pageId != null && revisionId == null;
-
-  let res;
-  if (pageId == null || isNoRevisionPage) {
-    res = await createPage(path, markdown, options);
-  }
-  else {
-    if (revisionId == null) {
-      const msg = '\'revisionId\' is required to update page';
-      throw new Error(msg);
+export const useSaveOrUpdate = (): SaveOrUpdateFunction => {
+  /* eslint-disable react-hooks/rules-of-hooks */
+  const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
+  /* eslint-enable react-hooks/rules-of-hooks */
+
+  return async function(markdown: string, pageInfo: PageInfo, optionsToSave?: OptionsToSave) {
+    const { path, pageId, revisionId } = pageInfo;
+
+    const options: OptionsToSave = Object.assign({}, optionsToSave);
+    /*
+    * Note: variable "markdown" will be received from params
+    * please delete the following code after implemating HackMD editor function
+    */
+    // let markdown;
+    // if (editorMode === EditorMode.HackMD) {
+    // const pageEditorByHackmd = this.appContainer.getComponentInstance('PageEditorByHackmd');
+    // markdown = await pageEditorByHackmd.getMarkdown();
+    // // set option to sync
+    // options.isSyncRevisionToHackmd = true;
+    // revisionId = this.state.revisionIdHackmdSynced;
+    // }
+    // else {
+    // const pageEditor = this.appContainer.getComponentInstance('PageEditor');
+    // const pageEditor = getComponentInstance('PageEditor');
+    // markdown = pageEditor.getMarkdown();
+    // }
+
+    const isNoRevisionPage = pageId != null && revisionId == null;
+
+    let res;
+    if (pageId == null || isNoRevisionPage) {
+      res = await createPage(path, markdown, options);
+    }
+    else {
+      if (revisionId == null) {
+        const msg = '\'revisionId\' is required to update page';
+        throw new Error(msg);
+      }
+      res = await updatePage(pageId, revisionId, markdown, options);
     }
-    res = await updatePage(pageId, revisionId, markdown, options);
-  }
 
-  return res;
+    // The updateFn should be a promise or asynchronous function to handle the remote mutation
+    // it should return updated data. see: https://swr.vercel.app/docs/mutation#optimistic-updates
+    // Moreover, `async() => false` does not work since it's too fast to be calculated.
+    await mutateIsEnabledUnsavedWarning(new Promise(r => setTimeout(() => r(false), 10)), { optimisticData: () => false });
+
+    return res;
+  };
 };

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

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

+ 21 - 34
packages/app/src/components/PageEditor.tsx

@@ -12,14 +12,14 @@ import { useTranslation } from 'next-i18next';
 import { useRouter } from 'next/router';
 import { throttle, debounce } from 'throttle-debounce';
 
-import { saveOrUpdate } from '~/client/services/page-operation';
+import { useSaveOrUpdate } from '~/client/services/page-operation';
 import { toastSuccess, toastError } from '~/client/util/apiNotification';
 import { apiGet, apiPostForm } from '~/client/util/apiv1-client';
-import { getOptionsToSave } from '~/client/util/editor';
 import { IEditorMethods } from '~/interfaces/editor-methods';
+import { OptionsToSave } from '~/interfaces/page-operation';
 import {
   useCurrentPathname, useCurrentPageId, useIsEnabledAttachTitleHeader, useTemplateBodyData,
-  useIsEditable, useIsIndentSizeForced, useIsUploadableFile, useIsUploadableImage, useEditingMarkdown, useIsNotFound,
+  useIsEditable, useIsUploadableFile, useIsUploadableImage, useEditingMarkdown, useIsNotFound,
 } from '~/stores/context';
 import {
   useCurrentIndentSize, useSWRxSlackChannels, useIsSlackEnabled, useIsTextlintEnabled, usePageTagsForEditors,
@@ -29,7 +29,7 @@ import { useCurrentPagePath, useSWRxCurrentPage } from '~/stores/page';
 import { usePreviewOptions } from '~/stores/renderer';
 import {
   EditorMode,
-  useEditorMode, useIsMobile, useSelectedGrant,
+  useEditorMode, useSelectedGrant,
 } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 
@@ -68,17 +68,16 @@ const PageEditor = React.memo((): JSX.Element => {
   const { data: templateBodyData } = useTemplateBodyData();
   const { data: isEditable } = useIsEditable();
   const { data: editorMode, mutate: mutateEditorMode } = useEditorMode();
-  const { data: isMobile } = useIsMobile();
   const { data: isSlackEnabled } = useIsSlackEnabled();
   const { data: slackChannelsData } = useSWRxSlackChannels(currentPagePath);
   const { data: isTextlintEnabled } = useIsTextlintEnabled();
-  const { data: isIndentSizeForced } = useIsIndentSizeForced();
-  const { data: indentSize, mutate: mutateCurrentIndentSize } = useCurrentIndentSize();
-  const { data: isEnabledUnsavedWarning, mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
+  const { data: indentSize } = useCurrentIndentSize();
   const { data: isUploadableFile } = useIsUploadableFile();
   const { data: isUploadableImage } = useIsUploadableImage();
 
   const { data: rendererOptions } = usePreviewOptions();
+  const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
+  const saveOrUpdate = useSaveOrUpdate();
 
   const currentRevisionId = currentPage?.revision?._id;
 
@@ -106,20 +105,6 @@ const PageEditor = React.memo((): JSX.Element => {
   const editorRef = useRef<IEditorMethods>(null);
   const previewRef = useRef<HTMLDivElement>(null);
 
-
-  // const optionsToSave = useMemo(() => {
-  //   if (grantData == null) {
-  //     return;
-  //   }
-  //   const slackChannels = slackChannelsData ? slackChannelsData.toString() : '';
-  //   const optionsToSave = getOptionsToSave(
-  //     isSlackEnabled ?? false, slackChannels,
-  //     grantData.grant, grantData.grantedGroup?.id, grantData.grantedGroup?.name,
-  //     pageTags || [],
-  //   );
-  //   return optionsToSave;
-  // }, [grantData, isSlackEnabled, pageTags, slackChannelsData]);
-
   const setMarkdownWithDebounce = useMemo(() => debounce(100, throttle(150, (value: string, isClean: boolean) => {
     markdownToSave.current = value;
     setMarkdownToPreview(value);
@@ -143,16 +128,21 @@ const PageEditor = React.memo((): JSX.Element => {
     const grant = grantData.grant || PageGrant.GRANT_PUBLIC;
     const grantedGroup = grantData?.grantedGroup;
 
-    const optionsToSave = Object.assign(
-      getOptionsToSave(isSlackEnabled, slackChannels, grant || 1, grantedGroup?.id, grantedGroup?.name, pageTags || []),
-      { ...opts },
-    );
+    const optionsToSave: OptionsToSave = {
+      isSlackEnabled,
+      slackChannels,
+      grant: grant || 1,
+      pageTags: pageTags || [],
+      grantUserGroupId: grantedGroup?.id,
+      grantUserGroupName: grantedGroup?.name,
+      ...opts,
+    };
 
     try {
       const { page } = await saveOrUpdate(
-        optionsToSave,
-        { pageId, path: currentPagePath || currentPathname, revisionId: currentRevisionId },
         markdownToSave.current,
+        { pageId, path: currentPagePath || currentPathname, revisionId: currentRevisionId },
+        optionsToSave,
       );
 
       return page;
@@ -172,7 +162,7 @@ const PageEditor = React.memo((): JSX.Element => {
     }
 
   // eslint-disable-next-line max-len
-  }, [grantData, isSlackEnabled, currentPathname, slackChannels, pageTags, pageId, currentPagePath, currentRevisionId]);
+  }, [grantData, isSlackEnabled, currentPathname, slackChannels, pageTags, saveOrUpdate, pageId, currentPagePath, currentRevisionId]);
 
   const saveAndReturnToViewHandler = useCallback(async(opts?: {overwriteScopesOfDescendants: boolean}) => {
     if (editorMode !== EditorMode.Editor) {
@@ -183,10 +173,7 @@ const PageEditor = React.memo((): JSX.Element => {
     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}`);
     }
@@ -195,7 +182,7 @@ const PageEditor = React.memo((): JSX.Element => {
       await mutateCurrentPage();
     }
     mutateEditorMode(EditorMode.View);
-  }, [editorMode, save, mutateIsEnabledUnsavedWarning, isNotFound, mutateEditorMode, router, mutateCurrentPageId, mutateCurrentPage]);
+  }, [editorMode, save, isNotFound, mutateEditorMode, router, mutateCurrentPageId, mutateCurrentPage]);
 
   const saveWithShortcut = useCallback(async() => {
     if (editorMode !== EditorMode.Editor) {

+ 32 - 46
packages/app/src/components/PageEditorByHackmd.tsx

@@ -10,16 +10,16 @@ import { useRouter } from 'next/router';
 import { useTranslation } from 'react-i18next';
 import urljoin from 'url-join';
 
-import { saveOrUpdate } from '~/client/services/page-operation';
+import { useSaveOrUpdate } from '~/client/services/page-operation';
 import { toastError, toastSuccess } from '~/client/util/apiNotification';
 import { apiPost } from '~/client/util/apiv1-client';
-import { getOptionsToSave } from '~/client/util/editor';
 import { IResHackmdIntegrated, IResHackmdDiscard } from '~/interfaces/hackmd';
+import { OptionsToSave } from '~/interfaces/page-operation';
 import {
   useCurrentPageId, useCurrentPathname, useHackmdUri, useIsNotFound,
 } from '~/stores/context';
 import {
-  useSWRxSlackChannels, useIsSlackEnabled, usePageTagsForEditors, useIsEnabledUnsavedWarning,
+  useSWRxSlackChannels, useIsSlackEnabled, usePageTagsForEditors,
 } from '~/stores/editor';
 import {
   usePageIdOnHackmd, useHasDraftOnHackmd, useRevisionIdHackmdSynced, useRemoteRevisionId,
@@ -57,6 +57,7 @@ export const PageEditorByHackmd = (): JSX.Element => {
   const { mutate: mutateTagsInfo } = useSWRxTagsInfo(pageId);
   const { data: grant } = useSelectedGrant();
   const { data: hackmdUri } = useHackmdUri();
+  const saveOrUpdate = useSaveOrUpdate();
 
   const { returnPathForURL } = pathUtils;
 
@@ -77,7 +78,6 @@ export const PageEditorByHackmd = (): JSX.Element => {
   const { data: pageIdOnHackmd, mutate: mutatePageIdOnHackmd } = usePageIdOnHackmd();
   const { data: hasDraftOnHackmd, mutate: mutateHasDraftOnHackmd } = useHasDraftOnHackmd();
   const { data: revisionIdHackmdSynced, mutate: mutateRevisionIdHackmdSynced } = useRevisionIdHackmdSynced();
-  const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const [isHackmdDraftUpdatingInRealtime, setIsHackmdDraftUpdatingInRealtime] = useState(false);
   const { data: remoteRevisionId, mutate: mutateRemoteRevisionId } = useRemoteRevisionId(revision?._id);
 
@@ -91,34 +91,26 @@ export const PageEditorByHackmd = (): JSX.Element => {
         throw new Error('Some materials to save are invalid');
       }
 
-      let optionsToSave;
-
-      const currentOptionsToSave = getOptionsToSave(
-        isSlackEnabled, slackChannels, grant.grant, grant.grantedGroup?.id, grant.grantedGroup?.name, pageTags ?? [], true,
-      );
-
-      if (opts != null) {
-        optionsToSave = Object.assign(currentOptionsToSave, {
-          ...opts,
-        });
-      }
-      else {
-        optionsToSave = currentOptionsToSave;
-      }
+      const optionsToSave: OptionsToSave = {
+        isSlackEnabled,
+        slackChannels,
+        grant: grant.grant,
+        grantUserGroupId: grant.grantedGroup?.id,
+        grantUserGroupName: grant.grantedGroup?.name,
+        pageTags: pageTags ?? [],
+        isSyncRevisionToHackmd: true,
+        ...opts,
+      };
 
       const markdown = await hackmdEditorRef.current.getValue();
 
-      const { page } = await saveOrUpdate(optionsToSave, { pageId, path: currentPagePath || currentPathname, revisionId: revision?._id }, markdown);
+      const { page } = await saveOrUpdate(markdown, { pageId, path: currentPagePath || currentPathname, revisionId: revision?._id }, optionsToSave);
       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}`);
       }
@@ -132,8 +124,8 @@ export const PageEditorByHackmd = (): JSX.Element => {
       logger.error('failed to save', error);
       toastError(error.message);
     }
-  }, [editorMode, isSlackEnabled, currentPathname, slackChannels, grant, revision, pageTags, pageId,
-      currentPagePath, mutatePageData, mutateTagsInfo, mutateIsEnabledUnsavedWarning, isNotFound, mutateEditorMode, router, mutateCurrentPageId]);
+  // eslint-disable-next-line max-len
+  }, [editorMode, isSlackEnabled, currentPathname, slackChannels, grant, revision, pageTags, saveOrUpdate, pageId, currentPagePath, mutatePageData, mutateTagsInfo, isNotFound, mutateEditorMode, router, mutateCurrentPageId]);
 
   // set handler to save and reload Page
   useEffect(() => {
@@ -226,7 +218,7 @@ export const PageEditorByHackmd = (): JSX.Element => {
       logger.error(err);
       toastError(err.message);
     }
-  }, [setIsHackmdDraftUpdatingInRealtime, mutateHasDraftOnHackmd, mutatePageIdOnHackmd, mutateRevisionIdHackmdSynced, pageId]);
+  }, [pageId, mutateHasDraftOnHackmd, mutatePageIdOnHackmd, mutateRemoteRevisionId, mutateRevisionIdHackmdSynced]);
 
   /**
    * save and update state of containers
@@ -239,10 +231,16 @@ export const PageEditorByHackmd = (): JSX.Element => {
         isSlackEnabled == null || grant == null || slackChannels == null || pageId == null
         || revisionIdHackmdSynced == null || currentPagePathOrPathname == null
       ) { throw new Error('Some materials to save are invalid') }
-      const optionsToSave = getOptionsToSave(
-        isSlackEnabled, slackChannels, grant.grant, grant.grantedGroup?.id, grant.grantedGroup?.name, pageTags ?? [], true,
-      );
-      const res = await saveOrUpdate(optionsToSave, { pageId, path: currentPagePathOrPathname, revisionId: revisionIdHackmdSynced }, markdown);
+      const optionsToSave = {
+        isSlackEnabled,
+        slackChannels,
+        grant: grant.grant,
+        grantUserGroupId: grant.grantedGroup?.id,
+        grantUserGroupName: grant.grantedGroup?.name,
+        pageTags: pageTags ?? [],
+        isSyncRevisionToHackmd: true,
+      };
+      const res = await saveOrUpdate(markdown, { pageId, path: currentPagePathOrPathname, revisionId: revisionIdHackmdSynced }, optionsToSave);
 
       // update pageData
       mutatePageData(res);
@@ -252,7 +250,6 @@ export const PageEditorByHackmd = (): JSX.Element => {
       mutateRevisionIdHackmdSynced(res.page.revisionHackmdSynced);
       mutateHasDraftOnHackmd(res.page.hasDraftOnHackmd);
       mutateTagsInfo();
-      mutateIsEnabledUnsavedWarning(false);
 
       logger.debug('success to save');
 
@@ -262,20 +259,8 @@ export const PageEditorByHackmd = (): JSX.Element => {
       logger.error('failed to save', error);
       toastError(error.message);
     }
-  }, [isSlackEnabled,
-      grant,
-      slackChannels,
-      pageId,
-      revisionIdHackmdSynced,
-      currentPathname,
-      pageTags,
-      currentPagePath,
-      mutatePageData,
-      mutateRevisionIdHackmdSynced,
-      mutateHasDraftOnHackmd,
-      mutateTagsInfo,
-      mutateIsEnabledUnsavedWarning,
-      t]);
+  // eslint-disable-next-line max-len
+  }, [currentPagePath, currentPathname, isSlackEnabled, grant, slackChannels, pageId, revisionIdHackmdSynced, pageTags, saveOrUpdate, mutatePageData, mutateRemoteRevisionId, mutateRevisionIdHackmdSynced, mutateHasDraftOnHackmd, mutateTagsInfo, t]);
 
   /**
    * onChange event of HackmdEditor handler
@@ -433,7 +418,8 @@ export const PageEditorByHackmd = (): JSX.Element => {
         {content}
       </div>
     );
-  }, [discardChanges, isInitializing, isResume, resumeToEdit, startToEdit, t, hackmdUri, pageId, remoteRevisionId, revisionIdHackmdSynced, revision?._id]);
+  // eslint-disable-next-line max-len
+  }, [pageId, hackmdUri, isResume, t, revisionIdHackmdSynced, remoteRevisionId, pageData, returnPathForURL, isInitializing, resumeToEdit, discardChanges, revision?._id, startToEdit]);
 
   if (editorMode == null || revision == null) {
     return <></>;

+ 0 - 10
packages/app/src/interfaces/editor-settings.ts

@@ -35,13 +35,3 @@ export type EditorConfig = {
     isUploadableImage: boolean,
   }
 }
-
-export type OptionsToSave = {
-  isSlackEnabled: boolean;
-  slackChannels: string;
-  grant: number;
-  pageTags: string[] | null;
-  grantUserGroupId?: string | null;
-  grantUserGroupName?: string | null;
-  isSyncRevisionToHackmd?: boolean;
-};

+ 10 - 0
packages/app/src/interfaces/page-operation.ts

@@ -26,3 +26,13 @@ export type IPageOperationProcessData = {
 export type IPageOperationProcessInfo = {
   [pageId: string]: IPageOperationProcessData,
 }
+
+export type OptionsToSave = {
+  isSlackEnabled: boolean;
+  slackChannels: string;
+  grant: number;
+  pageTags: string[] | null;
+  grantUserGroupId?: string | null;
+  grantUserGroupName?: string | null;
+  isSyncRevisionToHackmd?: boolean;
+};