Parcourir la source

Merge branch 'feat/79579-upgrade-conflict-modal' into feat/79579-commonize-editor-component

stevenfukase il y a 4 ans
Parent
commit
539c422339

+ 1 - 1
packages/app/package.json

@@ -84,7 +84,7 @@
     "cookie-parser": "^1.4.5",
     "csrf": "^3.1.0",
     "date-fns": "^2.23.0",
-    "detect-indent": "^6.0.0",
+    "detect-indent": "^7.0.0",
     "diff": "^5.0.0",
     "diff_match_patch": "^0.1.1",
     "elasticsearch": "^16.0.0",

+ 0 - 2
packages/app/src/client/services/PageContainer.js

@@ -96,8 +96,6 @@ export default class PageContainer extends Container {
       hasDraftOnHackmd: !!mainContent.getAttribute('data-page-has-draft-on-hackmd'),
       isHackmdDraftUpdatingInRealtime: false,
       isConflictDiffModalOpen: false,
-
-      revisionsOnConflict: {},
     };
 
     // parse creator, lastUpdateUser and revisionAuthor

+ 1 - 1
packages/app/src/client/util/apiv3-client.ts

@@ -36,7 +36,7 @@ const apiv3ErrorHandler = (_err) => {
 export async function apiv3Request<T = any>(method: string, path: string, params: unknown): Promise<AxiosResponse<T>> {
   try {
     const res = await axios[method](urljoin(apiv3Root, path), params);
-    return res.data;
+    return res;
   }
   catch (err) {
     const errors = apiv3ErrorHandler(err);

+ 2 - 1
packages/app/src/components/Admin/ElasticsearchManagement/ElasticsearchManagement.jsx

@@ -70,7 +70,8 @@ class ElasticsearchManagement extends React.Component {
     const { appContainer } = this.props;
 
     try {
-      const { info } = await appContainer.apiv3Get('/search/indices');
+      const { data } = await appContainer.apiv3Get('/search/indices');
+      const { info } = data;
 
       this.setState({
         isConnected: true,

+ 106 - 102
packages/app/src/components/PageEditor/ConflictDiffModal.tsx

@@ -39,7 +39,6 @@ export const ConflictDiffModal: FC<ConflictDiffModalProps> = (props) => {
 
   const { pageContainer, editorContainer, appContainer } = props;
 
-
   const currentTime: Date = new Date();
 
   const request: IRevisionOnConflictWithStringDate = {
@@ -98,113 +97,118 @@ export const ConflictDiffModal: FC<ConflictDiffModalProps> = (props) => {
         <i className="icon-fw icon-exclamation" />{t('modal_resolve_conflict.resolve_conflict')}
       </ModalHeader>
       <ModalBody>
-        <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 />
+        {
+          pageContainer.state.isConflictDiffModalOpen
+          && (
+            <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="ml-3 text-muted">
-                <p className="my-0">updated by {request.user?.username}</p>
-                <p className="my-0">{request.createdAt}</p>
+              <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 className="ml-3 text-muted">
+                    <p className="my-0">updated by {request.user.username}</p>
+                    <p className="my-0">{request.createdAt}</p>
+                  </div>d
+                </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>
-            <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 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>
+                <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="ml-3 text-muted">
-                <p className="my-0">updated by {origin.user?.username}</p>
-                <p className="my-0">{origin.createdAt}</p>
+              <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 className="ml-3 text-muted">
+                    <p className="my-0">updated by {latest.user.username}</p>
+                    <p className="my-0">{latest.createdAt}</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>
-            </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 className="ml-3 text-muted">
-                <p className="my-0">updated by {latest.user?.username}</p>
-                <p className="my-0">{latest.createdAt}</p>
+              <div className="col-12 border border-dark">
+                <h3 className="font-weight-bold my-2">{t('modal_resolve_conflict.selected_editable_revision')}</h3>
+                <UncontrolledCodeMirror
+                  value={resolvedRevision.current}
+                  options={{
+                    placeholder: t('modal_resolve_conflict.resolve_conflict_message'),
+                  }}
+                  onChange={(editor, data, pageBody) => {
+                    if (pageBody === '') setIsRevisionSelected(false);
+                    resolvedRevision.current = pageBody;
+                  }}
+                />
               </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>
-            <UncontrolledCodeMirror
-              value={resolvedRevision.current}
-              options={{
-                placeholder: t('modal_resolve_conflict.resolve_conflict_message'),
-              }}
-              onChange={(editor, data, pageBody) => {
-                if (pageBody === '') setIsRevisionSelected(false);
-                resolvedRevision.current = pageBody;
-              }}
-            />
-          </div>
-        </div>
+          )
+        }
       </ModalBody>
       <ModalFooter>
         <button

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

@@ -49,6 +49,14 @@ class SavePageControls extends React.Component {
     catch (error) {
       logger.error('failed to save', error);
       pageContainer.showErrorToastr(error);
+      if (error.code === 'conflict') {
+        pageContainer.setState({
+          remoteRevisionId: error.data.revisionId,
+          remoteRevisionBody: error.data.revisionBody,
+          remoteRevisionUpdateAt: error.data.createdAt,
+          lastUpdateUser: error.data.user,
+        });
+      }
     }
   }
 

+ 1 - 1
packages/app/src/server/routes/apiv3/response.js

@@ -10,7 +10,7 @@ const addCustomFunctionToResponse = (express, crowi) => {
       throw new Error('invalid value supplied to res.apiv3');
     }
 
-    this.status(status).json({ data: obj });
+    this.status(status).json(obj);
   };
 
   express.response.apiv3Err = function(_err, status = 400, info) { // not arrow function

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

@@ -831,7 +831,14 @@ module.exports = function(crowi, app) {
     const Revision = crowi.model('Revision');
     let page = await Page.findByIdAndViewer(pageId, req.user);
     if (page != null && revisionId != null && !page.isUpdatable(revisionId)) {
-      return res.json(ApiResponse.error('Posted param "revisionId" is outdated.', 'conflict'));
+      const latestRevision = await Revision.findById(page.revision).populate('author');
+      const returnLatestRevision = {
+        revisionId: latestRevision._id.toString(),
+        revisionBody: xss.process(latestRevision.body),
+        createdAt: latestRevision.createdAt,
+        user: serializeUserSecurely(latestRevision.author),
+      };
+      return res.json(ApiResponse.error('Posted param "revisionId" is outdated.', 'conflict', returnLatestRevision));
     }
 
     const options = { isSyncRevisionToHackmd };

+ 5 - 0
yarn.lock

@@ -7142,6 +7142,11 @@ detect-indent@^6.0.0:
   resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd"
   integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA==
 
+detect-indent@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-7.0.0.tgz#cab58e6ab1129c669e2101181a6c677917d43577"
+  integrity sha512-/6kJlmVv6RDFPqaHC/ZDcU8bblYcoph2dUQ3kB47QqhkUEqXe3VZPELK9BaEMrC73qu+wn0AQ7iSteceN+yuMw==
+
 detect-libc@^1.0.2:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"