Shun Miyazawa пре 2 година
родитељ
комит
d605700acf
1 измењених фајлова са 43 додато и 18 уклоњено
  1. 43 18
      apps/app/src/components/PageEditor/PageEditor.tsx

+ 43 - 18
apps/app/src/components/PageEditor/PageEditor.tsx

@@ -20,9 +20,9 @@ import { throttle, debounce } from 'throttle-debounce';
 
 
 import { useShouldExpandContent } from '~/client/services/layout';
 import { useShouldExpandContent } from '~/client/services/layout';
 import { useUpdateStateAfterSave } from '~/client/services/page-operation';
 import { useUpdateStateAfterSave } from '~/client/services/page-operation';
-import { updatePage } from '~/client/services/update-page';
+import { updatePage, extractRemoteRevisionDataFromErrorObj } from '~/client/services/update-page';
 import { apiv3Get, apiv3PostForm } from '~/client/util/apiv3-client';
 import { apiv3Get, apiv3PostForm } from '~/client/util/apiv3-client';
-import { toastError, toastSuccess } from '~/client/util/toastr';
+import { toastError, toastSuccess, toastWarning } from '~/client/util/toastr';
 import { SocketEventName } from '~/interfaces/websocket';
 import { SocketEventName } from '~/interfaces/websocket';
 import {
 import {
   useDefaultIndentSize, useCurrentUser,
   useDefaultIndentSize, useCurrentUser,
@@ -42,11 +42,13 @@ import {
   useCurrentPagePath, useSWRMUTxCurrentPage, useSWRxCurrentPage, useSWRxTagsInfo, useCurrentPageId, useIsNotFound, useIsLatestRevision, useTemplateBodyData,
   useCurrentPagePath, useSWRMUTxCurrentPage, useSWRxCurrentPage, useSWRxTagsInfo, useCurrentPageId, useIsNotFound, useIsLatestRevision, useTemplateBodyData,
 } from '~/stores/page';
 } from '~/stores/page';
 import { mutatePageTree } from '~/stores/page-listing';
 import { mutatePageTree } from '~/stores/page-listing';
+import type { RemoteRevisionData } from '~/stores/remote-latest-page';
 import {
 import {
   useRemoteRevisionId,
   useRemoteRevisionId,
   useRemoteRevisionBody,
   useRemoteRevisionBody,
   useRemoteRevisionLastUpdatedAt,
   useRemoteRevisionLastUpdatedAt,
   useRemoteRevisionLastUpdateUser,
   useRemoteRevisionLastUpdateUser,
+  useSetRemoteLatestPageData,
 } from '~/stores/remote-latest-page';
 } from '~/stores/remote-latest-page';
 import { usePreviewOptions } from '~/stores/renderer';
 import { usePreviewOptions } from '~/stores/renderer';
 import {
 import {
@@ -77,6 +79,17 @@ declare global {
 let isOriginOfScrollSyncEditor = false;
 let isOriginOfScrollSyncEditor = false;
 let isOriginOfScrollSyncPreview = false;
 let isOriginOfScrollSyncPreview = false;
 
 
+type ConflictHandler = (
+  conflictData: RemoteRevisionData,
+  newMarkdown: string
+) => void;
+
+type Save = (
+  revisionId?: string,
+  opts?: { slackChannels?: string, overwriteScopesOfDescendants?: boolean },
+  onConflict?: ConflictHandler
+) => Promise<IPageHasId | null>
+
 type Props = {
 type Props = {
   visibility?: boolean,
   visibility?: boolean,
 }
 }
@@ -114,6 +127,7 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
   const { mutate: mutateRemoteRevisionId } = useRemoteRevisionBody();
   const { mutate: mutateRemoteRevisionId } = useRemoteRevisionBody();
   const { mutate: mutateRemoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
   const { mutate: mutateRemoteRevisionLastUpdatedAt } = useRemoteRevisionLastUpdatedAt();
   const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
   const { mutate: mutateRemoteRevisionLastUpdateUser } = useRemoteRevisionLastUpdateUser();
+  const { setRemoteLatestPageData } = useSetRemoteLatestPageData();
   const { data: user } = useCurrentUser();
   const { data: user } = useCurrentUser();
   const { onEditorsUpdated } = useEditingUsers();
   const { onEditorsUpdated } = useEditingUsers();
 
 
@@ -189,21 +203,32 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
 
 
   }, [socket, checkIsConflict]);
   }, [socket, checkIsConflict]);
 
 
-  const save = useCallback(async(opts?: {slackChannels: string, overwriteScopesOfDescendants?: boolean}): Promise<IPageHasId | null> => {
-    if (pageId == null || currentRevisionId == null || grantData == null) {
+  const generateResolveConflictHandler = useCallback((revisionId: string, onConflict: ConflictHandler) => {
+    //
+  }, []);
+
+  const onConflictHandler: ConflictHandler = useCallback((remoteRevidsionData, newMarkdown) => {
+    setRemoteLatestPageData(remoteRevidsionData);
+
+  }, [setRemoteLatestPageData]);
+
+  const save: Save = useCallback(async(revisionId, opts, onConflict) => {
+    if (pageId == null || grantData == null) {
       logger.error('Some materials to save are invalid', {
       logger.error('Some materials to save are invalid', {
         pageId, currentRevisionId, grantData,
         pageId, currentRevisionId, grantData,
       });
       });
       throw new Error('Some materials to save are invalid');
       throw new Error('Some materials to save are invalid');
     }
     }
 
 
+    const newMarkdown = codeMirrorEditor?.getDoc() ?? '';
+
     try {
     try {
       mutateWaitingSaveProcessing(true);
       mutateWaitingSaveProcessing(true);
       const isRevisionIdRequiredForPageUpdate = currentPage?.revision?.origin === undefined;
       const isRevisionIdRequiredForPageUpdate = currentPage?.revision?.origin === undefined;
 
 
       const { page } = await updatePage({
       const { page } = await updatePage({
         pageId,
         pageId,
-        revisionId: isRevisionIdRequiredForPageUpdate ? currentRevisionId : undefined,
+        revisionId: isRevisionIdRequiredForPageUpdate ? revisionId : undefined,
         body: codeMirrorEditor?.getDoc() ?? '',
         body: codeMirrorEditor?.getDoc() ?? '',
         grant: grantData?.grant,
         grant: grantData?.grant,
         origin: Origin.Editor,
         origin: Origin.Editor,
@@ -220,41 +245,41 @@ export const PageEditor = React.memo((props: Props): JSX.Element => {
     }
     }
     catch (error) {
     catch (error) {
       logger.error('failed to save', error);
       logger.error('failed to save', error);
-      toastError(error);
-      if (error.code === 'conflict') {
-        mutateRemotePageId(error.data.revisionId);
-        mutateRemoteRevisionId(error.data.revisionBody);
-        mutateRemoteRevisionLastUpdatedAt(error.data.createdAt);
-        mutateRemoteRevisionLastUpdateUser(error.data.user);
+
+      const remoteRevisionData = extractRemoteRevisionDataFromErrorObj(error);
+      if (remoteRevisionData != null) {
+        onConflict?.(remoteRevisionData, newMarkdown);
+        toastWarning(t('modal_resolve_conflict.conflicts_with_new_body_on_server_side'));
+        return null;
       }
       }
+
+      toastError(error);
       return null;
       return null;
     }
     }
     finally {
     finally {
       mutateWaitingSaveProcessing(false);
       mutateWaitingSaveProcessing(false);
     }
     }
-
-  // eslint-disable-next-line max-len
-  }, [codeMirrorEditor, grantData, pageId, currentRevisionId, mutateWaitingSaveProcessing, mutateRemotePageId, mutateRemoteRevisionId, mutateRemoteRevisionLastUpdatedAt, mutateRemoteRevisionLastUpdateUser]);
+  }, [pageId, grantData, codeMirrorEditor, currentRevisionId, mutateWaitingSaveProcessing, currentPage?.revision?.origin, t]);
 
 
   const saveAndReturnToViewHandler = useCallback(async(opts: {slackChannels: string, overwriteScopesOfDescendants?: boolean}) => {
   const saveAndReturnToViewHandler = useCallback(async(opts: {slackChannels: string, overwriteScopesOfDescendants?: boolean}) => {
-    const page = await save(opts);
+    const page = await save(currentRevisionId, opts, onConflictHandler);
     if (page == null) {
     if (page == null) {
       return;
       return;
     }
     }
 
 
     mutateEditorMode(EditorMode.View);
     mutateEditorMode(EditorMode.View);
     updateStateAfterSave?.();
     updateStateAfterSave?.();
-  }, [mutateEditorMode, save, updateStateAfterSave]);
+  }, [currentRevisionId, mutateEditorMode, onConflictHandler, save, updateStateAfterSave]);
 
 
   const saveWithShortcut = useCallback(async() => {
   const saveWithShortcut = useCallback(async() => {
-    const page = await save();
+    const page = await save(currentRevisionId, undefined, onConflictHandler);
     if (page == null) {
     if (page == null) {
       return;
       return;
     }
     }
 
 
     toastSuccess(t('toaster.save_succeeded'));
     toastSuccess(t('toaster.save_succeeded'));
     updateStateAfterSave?.();
     updateStateAfterSave?.();
-  }, [save, t, updateStateAfterSave]);
+  }, [currentRevisionId, onConflictHandler, save, t, updateStateAfterSave]);
 
 
 
 
   // the upload event handler
   // the upload event handler