Procházet zdrojové kódy

Merge pull request #4490 from weseek/feat/77877-79006-create-modal

Feat/77877 79006 create modal
Yuki Takei před 4 roky
rodič
revize
83de5914b1

+ 1 - 0
packages/app/package.json

@@ -85,6 +85,7 @@
     "date-fns": "^2.23.0",
     "detect-indent": "^6.0.0",
     "diff": "^5.0.0",
+    "diff_match_patch": "^0.1.1",
     "elasticsearch": "^16.0.0",
     "entities": "^2.0.0",
     "esa-nodejs": "^0.0.7",

+ 4 - 0
packages/app/resource/locales/en_US/translation.json

@@ -460,6 +460,10 @@
     "enable_textlint": "Enable Textlint",
     "dont_ask_again": "Don't ask again"
   },
+  "modal_resolve_conflict": {
+    "title": "Resolve Conflict",
+    "resolve_and_save" : "Resolve and save"
+  },
   "link_edit": {
     "edit_link": "Edit Link",
     "set_link_and_label": "Set link and label",

+ 4 - 0
packages/app/resource/locales/ja_JP/translation.json

@@ -460,6 +460,10 @@
     "enable_textlint": "Textlintを有効にする",
     "dont_ask_again": "常に許可する"
   },
+  "modal_resolve_conflict": {
+    "title": "衝突を解消する",
+    "resolve_and_save" : "解消し保存する"
+  },
   "link_edit": {
     "edit_link": "リンク編集",
     "set_link_and_label": "リンク情報",

+ 4 - 0
packages/app/resource/locales/zh_CN/translation.json

@@ -438,6 +438,10 @@
     "enable_textlint": "启用Textlint",
     "dont_ask_again": "不要再问"
   },
+  "modal_resolve_conflict": {
+    "title": "解决冲突",
+    "resolve_and_save" : "解决冲突并保存"
+  },
   "link_edit": {
     "edit_link": "Edit Link",
     "set_link_and_label": "Set link and label",

+ 91 - 0
packages/app/src/components/PageEditor/ConflictDiffModal.tsx

@@ -0,0 +1,91 @@
+import React, { useState, useEffect, FC } from 'react';
+import PropTypes from 'prop-types';
+import {
+  Modal, ModalHeader, ModalBody, ModalFooter,
+} from 'reactstrap';
+import CodeMirror from 'codemirror/lib/codemirror';
+import { useTranslation } from 'react-i18next';
+
+require('codemirror/lib/codemirror.css');
+require('codemirror/addon/merge/merge');
+require('codemirror/addon/merge/merge.css');
+const DMP = require('diff_match_patch');
+
+Object.keys(DMP).forEach((key) => { window[key] = DMP[key] });
+
+type ConflictDiffModalProps = {
+  isOpen: boolean | null;
+  onCancel: (() => void) | null;
+  onResolveConflict: (() => void) | null;
+};
+
+export const ConflictDiffModal: FC<ConflictDiffModalProps> = (props) => {
+  const [val, setVal] = useState('value 1');
+  const [orig, setOrig] = useState('value 2');
+  const [codeMirrorRef, setCodeMirrorRef] = useState<HTMLDivElement | null>(null);
+  const { t } = useTranslation('');
+
+  useEffect(() => {
+    if (codeMirrorRef) {
+      CodeMirror.MergeView(codeMirrorRef, {
+        value: val,
+        origLeft: orig,
+        origRight: null,
+        connect: 'align',
+        lineNumbers: true,
+        collapseIdentical: true,
+        highlightDifferences: true,
+        allowEditingOriginals: false,
+      });
+    }
+  }, [codeMirrorRef, orig, val]);
+
+  const onCancel = () => {
+    if (props.onCancel != null) {
+      props.onCancel();
+    }
+  };
+
+  const onResolveConflict = () => {
+    if (props.onResolveConflict != null) {
+      props.onResolveConflict();
+    }
+  };
+
+  return (
+    <Modal isOpen={props.isOpen || false} toggle={onCancel} className="modal-gfm-cheatsheet">
+      <ModalHeader tag="h4" toggle={onCancel} className="bg-primary text-light">
+        <i className="icon-fw icon-exclamation" />{t('modal_resolve_conflict.title')}
+      </ModalHeader>
+      <ModalBody>
+        <div ref={(el) => { setCodeMirrorRef(el) }}></div>
+      </ModalBody>
+      <ModalFooter>
+        <button
+          type="button"
+          className="btn btn-outline-secondary"
+          onClick={onCancel}
+        >
+          {t('Cancel')}
+        </button>
+        <button
+          type="button"
+          className="btn btn-outline-primary ml-3"
+          onClick={onResolveConflict}
+        >
+          {t('modal_resolve_conflict.resolve_and_save')}
+        </button>
+      </ModalFooter>
+    </Modal>
+  );
+};
+
+ConflictDiffModal.propTypes = {
+  isOpen: PropTypes.bool,
+  onCancel: PropTypes.func,
+  onResolveConflict: PropTypes.func,
+};
+
+ConflictDiffModal.defaultProps = {
+  isOpen: false,
+};

+ 91 - 82
packages/app/src/components/PageEditor/Editor.jsx

@@ -18,6 +18,7 @@ import CodeMirrorEditor from './CodeMirrorEditor';
 import TextAreaEditor from './TextAreaEditor';
 
 import pasteHelper from './PasteHelper';
+import { ConflictDiffModal } from './ConflictDiffModal';
 
 class Editor extends AbstractEditor {
 
@@ -29,6 +30,7 @@ class Editor extends AbstractEditor {
       dropzoneActive: false,
       isUploading: false,
       isCheatsheetModalShown: false,
+      isConflictDiffModalOpen: false,
     };
 
     this.getEditorSubstance = this.getEditorSubstance.bind(this);
@@ -286,88 +288,95 @@ class Editor extends AbstractEditor {
     const isMobile = this.props.isMobile;
 
     return (
-      <div style={flexContainer} className="editor-container">
-        <Dropzone
-          ref={(c) => { this.dropzone = c }}
-          accept={this.getAcceptableType()}
-          noClick
-          noKeyboard
-          multiple={false}
-          onDragLeave={this.dragLeaveHandler}
-          onDrop={this.dropHandler}
-        >
-          {({
-            getRootProps,
-            getInputProps,
-            isDragAccept,
-            isDragReject,
-          }) => {
-            return (
-              <div className={this.getDropzoneClassName(isDragAccept, isDragReject)} {...getRootProps()}>
-                { this.state.dropzoneActive && this.renderDropzoneOverlay() }
-
-                { this.state.isComponentDidMount && this.renderNavbar() }
-
-                {/* for PC */}
-                { !isMobile && (
-                  <Subscribe to={[EditorContainer]}>
-                    { editorContainer => (
-                      // eslint-disable-next-line arrow-body-style
-                      <CodeMirrorEditor
-                        ref={(c) => { this.cmEditor = c }}
-                        indentSize={editorContainer.state.indentSize}
-                        editorOptions={editorContainer.state.editorOptions}
-                        isTextlintEnabled={editorContainer.state.isTextlintEnabled}
-                        textlintRules={editorContainer.state.textlintRules}
-                        onInitializeTextlint={editorContainer.retrieveEditorSettings}
-                        onPasteFiles={this.pasteFilesHandler}
-                        onDragEnter={this.dragEnterHandler}
-                        onMarkdownHelpButtonClicked={this.showMarkdownHelp}
-                        onAddAttachmentButtonClicked={this.addAttachmentHandler}
-                        {...this.props}
-                      />
-                    )}
-                  </Subscribe>
-                )}
-
-                {/* for mobile */}
-                { isMobile && (
-                  <TextAreaEditor
-                    ref={(c) => { this.taEditor = c }}
-                    onPasteFiles={this.pasteFilesHandler}
-                    onDragEnter={this.dragEnterHandler}
-                    {...this.props}
-                  />
-                )}
-
-                <input {...getInputProps()} />
-              </div>
-            );
-          }}
-        </Dropzone>
-
-        { this.props.isUploadable
-          && (
-            <button
-              type="button"
-              className="btn btn-outline-secondary btn-block btn-open-dropzone"
-              onClick={this.addAttachmentHandler}
-            >
-              <i className="icon-paper-clip" aria-hidden="true"></i>&nbsp;
-              Attach files
-              <span className="d-none d-sm-inline">
-              &nbsp;by dragging &amp; dropping,&nbsp;
-                <span className="btn-link">selecting them</span>,&nbsp;
-                or pasting from the clipboard.
-              </span>
-
-            </button>
-          )
-        }
-
-        { this.renderCheatsheetModal() }
-
-      </div>
+      <>
+        <div style={flexContainer} className="editor-container">
+          <Dropzone
+            ref={(c) => { this.dropzone = c }}
+            accept={this.getAcceptableType()}
+            noClick
+            noKeyboard
+            multiple={false}
+            onDragLeave={this.dragLeaveHandler}
+            onDrop={this.dropHandler}
+          >
+            {({
+              getRootProps,
+              getInputProps,
+              isDragAccept,
+              isDragReject,
+            }) => {
+              return (
+                <div className={this.getDropzoneClassName(isDragAccept, isDragReject)} {...getRootProps()}>
+                  { this.state.dropzoneActive && this.renderDropzoneOverlay() }
+
+                  { this.state.isComponentDidMount && this.renderNavbar() }
+
+                  {/* for PC */}
+                  { !isMobile && (
+                    <Subscribe to={[EditorContainer]}>
+                      { editorContainer => (
+                        // eslint-disable-next-line arrow-body-style
+                        <CodeMirrorEditor
+                          ref={(c) => { this.cmEditor = c }}
+                          indentSize={editorContainer.state.indentSize}
+                          editorOptions={editorContainer.state.editorOptions}
+                          isTextlintEnabled={editorContainer.state.isTextlintEnabled}
+                          textlintRules={editorContainer.state.textlintRules}
+                          onInitializeTextlint={editorContainer.retrieveEditorSettings}
+                          onPasteFiles={this.pasteFilesHandler}
+                          onDragEnter={this.dragEnterHandler}
+                          onMarkdownHelpButtonClicked={this.showMarkdownHelp}
+                          onAddAttachmentButtonClicked={this.addAttachmentHandler}
+                          {...this.props}
+                        />
+                      )}
+                    </Subscribe>
+                  )}
+
+                  {/* for mobile */}
+                  { isMobile && (
+                    <TextAreaEditor
+                      ref={(c) => { this.taEditor = c }}
+                      onPasteFiles={this.pasteFilesHandler}
+                      onDragEnter={this.dragEnterHandler}
+                      {...this.props}
+                    />
+                  )}
+
+                  <input {...getInputProps()} />
+                </div>
+              );
+            }}
+          </Dropzone>
+
+          { this.props.isUploadable
+            && (
+              <button
+                type="button"
+                className="btn btn-outline-secondary btn-block btn-open-dropzone"
+                onClick={this.addAttachmentHandler}
+              >
+                <i className="icon-paper-clip" aria-hidden="true"></i>&nbsp;
+                Attach files
+                <span className="d-none d-sm-inline">
+                &nbsp;by dragging &amp; dropping,&nbsp;
+                  <span className="btn-link">selecting them</span>,&nbsp;
+                  or pasting from the clipboard.
+                </span>
+
+              </button>
+            )
+          }
+
+          { this.renderCheatsheetModal() }
+
+        </div>
+        <ConflictDiffModal
+          isOpen={this.state.isConflictDiffModalOpen}
+          onCancel={() => this.setState({ isConflictDiffModalOpen: false })}
+          onResolveConflict={() => {}}
+        />
+      </>
     );
   }
 

+ 5 - 0
yarn.lock

@@ -7310,6 +7310,11 @@ diff@^5.0.0:
   resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
   integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
 
+diff_match_patch@^0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/diff_match_patch/-/diff_match_patch-0.1.1.tgz#d3f14d5b76fb4b5a9cf44706261dadb5bd97edbc"
+  integrity sha1-0/FNW3b7S1qc9EcGJh2ttb2X7bw=
+
 diffie-hellman@^5.0.0:
   version "5.0.2"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"