Просмотр исходного кода

Merge pull request #4738 from weseek/feat/79579-81268-display-conflict-alert-on-any-page-mode

feat: display conflict alert on any page mode
Yuki Takei 4 лет назад
Родитель
Сommit
cf94a1ba63

+ 7 - 1
packages/app/src/client/services/PageContainer.js

@@ -87,13 +87,14 @@ export default class PageContainer extends Container {
 
 
       // latest(on remote) information
       // latest(on remote) information
       remoteRevisionId: revisionId,
       remoteRevisionId: revisionId,
+      remoteRevisionBody: null,
+      remoteRevisionUpdateAt: null,
       revisionIdHackmdSynced: mainContent.getAttribute('data-page-revision-id-hackmd-synced') || null,
       revisionIdHackmdSynced: mainContent.getAttribute('data-page-revision-id-hackmd-synced') || null,
       lastUpdateUsername: mainContent.getAttribute('data-page-last-update-username') || null,
       lastUpdateUsername: mainContent.getAttribute('data-page-last-update-username') || null,
       deleteUsername: mainContent.getAttribute('data-page-delete-username') || null,
       deleteUsername: mainContent.getAttribute('data-page-delete-username') || null,
       pageIdOnHackmd: mainContent.getAttribute('data-page-id-on-hackmd') || null,
       pageIdOnHackmd: mainContent.getAttribute('data-page-id-on-hackmd') || null,
       hasDraftOnHackmd: !!mainContent.getAttribute('data-page-has-draft-on-hackmd'),
       hasDraftOnHackmd: !!mainContent.getAttribute('data-page-has-draft-on-hackmd'),
       isHackmdDraftUpdatingInRealtime: false,
       isHackmdDraftUpdatingInRealtime: false,
-      isConflictingOnSave: false,
       isConflictDiffModalOpen: false,
       isConflictDiffModalOpen: false,
 
 
       revisionsOnConflict: {},
       revisionsOnConflict: {},
@@ -108,6 +109,7 @@ export default class PageContainer extends Container {
     }
     }
     try {
     try {
       this.state.revisionAuthor = JSON.parse(mainContent.getAttribute('data-page-revision-author'));
       this.state.revisionAuthor = JSON.parse(mainContent.getAttribute('data-page-revision-author'));
+      this.state.lastUpdateUser = JSON.parse(mainContent.getAttribute('data-page-revision-author'));
     }
     }
     catch (e) {
     catch (e) {
       logger.warn('The data of \'data-page-revision-author\' is invalid', e);
       logger.warn('The data of \'data-page-revision-author\' is invalid', e);
@@ -363,8 +365,12 @@ export default class PageContainer extends Container {
   setLatestRemotePageData(s2cMessagePageUpdated) {
   setLatestRemotePageData(s2cMessagePageUpdated) {
     const newState = {
     const newState = {
       remoteRevisionId: s2cMessagePageUpdated.revisionId,
       remoteRevisionId: s2cMessagePageUpdated.revisionId,
+      remoteRevisionBody: s2cMessagePageUpdated.revisionBody,
+      remoteRevisionUpdateAt: s2cMessagePageUpdated.revisionUpdateAt,
       revisionIdHackmdSynced: s2cMessagePageUpdated.revisionIdHackmdSynced,
       revisionIdHackmdSynced: s2cMessagePageUpdated.revisionIdHackmdSynced,
+      // TODO // TODO remove lastUpdateUsername and refactor parts that lastUpdateUsername is used
       lastUpdateUsername: s2cMessagePageUpdated.lastUpdateUsername,
       lastUpdateUsername: s2cMessagePageUpdated.lastUpdateUsername,
+      lastUpdateUser: s2cMessagePageUpdated.remoteLastUpdateUser,
     };
     };
 
 
     if (s2cMessagePageUpdated.hasDraftOnHackmd != null) {
     if (s2cMessagePageUpdated.hasDraftOnHackmd != null) {

+ 141 - 110
packages/app/src/components/PageEditor/ConflictDiffModal.tsx

@@ -3,12 +3,15 @@ import PropTypes from 'prop-types';
 import {
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
   Modal, ModalHeader, ModalBody, ModalFooter,
 } from 'reactstrap';
 } from 'reactstrap';
-import { parseISO, format } from 'date-fns';
 import { useTranslation } from 'react-i18next';
 import { useTranslation } from 'react-i18next';
+import { format } from 'date-fns';
 // TODO: consider whether to use codemirrorEditor
 // TODO: consider whether to use codemirrorEditor
 import { UnControlled as CodeMirrorAny } from 'react-codemirror2';
 import { UnControlled as CodeMirrorAny } from 'react-codemirror2';
+import { UserPicture } from '@growi/ui';
 import PageContainer from '../../client/services/PageContainer';
 import PageContainer from '../../client/services/PageContainer';
 import EditorContainer from '../../client/services/EditorContainer';
 import EditorContainer from '../../client/services/EditorContainer';
+import AppContainer from '../../client/services/AppContainer';
+import { IRevisionOnConflict } from '../../interfaces/revision';
 
 
 require('codemirror/mode/htmlmixed/htmlmixed');
 require('codemirror/mode/htmlmixed/htmlmixed');
 const DMP = require('diff_match_patch');
 const DMP = require('diff_match_patch');
@@ -23,15 +26,42 @@ type ConflictDiffModalProps = {
   onCancel: (() => void) | null;
   onCancel: (() => void) | null;
   pageContainer: PageContainer;
   pageContainer: PageContainer;
   editorContainer: EditorContainer;
   editorContainer: EditorContainer;
+  appContainer: AppContainer;
+  markdownOnEdit: string;
 };
 };
 
 
+type IRevisionOnConflictWithStringDate = Omit<IRevisionOnConflict, 'createdAt'> & {
+  createdAt: string
+}
+
 export const ConflictDiffModal: FC<ConflictDiffModalProps> = (props) => {
 export const ConflictDiffModal: FC<ConflictDiffModalProps> = (props) => {
   const { t } = useTranslation('');
   const { t } = useTranslation('');
   const resolvedRevision = useRef<string>('');
   const resolvedRevision = useRef<string>('');
   const [isRevisionselected, setIsRevisionSelected] = useState<boolean>(false);
   const [isRevisionselected, setIsRevisionSelected] = useState<boolean>(false);
 
 
-  const { pageContainer, editorContainer } = props;
-  const { request, origin, latest } = pageContainer.state.revisionsOnConflict || { request: {}, origin: {}, latest: {} };
+  const { pageContainer, editorContainer, appContainer } = props;
+
+
+  const currentTime: Date = new Date();
+
+  const request: IRevisionOnConflictWithStringDate = {
+    revisionId: '',
+    revisionBody: props.markdownOnEdit,
+    createdAt: format(currentTime, 'yyyy/MM/dd HH:mm:ss'),
+    user: appContainer.currentUser,
+  };
+  const origin: IRevisionOnConflictWithStringDate = {
+    revisionId: pageContainer.state.revisionId || '',
+    revisionBody: pageContainer.state.markdown || '',
+    createdAt: pageContainer.state.updatedAt || '',
+    user: pageContainer.state.revisionAuthor,
+  };
+  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,
+  };
 
 
   const codeMirrorRevisionOption = {
   const codeMirrorRevisionOption = {
     mode: 'htmlmixed',
     mode: 'htmlmixed',
@@ -50,6 +80,7 @@ export const ConflictDiffModal: FC<ConflictDiffModalProps> = (props) => {
   const onResolveConflict = async() : Promise<void> => {
   const onResolveConflict = async() : Promise<void> => {
     // disable button after clicked
     // disable button after clicked
     setIsRevisionSelected(false);
     setIsRevisionSelected(false);
+    editorContainer.disableUnsavedWarning();
     try {
     try {
       await pageContainer.resolveConflictAndReload(
       await pageContainer.resolveConflictAndReload(
         pageContainer.state.pageId,
         pageContainer.state.pageId,
@@ -69,119 +100,117 @@ export const ConflictDiffModal: FC<ConflictDiffModalProps> = (props) => {
         <i className="icon-fw icon-exclamation" />{t('modal_resolve_conflict.resolve_conflict')}
         <i className="icon-fw icon-exclamation" />{t('modal_resolve_conflict.resolve_conflict')}
       </ModalHeader>
       </ModalHeader>
       <ModalBody>
       <ModalBody>
-        {Object.keys(pageContainer.state.revisionsOnConflict || {}).length > 0
-          && (
-            <div className="row mx-2">
-              <div className="col-12 text-center mt-2 mb-4">
-                <h2 className="font-weight-bold">{t('modal_resolve_conflict.resolve_conflict_message')}</h2>
+        <div className="row mx-2">
+          <div className="col-12 text-center mt-2 mb-4">
+            <h2 className="font-weight-bold">{t('modal_resolve_conflict.resolve_conflict_message')}</h2>
+          </div>
+          <div className="col-12 col-md-4 border border-dark">
+            <h3 className="font-weight-bold my-2">{t('modal_resolve_conflict.requested_revision')}</h3>
+            <div className="d-flex align-items-center my-3">
+              <div>
+                <UserPicture user={request.user} size="lg" noLink noTooltip />
               </div>
               </div>
-              <div className="col-12 col-md-4 border border-dark">
-                <h3 className="font-weight-bold my-2">{t('modal_resolve_conflict.requested_revision')}</h3>
-                <div className="d-flex align-items-center my-3">
-                  <div>
-                    <img height="40px" className="rounded-circle" src={request.userImgPath} />
-                  </div>
-                  <div className="ml-3 text-muted">
-                    <p className="my-0">updated by {request.userName}</p>
-                    <p className="my-0">{format(parseISO(request.createdAt), 'yyyy/MM/dd HH:mm:ss')}</p>
-                  </div>
-                </div>
-                <CodeMirror
-                  value={request.revisionBody}
-                  options={codeMirrorRevisionOption}
-                />
-                <div className="text-center my-4">
-                  <button
-                    type="button"
-                    className="btn btn-primary"
-                    onClick={() => {
-                      setIsRevisionSelected(true);
-                      resolvedRevision.current = request.revisionBody;
-                    }}
-                  >
-                    <i className="icon-fw icon-arrow-down-circle"></i>
-                    {t('modal_resolve_conflict.select_revision', { revision: 'request' })}
-                  </button>
-                </div>
+              <div className="ml-3 text-muted">
+                <p className="my-0">updated by {request.user.username}</p>
+                <p className="my-0">{request.createdAt}</p>
               </div>
               </div>
-              <div className="col-12 col-md-4 border border-dark">
-                <h3 className="font-weight-bold my-2">{t('origin_revision')}</h3>
-                <div className="d-flex align-items-center my-3">
-                  <div>
-                    <img height="40px" className="rounded-circle" src={origin.userImgPath} />
-                  </div>
-                  <div className="ml-3 text-muted">
-                    <p className="my-0">updated by {origin.userName}</p>
-                    <p className="my-0">{format(parseISO(origin.createdAt), 'yyyy/MM/dd HH:mm:ss')}</p>
-                  </div>
-                </div>
-                <CodeMirror
-                  value={origin.revisionBody}
-                  options={codeMirrorRevisionOption}
-                />
-                <div className="text-center my-4">
-                  <button
-                    type="button"
-                    className="btn btn-primary"
-                    onClick={() => {
-                      setIsRevisionSelected(true);
-                      resolvedRevision.current = origin.revisionBody;
-                    }}
-                  >
-                    <i className="icon-fw icon-arrow-down-circle"></i>
-                    {t('modal_resolve_conflict.select_revision', { revision: 'origin' })}
-                  </button>
-                </div>
+            </div>
+            <CodeMirror
+              value={request.revisionBody}
+              options={codeMirrorRevisionOption}
+            />
+            <div className="text-center my-4">
+              <button
+                type="button"
+                className="btn btn-primary"
+                onClick={() => {
+                  setIsRevisionSelected(true);
+                  resolvedRevision.current = request.revisionBody;
+                }}
+              >
+                <i className="icon-fw icon-arrow-down-circle"></i>
+                {t('modal_resolve_conflict.select_revision', { revision: 'request' })}
+              </button>
+            </div>
+          </div>
+          <div className="col-12 col-md-4 border border-dark">
+            <h3 className="font-weight-bold my-2">{t('origin_revision')}</h3>
+            <div className="d-flex align-items-center my-3">
+              <div>
+                <UserPicture user={origin.user} size="lg" noLink noTooltip />
+              </div>
+              <div className="ml-3 text-muted">
+                <p className="my-0">updated by {origin.user.username}</p>
+                <p className="my-0">{origin.createdAt}</p>
               </div>
               </div>
-              <div className="col-12 col-md-4 border border-dark">
-                <h3 className="font-weight-bold my-2">{t('modal_resolve_conflict.latest_revision')}</h3>
-                <div className="d-flex align-items-center my-3">
-                  <div>
-                    <img height="40px" className="rounded-circle" src={latest.userImgPath} />
-                  </div>
-                  <div className="ml-3 text-muted">
-                    <p className="my-0">updated by {latest.userName}</p>
-                    <p className="my-0">{format(parseISO(latest.createdAt), 'yyyy/MM/dd HH:mm:ss')}</p>
-                  </div>
-                </div>
-                <CodeMirror
-                  value={latest.revisionBody}
-                  options={codeMirrorRevisionOption}
-                />
-                <div className="text-center my-4">
-                  <button
-                    type="button"
-                    className="btn btn-primary"
-                    onClick={() => {
-                      setIsRevisionSelected(true);
-                      resolvedRevision.current = latest.revisionBody;
-                    }}
-                  >
-                    <i className="icon-fw icon-arrow-down-circle"></i>
-                    {t('modal_resolve_conflict.select_revision', { revision: 'latest' })}
-                  </button>
-                </div>
+            </div>
+            <CodeMirror
+              value={origin.revisionBody}
+              options={codeMirrorRevisionOption}
+            />
+            <div className="text-center my-4">
+              <button
+                type="button"
+                className="btn btn-primary"
+                onClick={() => {
+                  setIsRevisionSelected(true);
+                  if (resolvedRevision != null) {
+                    resolvedRevision.current = origin.revisionBody;
+                  }
+                }}
+              >
+                <i className="icon-fw icon-arrow-down-circle"></i>
+                {t('modal_resolve_conflict.select_revision', { revision: 'origin' })}
+              </button>
+            </div>
+          </div>
+          <div className="col-12 col-md-4 border border-dark">
+            <h3 className="font-weight-bold my-2">{t('modal_resolve_conflict.latest_revision')}</h3>
+            <div className="d-flex align-items-center my-3">
+              <div>
+                <UserPicture user={latest.user} size="lg" noLink noTooltip />
               </div>
               </div>
-              <div className="col-12 border border-dark">
-                <h3 className="font-weight-bold my-2">{t('modal_resolve_conflict.selected_editable_revision')}</h3>
-                <CodeMirror
-                  value={resolvedRevision.current}
-                  options={{
-                    mode: 'htmlmixed',
-                    lineNumbers: true,
-                    tabSize: 2,
-                    indentUnit: 2,
-                    placeholder: t('modal_resolve_conflict.resolve_conflict_message'),
-                  }}
-                  onChange={(editor, data, pageBody) => {
-                    if (pageBody === '') setIsRevisionSelected(false);
-                    resolvedRevision.current = pageBody;
-                  }}
-                />
+              <div className="ml-3 text-muted">
+                <p className="my-0">updated by {latest.user.username}</p>
+                <p className="my-0">{latest.createdAt}</p>
               </div>
               </div>
             </div>
             </div>
-          )
-        }
+            <CodeMirror
+              value={latest.revisionBody}
+              options={codeMirrorRevisionOption}
+            />
+            <div className="text-center my-4">
+              <button
+                type="button"
+                className="btn btn-primary"
+                onClick={() => {
+                  setIsRevisionSelected(true);
+                  resolvedRevision.current = latest.revisionBody;
+                }}
+              >
+                <i className="icon-fw icon-arrow-down-circle"></i>
+                {t('modal_resolve_conflict.select_revision', { revision: 'latest' })}
+              </button>
+            </div>
+          </div>
+          <div className="col-12 border border-dark">
+            <h3 className="font-weight-bold my-2">{t('modal_resolve_conflict.selected_editable_revision')}</h3>
+            <CodeMirror
+              value={resolvedRevision.current}
+              options={{
+                mode: 'htmlmixed',
+                lineNumbers: true,
+                tabSize: 2,
+                indentUnit: 2,
+                placeholder: t('modal_resolve_conflict.resolve_conflict_message'),
+              }}
+              onChange={(editor, data, pageBody) => {
+                if (pageBody === '') setIsRevisionSelected(false);
+                resolvedRevision.current = pageBody;
+              }}
+            />
+          </div>
+        </div>
       </ModalBody>
       </ModalBody>
       <ModalFooter>
       <ModalFooter>
         <button
         <button
@@ -209,6 +238,8 @@ ConflictDiffModal.propTypes = {
   onCancel: PropTypes.func,
   onCancel: PropTypes.func,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
   editorContainer:  PropTypes.instanceOf(EditorContainer).isRequired,
   editorContainer:  PropTypes.instanceOf(EditorContainer).isRequired,
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  markdownOnEdit: PropTypes.string.isRequired,
 };
 };
 
 
 ConflictDiffModal.defaultProps = {
 ConflictDiffModal.defaultProps = {

+ 8 - 1
packages/app/src/components/PageEditor/Editor.jsx

@@ -11,6 +11,7 @@ import Dropzone from 'react-dropzone';
 
 
 import EditorContainer from '~/client/services/EditorContainer';
 import EditorContainer from '~/client/services/EditorContainer';
 import PageContainer from '~/client/services/PageContainer';
 import PageContainer from '~/client/services/PageContainer';
+import AppContainer from '~/client/services/AppContainer';
 import { withUnstatedContainers } from '../UnstatedUtils';
 import { withUnstatedContainers } from '../UnstatedUtils';
 
 
 import Cheatsheet from './Cheatsheet';
 import Cheatsheet from './Cheatsheet';
@@ -278,6 +279,7 @@ class Editor extends AbstractEditor {
     );
     );
   }
   }
 
 
+
   render() {
   render() {
     const flexContainer = {
     const flexContainer = {
       height: '100%',
       height: '100%',
@@ -374,8 +376,10 @@ class Editor extends AbstractEditor {
         <ConflictDiffModal
         <ConflictDiffModal
           isOpen={this.props.pageContainer.state.isConflictDiffModalOpen}
           isOpen={this.props.pageContainer.state.isConflictDiffModalOpen}
           onCancel={() => this.props.pageContainer.setState({ isConflictDiffModalOpen: false })}
           onCancel={() => this.props.pageContainer.setState({ isConflictDiffModalOpen: false })}
+          appContainer={this.props.appContainer}
           pageContainer={this.props.pageContainer}
           pageContainer={this.props.pageContainer}
           editorContainer={this.props.editorContainer}
           editorContainer={this.props.editorContainer}
+          markdownOnEdit={this.props.value}
         />
         />
       </>
       </>
     );
     );
@@ -385,6 +389,8 @@ class Editor extends AbstractEditor {
 
 
 Editor.propTypes = Object.assign({
 Editor.propTypes = Object.assign({
   noCdn: PropTypes.bool,
   noCdn: PropTypes.bool,
+  // this value is markdown
+  value: PropTypes.string,
   isMobile: PropTypes.bool,
   isMobile: PropTypes.bool,
   isUploadable: PropTypes.bool,
   isUploadable: PropTypes.bool,
   isUploadableFile: PropTypes.bool,
   isUploadableFile: PropTypes.bool,
@@ -393,6 +399,7 @@ Editor.propTypes = Object.assign({
   onUpload: PropTypes.func,
   onUpload: PropTypes.func,
   editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
   editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
 }, AbstractEditor.propTypes);
 }, AbstractEditor.propTypes);
 
 
-export default withUnstatedContainers(Editor, [EditorContainer, PageContainer]);
+export default withUnstatedContainers(Editor, [EditorContainer, PageContainer, AppContainer]);

+ 33 - 7
packages/app/src/components/PageStatusAlert.jsx

@@ -29,12 +29,19 @@ class PageStatusAlert extends React.Component {
     this.getContentsForRevisionOutdated = this.getContentsForRevisionOutdated.bind(this);
     this.getContentsForRevisionOutdated = this.getContentsForRevisionOutdated.bind(this);
     this.getContentsForDraftExistsAlert = this.getContentsForDraftExistsAlert.bind(this);
     this.getContentsForDraftExistsAlert = this.getContentsForDraftExistsAlert.bind(this);
     this.getContentsForUpdatedAlert = this.getContentsForUpdatedAlert.bind(this);
     this.getContentsForUpdatedAlert = this.getContentsForUpdatedAlert.bind(this);
+    this.onClickResolveConflict = this.onClickResolveConflict.bind(this);
   }
   }
 
 
   refreshPage() {
   refreshPage() {
     window.location.reload();
     window.location.reload();
   }
   }
 
 
+  onClickResolveConflict() {
+    this.props.pageContainer.setState({
+      isConflictDiffModalOpen: true,
+    });
+  }
+
   getContentsForSomeoneEditingAlert() {
   getContentsForSomeoneEditingAlert() {
     const { t } = this.props;
     const { t } = this.props;
     return [
     return [
@@ -51,7 +58,17 @@ class PageStatusAlert extends React.Component {
   }
   }
 
 
   getContentsForRevisionOutdated() {
   getContentsForRevisionOutdated() {
-    const { t, pageContainer } = this.props;
+    const { t, appContainer, pageContainer } = this.props;
+    const pageEditor = appContainer.getComponentInstance('PageEditor');
+
+    let markdownOnEdit = '';
+    let isConflictOnEdit = false;
+
+    if (pageEditor != null) {
+      markdownOnEdit = pageEditor.getMarkdown();
+      isConflictOnEdit = markdownOnEdit !== pageContainer.state.markdown;
+    }
+
     return [
     return [
       ['bg-warning', 'd-hackmd-none'],
       ['bg-warning', 'd-hackmd-none'],
       <>
       <>
@@ -63,10 +80,18 @@ class PageStatusAlert extends React.Component {
           <i className="icon-fw icon-reload mr-1"></i>
           <i className="icon-fw icon-reload mr-1"></i>
           {t('Load latest')}
           {t('Load latest')}
         </button>
         </button>
-        <button type="button" onClick={() => pageContainer.setState({ isConflictDiffModalOpen: true })} className="btn btn-outline-white">
-          <i className="fa fa-fw fa-file-text-o mr-1"></i>
-          {t('modal_resolve_conflict.resolve_conflict')}
-        </button>
+        {isConflictOnEdit
+          && (
+            <button
+              type="button"
+              onClick={this.onClickResolveConflict}
+              className="btn btn-outline-white"
+            >
+              <i className="fa fa-fw fa-file-text-o mr-1"></i>
+              {t('modal_resolve_conflict.resolve_conflict')}
+            </button>
+          )
+        }
       </>,
       </>,
     ];
     ];
   }
   }
@@ -106,15 +131,16 @@ class PageStatusAlert extends React.Component {
 
 
   render() {
   render() {
     const {
     const {
-      revisionId, revisionIdHackmdSynced, remoteRevisionId, hasDraftOnHackmd, isHackmdDraftUpdatingInRealtime, isConflictingOnSave,
+      revisionId, revisionIdHackmdSynced, remoteRevisionId, hasDraftOnHackmd, isHackmdDraftUpdatingInRealtime,
     } = this.props.pageContainer.state;
     } = this.props.pageContainer.state;
 
 
     const isRevisionOutdated = revisionId !== remoteRevisionId;
     const isRevisionOutdated = revisionId !== remoteRevisionId;
     const isHackmdDocumentOutdated = revisionIdHackmdSynced !== remoteRevisionId;
     const isHackmdDocumentOutdated = revisionIdHackmdSynced !== remoteRevisionId;
 
 
     let getContentsFunc = null;
     let getContentsFunc = null;
+
     // when conflicting on save
     // when conflicting on save
-    if (isConflictingOnSave) {
+    if (isRevisionOutdated) {
       getContentsFunc = this.getContentsForRevisionOutdated;
       getContentsFunc = this.getContentsForRevisionOutdated;
     }
     }
     // when remote revision is newer than both
     // when remote revision is newer than both

+ 0 - 6
packages/app/src/components/SavePageControls.jsx

@@ -49,12 +49,6 @@ class SavePageControls extends React.Component {
     catch (error) {
     catch (error) {
       logger.error('failed to save', error);
       logger.error('failed to save', error);
       pageContainer.showErrorToastr(error);
       pageContainer.showErrorToastr(error);
-      if (error.code === 'conflict') {
-        pageContainer.setState({
-          isConflictingOnSave: true,
-          revisionsOnConflict: error.data,
-        });
-      }
     }
     }
   }
   }
 
 

+ 7 - 0
packages/app/src/interfaces/revision.ts

@@ -7,3 +7,10 @@ export type IRevision = {
   createdAt: Date,
   createdAt: Date,
   updatedAt: Date,
   updatedAt: Date,
 }
 }
+
+export type IRevisionOnConflict = {
+  revisionId: string,
+  revisionBody: string,
+  createdAt: Date,
+  user: IUser
+}

+ 5 - 1
packages/app/src/server/models/vo/s2c-message.js

@@ -10,15 +10,19 @@ class S2cMessagePageUpdated {
     const serializedPage = serializePageSecurely(page);
     const serializedPage = serializePageSecurely(page);
 
 
     const {
     const {
-      _id, revision, revisionHackmdSynced, hasDraftOnHackmd,
+      _id, revision, updatedAt, revisionHackmdSynced, hasDraftOnHackmd,
     } = serializedPage;
     } = serializedPage;
 
 
     this.pageId = _id;
     this.pageId = _id;
     this.revisionId = revision;
     this.revisionId = revision;
+    this.revisionBody = page.revision.body;
+    this.revisionUpdateAt = updatedAt;
     this.revisionIdHackmdSynced = revisionHackmdSynced;
     this.revisionIdHackmdSynced = revisionHackmdSynced;
     this.hasDraftOnHackmd = hasDraftOnHackmd;
     this.hasDraftOnHackmd = hasDraftOnHackmd;
 
 
     if (user != null) {
     if (user != null) {
+      this.remoteLastUpdateUser = user;
+      // TODO remove lastUpdateUsername and refactor parts that lastUpdateUsername is used
       this.lastUpdateUsername = user.name;
       this.lastUpdateUsername = user.name;
     }
     }
   }
   }

+ 1 - 29
packages/app/src/server/routes/page.js

@@ -831,35 +831,7 @@ module.exports = function(crowi, app) {
     const Revision = crowi.model('Revision');
     const Revision = crowi.model('Revision');
     let page = await Page.findByIdAndViewer(pageId, req.user);
     let page = await Page.findByIdAndViewer(pageId, req.user);
     if (page != null && revisionId != null && !page.isUpdatable(revisionId)) {
     if (page != null && revisionId != null && !page.isUpdatable(revisionId)) {
-      const populatedFields = 'name imageUrlCached';
-      // when isUpdatable is false, originRevisionId is a reqested revisionId
-      const originRevision = await Revision.findById(revisionId).populate('author', populatedFields);
-      const latestRevision = await Revision.findById(page.revision).populate('author', populatedFields);
-
-      const revisions = {};
-
-      revisions.request = {
-        revisionId: '',
-        revisionBody: pageBody,
-        createdAt: new Date(),
-        userName: req.user.name,
-        userImgPath: req.user.imageUrlCached,
-      };
-      revisions.origin = {
-        revisionId: originRevision._id.toString(),
-        revisionBody: originRevision.body,
-        createdAt: originRevision.createdAt,
-        userName: originRevision.author.name,
-        userImgPath: originRevision.author.imageUrlCached,
-      };
-      revisions.latest = {
-        revisionId: latestRevision._id.toString(),
-        revisionBody: latestRevision.body,
-        createdAt: latestRevision.createdAt,
-        userName: latestRevision.author.name,
-        userImgPath: latestRevision.author.imageUrlCached,
-      };
-      return res.json(ApiResponse.error('Posted param "revisionId" is outdated.', 'conflict', revisions));
+      return res.json(ApiResponse.error('Posted param "revisionId" is outdated.', 'conflict'));
     }
     }
 
 
     const options = { isSyncRevisionToHackmd };
     const options = { isSyncRevisionToHackmd };