Przeglądaj źródła

Merge pull request #7080 from weseek/support/110444-next-ConflictDiffModal

fix: next conflict diff modal
yuken 3 lat temu
rodzic
commit
3db4a795b5

+ 12 - 6
packages/app/src/components/Page/DisplaySwitcher.tsx

@@ -12,7 +12,9 @@ import {
 } from '~/stores/context';
 } from '~/stores/context';
 import { useDescendantsPageListModal } from '~/stores/modal';
 import { useDescendantsPageListModal } from '~/stores/modal';
 import { useCurrentPagePath, useSWRxCurrentPage } from '~/stores/page';
 import { useCurrentPagePath, useSWRxCurrentPage } from '~/stores/page';
-import { useRemoteRevisionId, useRemoteRevisionLastUpdatUser } from '~/stores/remote-latest-page';
+import {
+  useSetRemoteLatestPageData,
+} from '~/stores/remote-latest-page';
 import { EditorMode, useEditorMode } from '~/stores/ui';
 import { EditorMode, useEditorMode } from '~/stores/ui';
 import { useGlobalSocket } from '~/stores/websocket';
 import { useGlobalSocket } from '~/stores/websocket';
 
 
@@ -47,8 +49,7 @@ const PageView = React.memo((): JSX.Element => {
   const { data: isNotFound } = useIsNotFound();
   const { data: isNotFound } = useIsNotFound();
   const { data: currentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
   const { data: currentPage } = useSWRxCurrentPage(shareLinkId ?? undefined);
   const { open: openDescendantPageListModal } = useDescendantsPageListModal();
   const { open: openDescendantPageListModal } = useDescendantsPageListModal();
-  const { mutate: mutateRemoteRevisionId } = useRemoteRevisionId();
-  const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdatUser();
+  const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
 
 
   const isTopPagePath = isTopPage(currentPagePath ?? '');
   const isTopPagePath = isTopPage(currentPagePath ?? '');
   const isUsersHomePagePath = isUsersHomePage(currentPagePath ?? '');
   const isUsersHomePagePath = isUsersHomePage(currentPagePath ?? '');
@@ -58,9 +59,14 @@ const PageView = React.memo((): JSX.Element => {
   const setLatestRemotePageData = useCallback((data) => {
   const setLatestRemotePageData = useCallback((data) => {
     const { s2cMessagePageUpdated } = data;
     const { s2cMessagePageUpdated } = data;
 
 
-    mutateRemoteRevisionId(s2cMessagePageUpdated.revisionId);
-    mutateRemoteRevisionLastUpdateUser(s2cMessagePageUpdated.remoteLastUpdateUser);
-  }, [mutateRemoteRevisionId, mutateRemoteRevisionLastUpdateUser]);
+    const remoteData = {
+      remoteRevisionId: s2cMessagePageUpdated.revisionId,
+      remoteRevisionBody: s2cMessagePageUpdated.revisionBody,
+      remoteRevisionLastUpdateUser: s2cMessagePageUpdated.remoteLastUpdateUser,
+      remoteRevisionLastUpdatedAt: s2cMessagePageUpdated.revisionUpdateAt,
+    };
+    setRemoteLatestPageData(remoteData);
+  }, [setRemoteLatestPageData]);
 
 
   // listen socket for someone updating this page
   // listen socket for someone updating this page
   useEffect(() => {
   useEffect(() => {

+ 31 - 10
packages/app/src/components/PageEditor.tsx

@@ -29,7 +29,8 @@ import {
   useIsConflict,
   useIsConflict,
   useEditingMarkdown,
   useEditingMarkdown,
 } from '~/stores/editor';
 } from '~/stores/editor';
-import { useCurrentPagePath, useSWRxCurrentPage } from '~/stores/page';
+import { useConflictDiffModal } from '~/stores/modal';
+import { useCurrentPagePath, useSWRxCurrentPage, useSWRxTagsInfo } from '~/stores/page';
 import { usePreviewOptions } from '~/stores/renderer';
 import { usePreviewOptions } from '~/stores/renderer';
 import {
 import {
   EditorMode,
   EditorMode,
@@ -41,6 +42,7 @@ import loggerFactory from '~/utils/logger';
 
 
 
 
 // import { ConflictDiffModal } from './PageEditor/ConflictDiffModal';
 // import { ConflictDiffModal } from './PageEditor/ConflictDiffModal';
+import { ConflictDiffModal } from './PageEditor/ConflictDiffModal';
 import Editor from './PageEditor/Editor';
 import Editor from './PageEditor/Editor';
 import Preview from './PageEditor/Preview';
 import Preview from './PageEditor/Preview';
 import scrollSyncHelper from './PageEditor/ScrollSyncHelper';
 import scrollSyncHelper from './PageEditor/ScrollSyncHelper';
@@ -71,8 +73,9 @@ const PageEditor = React.memo((): JSX.Element => {
   const { data: currentPathname } = useCurrentPathname();
   const { data: currentPathname } = useCurrentPathname();
   const { data: currentPage, mutate: mutateCurrentPage } = useSWRxCurrentPage();
   const { data: currentPage, mutate: mutateCurrentPage } = useSWRxCurrentPage();
   const { data: grantData, mutate: mutateGrant } = useSelectedGrant();
   const { data: grantData, mutate: mutateGrant } = useSelectedGrant();
-  const { data: pageTags } = usePageTagsForEditors(pageId);
-  const { data: editingMarkdown } = useEditingMarkdown();
+  const { data: pageTags, sync: syncTagsInfoForEditor } = usePageTagsForEditors(pageId);
+  const { mutate: mutateTagsInfo } = useSWRxTagsInfo(pageId);
+  const { data: editingMarkdown, mutate: mutateEditingMarkdown } = useEditingMarkdown();
   const { data: isEnabledAttachTitleHeader } = useIsEnabledAttachTitleHeader();
   const { data: isEnabledAttachTitleHeader } = useIsEnabledAttachTitleHeader();
   const { data: templateBodyData } = useTemplateBodyData();
   const { data: templateBodyData } = useTemplateBodyData();
   const { data: isEditable } = useIsEditable();
   const { data: isEditable } = useIsEditable();
@@ -84,6 +87,7 @@ const PageEditor = React.memo((): JSX.Element => {
   const { data: currentIndentSize, mutate: mutateCurrentIndentSize } = useCurrentIndentSize();
   const { data: currentIndentSize, mutate: mutateCurrentIndentSize } = useCurrentIndentSize();
   const { data: isUploadableFile } = useIsUploadableFile();
   const { data: isUploadableFile } = useIsUploadableFile();
   const { data: isUploadableImage } = useIsUploadableImage();
   const { data: isUploadableImage } = useIsUploadableImage();
+  const { data: conflictDiffModalStatus, close: closeConflictDiffModal } = useConflictDiffModal();
 
 
   const { data: rendererOptions, mutate: mutateRendererOptions } = usePreviewOptions();
   const { data: rendererOptions, mutate: mutateRendererOptions } = usePreviewOptions();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
   const { mutate: mutateIsEnabledUnsavedWarning } = useIsEnabledUnsavedWarning();
@@ -411,6 +415,23 @@ const PageEditor = React.memo((): JSX.Element => {
   }, []);
   }, []);
   const scrollEditorByPreviewScrollWithThrottle = useMemo(() => throttle(20, scrollEditorByPreviewScroll), [scrollEditorByPreviewScroll]);
   const scrollEditorByPreviewScrollWithThrottle = useMemo(() => throttle(20, scrollEditorByPreviewScroll), [scrollEditorByPreviewScroll]);
 
 
+  const afterResolvedHandler = useCallback(async() => {
+    // get page data from db
+    const pageData = await mutateCurrentPage();
+
+    // update tag
+    await mutateTagsInfo(); // get from DB
+    syncTagsInfoForEditor(); // sync global state for client
+
+    // clear isConflict
+    mutateIsConflict(false);
+
+    // set resolved markdown in editing markdown
+    const markdown = pageData?.revision.body ?? '';
+    mutateEditingMarkdown(markdown);
+
+  }, [mutateCurrentPage, mutateEditingMarkdown, mutateIsConflict, mutateTagsInfo, syncTagsInfoForEditor]);
+
 
 
   // initialize
   // initialize
   useEffect(() => {
   useEffect(() => {
@@ -514,13 +535,13 @@ const PageEditor = React.memo((): JSX.Element => {
           onScroll={offset => scrollEditorByPreviewScrollWithThrottle(offset)}
           onScroll={offset => scrollEditorByPreviewScrollWithThrottle(offset)}
         />
         />
       </div>
       </div>
-      {/* <ConflictDiffModal
-        isOpen={pageContainer.state.isConflictDiffModalOpen}
-        onClose={() => pageContainer.setState({ isConflictDiffModalOpen: false })}
-        pageContainer={pageContainer}
-        markdownOnEdit={markdown}
-        optionsToSave={optionsToSave}
-      /> */}
+      <ConflictDiffModal
+        isOpen={conflictDiffModalStatus?.isOpened}
+        onClose={() => closeConflictDiffModal()}
+        markdownOnEdit={markdownToPreview}
+        optionsToSave={undefined} // replace undefined
+        afterResolvedHandler={afterResolvedHandler}
+      />
     </div>
     </div>
   );
   );
 });
 });

+ 96 - 45
packages/app/src/components/PageEditor/ConflictDiffModal.tsx

@@ -2,7 +2,6 @@ import React, {
   useState, useEffect, useRef, useMemo, useCallback,
   useState, useEffect, useRef, useMemo, useCallback,
 } from 'react';
 } from 'react';
 
 
-import type { IUser } from '@growi/core';
 import { UserPicture } from '@growi/ui';
 import { UserPicture } from '@growi/ui';
 import CodeMirror from 'codemirror/lib/codemirror';
 import CodeMirror from 'codemirror/lib/codemirror';
 import { format } from 'date-fns';
 import { format } from 'date-fns';
@@ -11,9 +10,14 @@ import {
   Modal, ModalHeader, ModalBody, ModalFooter,
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 } from 'reactstrap';
 
 
+import { useSaveOrUpdate } from '~/client/services/page-operation';
+import { toastError, toastSuccess } from '~/client/util/toastr';
 import { OptionsToSave } from '~/interfaces/page-operation';
 import { OptionsToSave } from '~/interfaces/page-operation';
-import { useCurrentUser } from '~/stores/context';
-import { useEditorMode } from '~/stores/ui';
+import { useCurrentPageId, useCurrentPathname, useCurrentUser } from '~/stores/context';
+import { useCurrentPagePath, useSWRxCurrentPage } from '~/stores/page';
+import {
+  useRemoteRevisionBody, useRemoteRevisionId, useRemoteRevisionLastUpdatedAt, useRemoteRevisionLastUpdateUser, useSetRemoteLatestPageData,
+} from '~/stores/remote-latest-page';
 
 
 import { IRevisionOnConflict } from '../../interfaces/revision';
 import { IRevisionOnConflict } from '../../interfaces/revision';
 import ExpandOrContractButton from '../ExpandOrContractButton';
 import ExpandOrContractButton from '../ExpandOrContractButton';
@@ -29,19 +33,29 @@ Object.keys(DMP).forEach((key) => { window[key] = DMP[key] });
 type ConflictDiffModalProps = {
 type ConflictDiffModalProps = {
   isOpen?: boolean;
   isOpen?: boolean;
   onClose?: (() => void);
   onClose?: (() => void);
-  // pageContainer: PageContainer;
   markdownOnEdit: string;
   markdownOnEdit: string;
   optionsToSave: OptionsToSave | undefined;
   optionsToSave: OptionsToSave | undefined;
+  afterResolvedHandler: () => void,
+};
+
+type ConflictDiffModalCoreProps = {
+  isOpen?: boolean;
+  onClose?: (() => void);
+  optionsToSave: OptionsToSave | undefined;
+  request: IRevisionOnConflictWithStringDate,
+  origin: IRevisionOnConflictWithStringDate,
+  latest: IRevisionOnConflictWithStringDate,
+  afterResolvedHandler: () => void,
 };
 };
 
 
 type IRevisionOnConflictWithStringDate = Omit<IRevisionOnConflict, 'createdAt'> & {
 type IRevisionOnConflictWithStringDate = Omit<IRevisionOnConflict, 'createdAt'> & {
   createdAt: string
   createdAt: string
 }
 }
 
 
-const ConflictDiffModalCore = (props: ConflictDiffModalProps & { currentUser: IUser }): JSX.Element => {
-  const { currentUser, onClose } = props;
-
-  const { data: editorMode } = useEditorMode();
+const ConflictDiffModalCore = (props: ConflictDiffModalCoreProps): JSX.Element => {
+  const {
+    onClose, request, origin, latest, optionsToSave, afterResolvedHandler,
+  } = props;
 
 
   const { t } = useTranslation('');
   const { t } = useTranslation('');
   const [resolvedRevision, setResolvedRevision] = useState<string>('');
   const [resolvedRevision, setResolvedRevision] = useState<string>('');
@@ -49,37 +63,15 @@ const ConflictDiffModalCore = (props: ConflictDiffModalProps & { currentUser: IU
   const [isModalExpanded, setIsModalExpanded] = useState<boolean>(false);
   const [isModalExpanded, setIsModalExpanded] = useState<boolean>(false);
   const [codeMirrorRef, setCodeMirrorRef] = useState<HTMLDivElement | null>(null);
   const [codeMirrorRef, setCodeMirrorRef] = useState<HTMLDivElement | null>(null);
 
 
-  const uncontrolledRef = useRef<CodeMirror>(null);
+  const { data: remoteRevisionId } = useRemoteRevisionId();
+  const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
+  const { data: pageId } = useCurrentPageId();
+  const { data: currentPagePath } = useCurrentPagePath();
+  const { data: currentPathname } = useCurrentPathname();
 
 
-  const currentTime: Date = new Date();
+  const saveOrUpdate = useSaveOrUpdate();
 
 
-  const request: IRevisionOnConflictWithStringDate = {
-    revisionId: '',
-    revisionBody: props.markdownOnEdit,
-    createdAt: format(currentTime, 'yyyy/MM/dd HH:mm:ss'),
-    user: currentUser,
-  };
-  const origin: IRevisionOnConflictWithStringDate = {
-    // revisionId: pageContainer.state.revisionId || '',
-    // revisionBody: pageContainer.state.markdown || '',
-    // createdAt: pageContainer.state.updatedAt || '',
-    // user: pageContainer.state.revisionAuthor,
-    revisionId:  '',
-    revisionBody: '',
-    createdAt: '',
-    user: {} as IUser,
-  };
-  const latest: IRevisionOnConflictWithStringDate = {
-    // revisionId: pageContainer.state.remoteRevisionId || '',
-    // revisionBody: pageContainer.state.remoteRevisionBody || '',
-    // createdAt: format(new Date(pageContainer.state.remoteRevisionUpdateAt || currentTime.toString()), 'yyyy/MM/dd HH:mm:ss'),
-    // user: pageContainer.state.lastUpdateUser,
-    revisionId: '',
-    revisionBody: '',
-    createdAt: format(new Date(''), 'yyyy/MM/dd HH:mm:ss'),
-    user: {} as IUser,
-
-  };
+  const uncontrolledRef = useRef<CodeMirror>(null);
 
 
   useEffect(() => {
   useEffect(() => {
     if (codeMirrorRef != null) {
     if (codeMirrorRef != null) {
@@ -105,21 +97,36 @@ const ConflictDiffModalCore = (props: ConflictDiffModalProps & { currentUser: IU
   }, [onClose]);
   }, [onClose]);
 
 
   const onResolveConflict = useCallback(async() => {
   const onResolveConflict = useCallback(async() => {
+    if (currentPathname == null) { return }
     // disable button after clicked
     // disable button after clicked
     setIsRevisionSelected(false);
     setIsRevisionSelected(false);
 
 
     const codeMirrorVal = uncontrolledRef.current?.editor.doc.getValue();
     const codeMirrorVal = uncontrolledRef.current?.editor.doc.getValue();
 
 
     try {
     try {
-      // await pageContainer.resolveConflict(codeMirrorVal, editorMode, props.optionsToSave);
-      // close();
-      // pageContainer.showSuccessToastr();
+      const { page } = await saveOrUpdate(
+        codeMirrorVal,
+        { pageId, path: currentPagePath || currentPathname, revisionId: remoteRevisionId },
+        optionsToSave,
+      );
+      const remotePageData = {
+        remoteRevisionId: page.revision._id,
+        remoteRevisionBody: page.revision.body,
+        remoteRevisionLastUpdateUser: page.lastUpdateUser,
+        remoteRevisionLastUpdatedAt: page.updatedAt,
+      };
+      setRemoteLatestPageData(remotePageData);
+      afterResolvedHandler();
+
+      close();
+
+      toastSuccess('Saved successfully');
     }
     }
     catch (error) {
     catch (error) {
-      // pageContainer.showErrorToastr(error);
+      toastError(`Error occured: ${error.message}`);
     }
     }
 
 
-  }, []);
+  }, [afterResolvedHandler, close, currentPagePath, currentPathname, optionsToSave, pageId, remoteRevisionId, saveOrUpdate, setRemoteLatestPageData]);
 
 
   const resizeAndCloseButtons = useMemo(() => (
   const resizeAndCloseButtons = useMemo(() => (
     <div className="d-flex flex-nowrap">
     <div className="d-flex flex-nowrap">
@@ -274,12 +281,56 @@ const ConflictDiffModalCore = (props: ConflictDiffModalProps & { currentUser: IU
 
 
 
 
 export const ConflictDiffModal = (props: ConflictDiffModalProps): JSX.Element => {
 export const ConflictDiffModal = (props: ConflictDiffModalProps): JSX.Element => {
-  const { isOpen } = props;
+  const {
+    isOpen, onClose, optionsToSave, afterResolvedHandler,
+  } = props;
   const { data: currentUser } = useCurrentUser();
   const { data: currentUser } = useCurrentUser();
 
 
-  if (!isOpen || currentUser == null) {
+  // state for current page
+  const { data: currentPage } = useSWRxCurrentPage();
+
+  // state for latest page
+  const { data: remoteRevisionId } = useRemoteRevisionId();
+  const { data: remoteRevisionBody } = useRemoteRevisionBody();
+  const { data: remoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
+  const { data: remoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
+
+  const currentTime: Date = new Date();
+
+  const isRemotePageDataInappropriate = remoteRevisionId == null || remoteRevisionBody == null || remoteRevisionLastUpdateUser == null;
+
+  if (!isOpen || currentUser == null || currentPage == null || isRemotePageDataInappropriate) {
     return <></>;
     return <></>;
   }
   }
 
 
-  return <ConflictDiffModalCore {...props} currentUser={currentUser} />;
+  const request: IRevisionOnConflictWithStringDate = {
+    revisionId: '',
+    revisionBody: props.markdownOnEdit,
+    createdAt: format(currentTime, 'yyyy/MM/dd HH:mm:ss'),
+    user: currentUser,
+  };
+  const origin: IRevisionOnConflictWithStringDate = {
+    revisionId: currentPage?.revision._id,
+    revisionBody: currentPage?.revision.body,
+    createdAt: format(currentPage.updatedAt, 'yyyy/MM/dd HH:mm:ss'),
+    user: currentPage?.lastUpdateUser,
+  };
+  const latest: IRevisionOnConflictWithStringDate = {
+    revisionId: remoteRevisionId,
+    revisionBody: remoteRevisionBody,
+    createdAt: format(new Date(remoteRevisionLastUpdatedAt || currentTime.toString()), 'yyyy/MM/dd HH:mm:ss'),
+    user: remoteRevisionLastUpdateUser,
+  };
+
+  const propsForCore = {
+    isOpen,
+    onClose,
+    optionsToSave,
+    request,
+    origin,
+    latest,
+    afterResolvedHandler,
+  };
+
+  return <ConflictDiffModalCore {...propsForCore}/>;
 };
 };

+ 6 - 6
packages/app/src/components/PageStatusAlert.tsx

@@ -7,8 +7,9 @@ import { useEditingMarkdown, useIsConflict } from '~/stores/editor';
 import {
 import {
   useHasDraftOnHackmd, useIsHackmdDraftUpdatingInRealtime, useRevisionIdHackmdSynced,
   useHasDraftOnHackmd, useIsHackmdDraftUpdatingInRealtime, useRevisionIdHackmdSynced,
 } from '~/stores/hackmd';
 } from '~/stores/hackmd';
+import { useConflictDiffModal } from '~/stores/modal';
 import { useSWRxCurrentPage } from '~/stores/page';
 import { useSWRxCurrentPage } from '~/stores/page';
-import { useRemoteRevisionId, useRemoteRevisionLastUpdatUser } from '~/stores/remote-latest-page';
+import { useRemoteRevisionId, useRemoteRevisionLastUpdateUser } from '~/stores/remote-latest-page';
 
 
 import { Username } from './User/Username';
 import { Username } from './User/Username';
 
 
@@ -27,11 +28,12 @@ export const PageStatusAlert = (): JSX.Element => {
   const { data: hasDraftOnHackmd } = useHasDraftOnHackmd();
   const { data: hasDraftOnHackmd } = useHasDraftOnHackmd();
   const { data: isConflict } = useIsConflict();
   const { data: isConflict } = useIsConflict();
   const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
   const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
+  const { open: openConflictDiffModal } = useConflictDiffModal();
 
 
   // store remote latest page data
   // store remote latest page data
   const { data: revisionIdHackmdSynced } = useRevisionIdHackmdSynced();
   const { data: revisionIdHackmdSynced } = useRevisionIdHackmdSynced();
   const { data: remoteRevisionId } = useRemoteRevisionId();
   const { data: remoteRevisionId } = useRemoteRevisionId();
-  const { data: remoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdatUser();
+  const { data: remoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
 
 
   const { data: pageData, mutate: mutatePageData } = useSWRxCurrentPage();
   const { data: pageData, mutate: mutatePageData } = useSWRxCurrentPage();
   const revision = pageData?.revision;
   const revision = pageData?.revision;
@@ -42,10 +44,8 @@ export const PageStatusAlert = (): JSX.Element => {
   }, [mutateEditingMarkdown, mutatePageData]);
   }, [mutateEditingMarkdown, mutatePageData]);
 
 
   const onClickResolveConflict = useCallback(() => {
   const onClickResolveConflict = useCallback(() => {
-    // this.props.pageContainer.setState({
-    //   isConflictDiffModalOpen: true,
-    // });
-  }, []);
+    openConflictDiffModal();
+  }, [openConflictDiffModal]);
 
 
   const getContentsForSomeoneEditingAlert = useCallback((): AlertComponentContents => {
   const getContentsForSomeoneEditingAlert = useCallback((): AlertComponentContents => {
     return {
     return {

+ 27 - 0
packages/app/src/stores/modal.tsx

@@ -553,3 +553,30 @@ export const useHandsontableModal = (status?: HandsontableModalStatus): SWRRespo
     close,
     close,
   };
   };
 };
 };
+
+/*
+ * ConflictDiffModal
+ */
+type ConflictDiffModalStatus = {
+  isOpened: boolean,
+}
+
+type ConflictDiffModalUtils = {
+  open(): void,
+  close(): void,
+}
+
+export const useConflictDiffModal = (): SWRResponse<ConflictDiffModalStatus, Error> & ConflictDiffModalUtils => {
+
+  const initialStatus: ConflictDiffModalStatus = { isOpened: false };
+  const swrResponse = useStaticSWR<ConflictDiffModalStatus, Error>('conflictDiffModal', undefined, { fallbackData: initialStatus });
+
+  return Object.assign(swrResponse, {
+    open: () => {
+      swrResponse.mutate({ isOpened: true });
+    },
+    close: () => {
+      swrResponse.mutate({ isOpened: false });
+    },
+  });
+};

+ 38 - 3
packages/app/src/stores/remote-latest-page.ts

@@ -10,9 +10,44 @@ export const useRemoteRevisionId = (initialData?: string): SWRResponse<string, E
 };
 };
 
 
 export const useRemoteRevisionBody = (initialData?: string): SWRResponse<string, Error> => {
 export const useRemoteRevisionBody = (initialData?: string): SWRResponse<string, Error> => {
-  return useStaticSWR<string, Error>('remoteRevisionId', initialData);
+  return useStaticSWR<string, Error>('remoteRevisionBody', initialData);
+};
+
+export const useRemoteRevisionLastUpdateUser = (initialData?: IUser): SWRResponse<IUser, Error> => {
+  return useStaticSWR<IUser, Error>('remoteRevisionLastUpdateUser', initialData);
+};
+
+export const useRemoteRevisionLastUpdatedAt = (initialData?: Date): SWRResponse<Date, Error> => {
+  return useStaticSWR<Date, Error>('remoteRevisionLastUpdatedAt', initialData);
 };
 };
 
 
-export const useRemoteRevisionLastUpdatUser = (initialData?: IUser): SWRResponse<IUser, Error> => {
-  return useStaticSWR<IUser, Error>('remoteRevisionLastUpdatUser', initialData);
+type RemoteRevisionData = {
+  remoteRevisionId: string,
+  remoteRevisionBody: string,
+  remoteRevisionLastUpdateUser: IUser,
+  remoteRevisionLastUpdatedAt: Date
+}
+
+
+// set remote data all at once
+export const useSetRemoteLatestPageData = (): { setRemoteLatestPageData: (pageData: RemoteRevisionData) => void } => {
+  const { mutate: mutateRemoteRevisionId } = useRemoteRevisionId();
+  const { mutate: mutateRemoteRevisionBody } = useRemoteRevisionBody();
+  const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
+  const { mutate: mutateRemoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
+
+  const setRemoteLatestPageData = (remoteRevisionData: RemoteRevisionData) => {
+    const {
+      remoteRevisionId, remoteRevisionBody, remoteRevisionLastUpdateUser, remoteRevisionLastUpdatedAt,
+    } = remoteRevisionData;
+    mutateRemoteRevisionId(remoteRevisionId);
+    mutateRemoteRevisionBody(remoteRevisionBody);
+    mutateRemoteRevisionLastUpdateUser(remoteRevisionLastUpdateUser);
+    mutateRemoteRevisionLastUpdatedAt(remoteRevisionLastUpdatedAt);
+  };
+
+  return {
+    setRemoteLatestPageData,
+  };
+
 };
 };