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

Merge pull request #2486 from weseek/imprv/add-suspence-for-taglabel

Imprv/add suspence for taglabel
itizawa 5 лет назад
Родитель
Сommit
163ac9ae9e

+ 66 - 0
src/client/js/components/Page/RenderTagLabels.jsx

@@ -0,0 +1,66 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
+import { withUnstatedContainers } from '../UnstatedUtils';
+import PageContainer from '../../services/PageContainer';
+
+function RenderTagLabels(props) {
+  const { t, tags, pageContainer } = props;
+  const { pageId } = pageContainer;
+
+  function openEditorHandler() {
+    if (props.openEditorModal == null) {
+      return;
+    }
+    props.openEditorModal();
+  }
+
+  // activate suspense
+  if (tags == null) {
+    throw new Promise(() => {});
+  }
+
+  if (tags.length === 0) {
+    return (
+      <a className="btn btn-link btn-edit-tags no-tags p-0 text-muted" onClick={openEditorHandler}>
+        { t('Add tags for this page') } <i className="manage-tags ml-2 icon-plus"></i>
+      </a>
+    );
+  }
+
+  return (
+    <>
+      {tags.map((tag) => {
+        return (
+          <span key={`${pageId}_${tag}`} className="text-muted">
+            <i className="tag-icon icon-tag mr-1"></i>
+            <a className="tag-name mr-2" href={`/_search?q=tag:${tag}`} key={`${pageId}_${tag}_link`}>{tag}</a>
+          </span>
+        );
+      })}
+      <a className="btn btn-link btn-edit-tags p-0 text-muted" onClick={openEditorHandler}>
+        <i className="manage-tags ml-2 icon-plus"></i> { t('Edit tags for this page') }
+      </a>
+    </>
+  );
+
+}
+
+/**
+ * Wrapper component for using unstated
+ */
+const RenderTagLabelsWrapper = withUnstatedContainers(RenderTagLabels, [PageContainer]);
+
+
+RenderTagLabels.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+
+  tags: PropTypes.array,
+  openEditorModal: PropTypes.func,
+
+  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+
+};
+
+export default withTranslation()(RenderTagLabelsWrapper);

+ 62 - 0
src/client/js/components/Page/TagEditModal.jsx

@@ -0,0 +1,62 @@
+import React, { useState, useEffect } from 'react';
+import PropTypes from 'prop-types';
+
+import {
+  Button, Modal, ModalHeader, ModalBody, ModalFooter,
+} from 'reactstrap';
+
+import TagsInput from './TagsInput';
+
+function TagEditModal(props) {
+  const [tags, setTags] = useState([]);
+
+  function onTagsUpdatedByTagsInput(tags) {
+    setTags(tags);
+  }
+
+  useEffect(() => {
+    setTags(props.tags);
+  }, [props.tags]);
+
+  function closeModalHandler() {
+    if (props.onClose == null) {
+      return;
+    }
+    props.onClose();
+  }
+
+  function handleSubmit() {
+    if (props.onTagsUpdated == null) {
+      return;
+    }
+
+    props.onTagsUpdated(tags);
+    closeModalHandler();
+  }
+
+  return (
+    <Modal isOpen={props.isOpen} toggle={closeModalHandler} id="edit-tag-modal">
+      <ModalHeader tag="h4" toggle={closeModalHandler} className="bg-primary text-light">
+          Edit Tags
+      </ModalHeader>
+      <ModalBody>
+        <TagsInput tags={tags} onTagsUpdated={onTagsUpdatedByTagsInput} />
+      </ModalBody>
+      <ModalFooter>
+        <Button color="primary" onClick={handleSubmit}>
+            Done
+        </Button>
+      </ModalFooter>
+    </Modal>
+  );
+
+}
+
+TagEditModal.propTypes = {
+  tags: PropTypes.array,
+  isOpen: PropTypes.bool.isRequired,
+  onClose: PropTypes.func,
+  onTagsUpdated: PropTypes.func,
+};
+
+export default TagEditModal;

+ 0 - 71
src/client/js/components/Page/TagEditor.jsx

@@ -1,71 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import {
-  Button, Modal, ModalHeader, ModalBody, ModalFooter,
-} from 'reactstrap';
-
-import AppContainer from '../../services/AppContainer';
-
-import TagsInput from './TagsInput';
-
-export default class TagEditor extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      tags: [],
-      isOpenModal: false,
-    };
-
-    this.show = this.show.bind(this);
-    this.onTagsUpdatedByTagsInput = this.onTagsUpdatedByTagsInput.bind(this);
-    this.closeModalHandler = this.closeModalHandler.bind(this);
-    this.handleSubmit = this.handleSubmit.bind(this);
-  }
-
-  show(tags) {
-    this.setState({ tags, isOpenModal: true });
-  }
-
-  onTagsUpdatedByTagsInput(tags) {
-    this.setState({ tags });
-  }
-
-  closeModalHandler() {
-    this.setState({ isOpenModal: false });
-  }
-
-  async handleSubmit() {
-    this.props.onTagsUpdated(this.state.tags);
-
-    // close modal
-    this.setState({ isOpenModal: false });
-  }
-
-  render() {
-    return (
-      <Modal isOpen={this.state.isOpenModal} toggle={this.closeModalHandler} id="edit-tag-modal">
-        <ModalHeader tag="h4" toggle={this.closeModalHandler} className="bg-primary text-light">
-          Edit Tags
-        </ModalHeader>
-        <ModalBody>
-          <TagsInput tags={this.state.tags} onTagsUpdated={this.onTagsUpdatedByTagsInput} />
-        </ModalBody>
-        <ModalFooter>
-          <Button color="primary" onClick={this.handleSubmit}>
-            Done
-          </Button>
-        </ModalFooter>
-      </Modal>
-    );
-  }
-
-}
-
-TagEditor.propTypes = {
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-
-  onTagsUpdated: PropTypes.func.isRequired,
-};

+ 62 - 92
src/client/js/components/Page/TagLabels.jsx

@@ -1,16 +1,16 @@
-import React from 'react';
+import React, { Suspense } from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
-import * as toastr from 'toastr';
+import { toastSuccess, toastError } from '../../util/apiNotification';
 
 import { withUnstatedContainers } from '../UnstatedUtils';
 import AppContainer from '../../services/AppContainer';
-import NavigationContainer from '../../services/NavigationContainer';
 import PageContainer from '../../services/PageContainer';
 import EditorContainer from '../../services/EditorContainer';
 
-import TagEditor from './TagEditor';
+import RenderTagLabels from './RenderTagLabels';
+import TagEditModal from './TagEditModal';
 
 class TagLabels extends React.Component {
 
@@ -18,118 +18,83 @@ class TagLabels extends React.Component {
     super(props);
 
     this.state = {
-      showTagEditor: false,
+      isTagEditModalShown: false,
     };
 
-    this.showEditor = this.showEditor.bind(this);
+    this.openEditorModal = this.openEditorModal.bind(this);
+    this.closeEditorModal = this.closeEditorModal.bind(this);
     this.tagsUpdatedHandler = this.tagsUpdatedHandler.bind(this);
   }
 
   /**
    * @return tags data
-   *   1. pageContainer.state.tags if editorMode is null
-   *   2. editorContainer.state.tags if editorMode is not null
+   *   1. pageContainer.state.tags if isEditorMode is false
+   *   2. editorContainer.state.tags if isEditorMode is true
    */
   getEditTargetData() {
-    const { editorMode } = this.props.navigationContainer.state;
-    return (editorMode == null)
-      ? this.props.pageContainer.state.tags
-      : this.props.editorContainer.state.tags;
+    const { isEditorMode } = this.props;
+    return (isEditorMode) ? this.props.editorContainer.state.tags : this.props.pageContainer.state.tags;
   }
 
-  showEditor() {
-    this.tagEditor.show(this.getEditTargetData());
+  openEditorModal() {
+    this.setState({ isTagEditModalShown: true });
+  }
+
+  closeEditorModal() {
+    this.setState({ isTagEditModalShown: false });
   }
 
   async tagsUpdatedHandler(tags) {
-    const { appContainer, navigationContainer, editorContainer } = this.props;
-    const { editorMode } = navigationContainer.state;
+    const { appContainer, editorContainer, isEditorMode } = this.props;
 
-    // post api request and update tags
-    if (editorMode == null) {
-      const { pageContainer } = this.props;
-
-      try {
-        const { pageId } = pageContainer.state;
-        await appContainer.apiPost('/tags.update', { pageId, tags });
-
-        // update pageContainer.state
-        pageContainer.setState({ tags });
-        editorContainer.setState({ tags });
-
-        this.apiSuccessHandler();
-      }
-      catch (err) {
-        this.apiErrorHandler(err);
-        return;
-      }
-    }
     // only update tags in editorContainer
-    else {
-      editorContainer.setState({ tags });
+    if (isEditorMode) {
+      return editorContainer.setState({ tags });
     }
-  }
 
-  apiSuccessHandler() {
-    toastr.success(undefined, 'updated tags successfully', {
-      closeButton: true,
-      progressBar: true,
-      newestOnTop: false,
-      showDuration: '100',
-      hideDuration: '100',
-      timeOut: '1200',
-      extendedTimeOut: '150',
-    });
-  }
+    // post api request and update tags
+    const { pageContainer } = this.props;
+
+    try {
+      const { pageId } = pageContainer.state;
+      await appContainer.apiPost('/tags.update', { pageId, tags });
+
+      // update pageContainer.state
+      pageContainer.setState({ tags });
+      editorContainer.setState({ tags });
 
-  apiErrorHandler(err) {
-    toastr.error(err.message, 'Error occured', {
-      closeButton: true,
-      progressBar: true,
-      newestOnTop: false,
-      showDuration: '100',
-      hideDuration: '100',
-      timeOut: '3000',
-    });
+      toastSuccess('updated tags successfully');
+    }
+    catch (err) {
+      toastError(err, 'fail to update tags');
+    }
   }
 
-  render() {
-    const { t } = this.props;
-    const { pageId } = this.props.pageContainer.state;
 
+  render() {
     const tags = this.getEditTargetData();
 
-    const tagElements = tags.map((tag) => {
-      return (
-        <span key={`${pageId}_${tag}`} className="text-muted">
-          <i className="tag-icon icon-tag mr-1"></i>
-          <a className="tag-name mr-2" href={`/_search?q=tag:${tag}`} key={`${pageId}_${tag}_link`}>{tag}</a>
-        </span>
-      );
-    });
-
     return (
-      <div className="tag-labels">
-        {tags.length === 0 && (
-          <a className="btn btn-link btn-edit-tags no-tags p-0 text-muted" onClick={this.showEditor}>
-            { t('Add tags for this page') } <i className="manage-tags ml-2 icon-plus"></i>
-          </a>
-        )}
-        {tagElements}
-        {tags.length > 0 && (
-          <a className="btn btn-link btn-edit-tags p-0 text-muted" onClick={this.showEditor}>
-            <i className="manage-tags ml-2 icon-plus"></i> { t('Edit tags for this page') }
-          </a>
-        )}
-
-        <TagEditor
-          ref={(c) => { this.tagEditor = c }}
+      <React.Fragment>
+
+        <div className="tag-labels">
+          <Suspense fallback={<p>...</p>}>
+            <RenderTagLabels
+              tags={tags}
+              openEditorModal={this.openEditorModal}
+            />
+          </Suspense>
+        </div>
+
+        <TagEditModal
+          tags={tags}
+          isOpen={this.state.isTagEditModalShown}
+          onClose={this.closeEditorModal}
           appContainer={this.props.appContainer}
-          show={this.state.showTagEditor}
           onTagsUpdated={this.tagsUpdatedHandler}
-        >
-        </TagEditor>
-      </div>
+        />
+
+      </React.Fragment>
     );
   }
 
@@ -138,15 +103,20 @@ class TagLabels extends React.Component {
 /**
  * Wrapper component for using unstated
  */
-const TagLabelsWrapper = withUnstatedContainers(TagLabels, [AppContainer, NavigationContainer, PageContainer, EditorContainer]);
-
+const TagLabelsWrapper = withUnstatedContainers(TagLabels, [AppContainer, PageContainer, EditorContainer]);
 
 TagLabels.propTypes = {
   t: PropTypes.func.isRequired, // i18next
+
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  navigationContainer: PropTypes.instanceOf(NavigationContainer).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
   editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
+
+  isEditorMode: PropTypes.bool,
+};
+
+TagLabels.defaultProps = {
+  isEditorMode: false,
 };
 
 export default withTranslation()(TagLabelsWrapper);

+ 1 - 1
src/client/js/components/PageEditor/PagePathNavForEditor.jsx

@@ -28,7 +28,7 @@ const PagePathNavForEditor = (props) => {
           pagePath={path}
         />
       </span>
-      <TagLabels />
+      <TagLabels isEditorMode />
     </div>
   );
 };

+ 1 - 1
src/client/js/services/EditorContainer.js

@@ -24,7 +24,7 @@ export default class EditorContainer extends Container {
     }
 
     this.state = {
-      tags: [],
+      tags: null,
 
       isSlackEnabled: false,
       slackChannels: mainContent.getAttribute('data-slack-channels') || '',

+ 1 - 1
src/client/js/services/PageContainer.js

@@ -59,7 +59,7 @@ export default class PageContainer extends Container {
       isDeleted:  JSON.parse(mainContent.getAttribute('data-page-is-deleted')),
       isDeletable:  JSON.parse(mainContent.getAttribute('data-page-is-deletable')),
       isAbleToDeleteCompletely:  JSON.parse(mainContent.getAttribute('data-page-is-able-to-delete-completely')),
-      tags: [],
+      tags: null,
       hasChildren: JSON.parse(mainContent.getAttribute('data-page-has-children')),
       templateTagData: mainContent.getAttribute('data-template-tags') || null,