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

refactor SavePageControls, CommentEditor

Yuki Takei 6 лет назад
Родитель
Сommit
ea0ae272e9

+ 2 - 2
src/client/js/app.js

@@ -175,7 +175,7 @@ const saveWithShortcut = function(markdown) {
   let { revisionId } = pageContainer.state;
 
   // get options
-  const options = pageContainer.getCurrentOptionsToSave();
+  const options = editorContainer.getCurrentOptionsToSave();
   options.socketClientId = websocketContainer.getCocketClientId();
   options.pageTags = editorContainer.state.tags;
 
@@ -214,7 +214,7 @@ const saveWithSubmitButton = function(submitOpts) {
   const { pageId, path } = pageContainer.state;
   let { revisionId } = pageContainer.state;
   // get options
-  const options = pageContainer.getCurrentOptionsToSave();
+  const options = editorContainer.getCurrentOptionsToSave();
   options.socketClientId = websocketContainer.getCocketClientId();
   options.pageTags = editorContainer.state.tags;
 

+ 14 - 11
src/client/js/components/PageComment/CommentEditor.jsx

@@ -9,6 +9,7 @@ import * as toastr from 'toastr';
 import AppContainer from '../../services/AppContainer';
 import PageContainer from '../../services/PageContainer';
 import CommentContainer from '../../services/CommentContainer';
+import EditorContainer from '../../services/EditorContainer';
 import GrowiRenderer from '../../util/GrowiRenderer';
 
 import { createSubscribedElement } from '../UnstatedUtils';
@@ -44,8 +45,6 @@ class CommentEditor extends React.Component {
       isUploadableFile,
       errorMessage: undefined,
       hasSlackConfig: config.hasSlackConfig,
-      isSlackEnabled: false,
-      slackChannels: this.props.pageContainer.state.slackChannels,
     };
 
     this.growiRenderer = new GrowiRenderer(window.crowi, this.props.crowiOriginRenderer, { mode: 'comment' });
@@ -87,12 +86,12 @@ class CommentEditor extends React.Component {
     this.renderHtml(this.state.comment);
   }
 
-  onSlackEnabledFlagChange(value) {
-    this.setState({ isSlackEnabled: value });
+  onSlackEnabledFlagChange(isSlackEnabled) {
+    this.props.commentContainer.setState({ isSlackEnabled });
   }
 
-  onSlackChannelsChange(value) {
-    this.setState({ slackChannels: value });
+  onSlackChannelsChange(slackChannels) {
+    this.props.commentContainer.setState({ slackChannels });
   }
 
   /**
@@ -103,12 +102,14 @@ class CommentEditor extends React.Component {
       event.preventDefault();
     }
 
+    const { commentContainer } = this.props;
+
     this.props.commentContainer.postComment(
       this.state.comment,
       this.state.isMarkdown,
       this.props.replyTo,
-      this.state.isSlackEnabled,
-      this.state.slackChannels,
+      commentContainer.state.isSlackEnabled,
+      commentContainer.state.slackChannels,
     )
       .then((res) => {
         this.setState({
@@ -117,7 +118,6 @@ class CommentEditor extends React.Component {
           html: '',
           key: 1,
           errorMessage: undefined,
-          isSlackEnabled: false,
         });
         // reset value
         this.editor.setValue('');
@@ -205,7 +205,7 @@ class CommentEditor extends React.Component {
   }
 
   render() {
-    const { appContainer } = this.props;
+    const { appContainer, commentContainer } = this.props;
     const username = appContainer.me;
     const user = appContainer.findUser(username);
     const commentPreview = this.state.isMarkdown ? this.getCommentHtml() : null;
@@ -290,6 +290,8 @@ class CommentEditor extends React.Component {
                     && (
                     <div className="form-inline align-self-center mr-md-2">
                       <SlackNotification
+                        isSlackEnabled={commentContainer.state.isSlackEnabled}
+                        slackChannels={commentContainer.state.slackChannels}
                         onEnabledFlagChange={this.onSlackEnabledFlagChange}
                         onChannelChange={this.onSlackChannelsChange}
                       />
@@ -320,12 +322,13 @@ class CommentEditor extends React.Component {
  * Wrapper component for using unstated
  */
 const CommentEditorWrapper = (props) => {
-  return createSubscribedElement(CommentEditor, props, [AppContainer, PageContainer, CommentContainer]);
+  return createSubscribedElement(CommentEditor, props, [AppContainer, PageContainer, EditorContainer, CommentContainer]);
 };
 
 CommentEditor.propTypes = {
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+  editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
   commentContainer: PropTypes.instanceOf(CommentContainer).isRequired,
 
   crowiOriginRenderer: PropTypes.object.isRequired,

+ 29 - 6
src/client/js/components/SavePageControls.jsx

@@ -9,13 +9,14 @@ import MenuItem from 'react-bootstrap/es/MenuItem';
 
 import PageContainer from '../services/PageContainer';
 import AppContainer from '../services/AppContainer';
+import EditorContainer from '../services/EditorContainer';
 
 import { createSubscribedElement } from './UnstatedUtils';
 import SlackNotification from './SlackNotification';
 import GrantSelector from './SavePageControls/GrantSelector';
 
 
-class SavePageControls extends React.PureComponent {
+class SavePageControls extends React.Component {
 
   constructor(props) {
     super(props);
@@ -24,11 +25,24 @@ class SavePageControls extends React.PureComponent {
     this.hasSlackConfig = config.hasSlackConfig;
     this.isAclEnabled = config.isAclEnabled;
 
+    this.slackEnabledFlagChangedHandler = this.slackEnabledFlagChangedHandler.bind(this);
+    this.slackChannelsChangedHandler = this.slackChannelsChangedHandler.bind(this);
+    this.updateGrantHandler = this.updateGrantHandler.bind(this);
+
     this.submit = this.submit.bind(this);
     this.submitAndOverwriteScopesOfDescendants = this.submitAndOverwriteScopesOfDescendants.bind(this);
   }
 
-  componentWillMount() {
+  slackEnabledFlagChangedHandler(isSlackEnabled) {
+    this.props.editorContainer.setState({ isSlackEnabled });
+  }
+
+  slackChannelsChangedHandler(slackChannels) {
+    this.props.editorContainer.setState({ slackChannels });
+  }
+
+  updateGrantHandler(data) {
+    this.props.editorContainer.setState(data);
   }
 
   submit() {
@@ -41,7 +55,7 @@ class SavePageControls extends React.PureComponent {
   }
 
   render() {
-    const { t } = this.props;
+    const { t, editorContainer } = this.props;
     const labelSubmitButton = this.props.pageContainer.state.pageId == null ? t('Create') : t('Update');
     const labelOverwriteScopes = t('page_edit.overwrite_scopes', { operation: labelSubmitButton });
 
@@ -51,7 +65,10 @@ class SavePageControls extends React.PureComponent {
           && (
           <div className="mr-2">
             <SlackNotification
-              isSlackEnabled={false}
+              isSlackEnabled={editorContainer.state.isSlackEnabled}
+              slackChannels={editorContainer.state.slackChannels}
+              onEnabledFlagChange={this.slackEnabledFlagChangedHandler}
+              onChannelChange={this.slackChannelsChangedHandler}
             />
           </div>
           )
@@ -60,7 +77,12 @@ class SavePageControls extends React.PureComponent {
         {this.isAclEnabled
           && (
           <div className="mr-2">
-            <GrantSelector />
+            <GrantSelector
+              grant={editorContainer.state.grant}
+              grantGroupId={editorContainer.state.grantGroupId}
+              grantGroupName={editorContainer.state.grantGroupName}
+              onUpdateGrant={this.updateGrantHandler}
+            />
           </div>
           )
         }
@@ -89,7 +111,7 @@ class SavePageControls extends React.PureComponent {
  * Wrapper component for using unstated
  */
 const SavePageControlsWrapper = (props) => {
-  return createSubscribedElement(SavePageControls, props, [AppContainer, PageContainer]);
+  return createSubscribedElement(SavePageControls, props, [AppContainer, PageContainer, EditorContainer]);
 };
 
 SavePageControls.propTypes = {
@@ -97,6 +119,7 @@ SavePageControls.propTypes = {
 
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
   pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+  editorContainer: PropTypes.instanceOf(EditorContainer).isRequired,
 
   onSubmit: PropTypes.func.isRequired,
 };

+ 22 - 17
src/client/js/components/SavePageControls/GrantSelector.jsx

@@ -10,7 +10,6 @@ import ListGroupItem from 'react-bootstrap/es/ListGroupItem';
 import Modal from 'react-bootstrap/es/Modal';
 
 import AppContainer from '../../services/AppContainer';
-import PageContainer from '../../services/PageContainer';
 
 import { createSubscribedElement } from '../UnstatedUtils';
 
@@ -47,17 +46,16 @@ class GrantSelector extends React.Component {
       }, // appeared only one of these 'grant: 5'
     ];
 
-    const { pageContainer } = this.props;
-
     this.state = {
       userRelatedGroups: [],
       isSelectGroupModalShown: false,
+      grant: this.props.grant,
       grantGroup: null,
     };
-    if (pageContainer.state.grantGroupId != null) {
+    if (this.props.grantGroupId != null) {
       this.state.grantGroup = {
-        _id: pageContainer.state.grantGroupId,
-        name: pageContainer.state.grantGroupName,
+        _id: this.props.grantGroupId,
+        name: this.props.grantGroupName,
       };
     }
 
@@ -126,8 +124,6 @@ class GrantSelector extends React.Component {
    * change event handler for grant selector
    */
   changeGrantHandler() {
-    const { pageContainer } = this.props;
-
     const grant = +this.grantSelectorInputEl.value;
 
     // select group
@@ -140,15 +136,19 @@ class GrantSelector extends React.Component {
       return;
     }
 
-    this.setState({ grantGroup: null });
-    pageContainer.setState({ grant, grantGroupId: null, grantGroupName: null });
+    this.setState({ grant, grantGroup: null });
+
+    if (this.props.onUpdateGrant != null) {
+      this.props.onUpdateGrant({ grant, grantGroupId: null, grantGroupName: null });
+    }
   }
 
   groupListItemClickHandler(grantGroup) {
-    const { pageContainer } = this.props;
+    this.setState({ grant: 5, grantGroup });
 
-    this.setState({ grantGroup });
-    pageContainer.setState({ grant: 5, grantGroupId: grantGroup._id, grantGroupName: grantGroup.name });
+    if (this.props.onUpdateGrant != null) {
+      this.props.onUpdateGrant({ grant: 5, grantGroupId: grantGroup._id, grantGroupName: grantGroup.name });
+    }
 
     // hide modal
     this.hideSelectGroupModal();
@@ -160,10 +160,10 @@ class GrantSelector extends React.Component {
    * @memberof GrantSelector
    */
   renderGrantSelector() {
-    const { t, pageContainer } = this.props;
+    const { t } = this.props;
 
     let index = 0;
-    let selectedValue = pageContainer.state.grant;
+    let selectedValue = this.state.grant;
     const grantElems = this.availableGrants.map((opt) => {
       const dataContent = `<i class="icon icon-fw ${opt.iconClass} ${opt.styleClass}"></i> <span class="${opt.styleClass}">${t(opt.label)}</span>`;
       return <option key={index++} value={opt.grant} data-content={dataContent}>{t(opt.label)}</option>;
@@ -287,13 +287,18 @@ class GrantSelector extends React.Component {
  * Wrapper component for using unstated
  */
 const GrantSelectorWrapper = (props) => {
-  return createSubscribedElement(GrantSelector, props, [AppContainer, PageContainer]);
+  return createSubscribedElement(GrantSelector, props, [AppContainer]);
 };
 
 GrantSelector.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+
+  grant: PropTypes.number.isRequired,
+  grantGroupId: PropTypes.string,
+  grantGroupName: PropTypes.string,
+
+  onUpdateGrant: PropTypes.func,
 };
 
 export default withTranslation()(GrantSelectorWrapper);

+ 40 - 38
src/client/js/components/SlackNotification.jsx

@@ -1,8 +1,5 @@
 import React from 'react';
-
-import { Subscribe } from 'unstated';
-
-import PageContainer from '../services/PageContainer';
+import PropTypes from 'prop-types';
 
 /**
  *
@@ -18,55 +15,60 @@ export default class SlackNotification extends React.Component {
   constructor(props) {
     super(props);
 
-    this.updateStateCheckbox = this.updateStateCheckbox.bind(this);
+    this.updateCheckboxHandler = this.updateCheckboxHandler.bind(this);
+    this.updateSlackChannelsHandler = this.updateSlackChannelsHandler.bind(this);
   }
 
-  updateStateCheckbox(event, pageContainer) {
+  updateCheckboxHandler(event) {
     const value = event.target.checked;
-    pageContainer.setState({ isSlackEnabled: value });
+    if (this.props.onEnabledFlagChange != null) {
+      this.props.onEnabledFlagChange(value);
+    }
   }
 
-  updateStateChannels(slackChannels, pageContainer) {
-    pageContainer.setState({ slackChannels });
+  updateSlackChannelsHandler(event) {
+    const value = event.target.value;
+    if (this.props.onChannelChange != null) {
+      this.props.onChannelChange(value);
+    }
   }
 
   render() {
     return (
-      <Subscribe to={[PageContainer]}>
-        { pageContainer => (
-          // eslint-disable-next-line arrow-body-style
-          <div className="input-group input-group-sm input-group-slack extended-setting">
-            <label className="input-group-addon">
-              <img id="slack-mark-white" alt="slack-mark" src="/images/icons/slack/mark-monochrome_white.svg" width="18" height="18" />
-              <img id="slack-mark-black" alt="slack-mark" src="/images/icons/slack/mark-monochrome_black.svg" width="18" height="18" />
+      <div className="input-group input-group-sm input-group-slack extended-setting">
+        <label className="input-group-addon">
+          <img id="slack-mark-white" alt="slack-mark" src="/images/icons/slack/mark-monochrome_white.svg" width="18" height="18" />
+          <img id="slack-mark-black" alt="slack-mark" src="/images/icons/slack/mark-monochrome_black.svg" width="18" height="18" />
 
-              <input
-                type="checkbox"
-                value="1"
-                checked={pageContainer.state.isSlackEnabled}
-                onChange={(e) => { this.updateStateCheckbox(e, pageContainer) }}
-              />
+          <input
+            type="checkbox"
+            value="1"
+            checked={this.props.isSlackEnabled}
+            onChange={this.updateCheckboxHandler}
+          />
 
-            </label>
-            <input
-              className="form-control"
-              type="text"
-              value={pageContainer.state.slackChannels}
-              placeholder="slack channel name"
-              data-toggle="popover"
-              title="Slack通知"
-              data-content="通知するにはチェックを入れてください。カンマ区切りで複数チャンネルに通知することができます。"
-              data-trigger="focus"
-              data-placement="top"
-              onChange={(e) => { this.updateStateChannels(e.target.value, pageContainer) }}
-            />
-          </div>
-        )}
-      </Subscribe>
+        </label>
+        <input
+          className="form-control"
+          type="text"
+          value={this.props.slackChannels}
+          placeholder="slack channel name"
+          data-toggle="popover"
+          title="Slack通知"
+          data-content="通知するにはチェックを入れてください。カンマ区切りで複数チャンネルに通知することができます。"
+          data-trigger="focus"
+          data-placement="top"
+          onChange={this.updateSlackChannelsHandler}
+        />
+      </div>
     );
   }
 
 }
 
 SlackNotification.propTypes = {
+  isSlackEnabled: PropTypes.bool.isRequired,
+  slackChannels: PropTypes.string.isRequired,
+  onEnabledFlagChange: PropTypes.func,
+  onChannelChange: PropTypes.func,
 };

+ 15 - 0
src/client/js/services/CommentContainer.js

@@ -1,5 +1,9 @@
 import { Container } from 'unstated';
 
+import loggerFactory from '@alias/logger';
+
+const logger = loggerFactory('growi:services:CommentContainer');
+
 /**
  *
  * @author Yuki Takei <yuki@weseek.co.jp>
@@ -14,8 +18,19 @@ export default class CommentContainer extends Container {
     this.appContainer = appContainer;
     this.appContainer.registerContainer(this);
 
+    const mainContent = document.querySelector('#content-main');
+
+    if (mainContent == null) {
+      logger.debug('#content-main element is not exists');
+      return;
+    }
+
     this.state = {
       comments: [],
+
+      // settings shared among all of CommentEditor
+      isSlackEnabled: false,
+      slackChannels: mainContent.getAttribute('data-slack-channels') || '',
     };
 
     this.retrieveComments = this.retrieveComments.bind(this);

+ 55 - 3
src/client/js/services/EditorContainer.js

@@ -1,5 +1,9 @@
 import { Container } from 'unstated';
 
+import loggerFactory from '@alias/logger';
+
+const logger = loggerFactory('growi:services:EditorContainer');
+
 /**
  * Service container related to options for Editor/Preview
  * @extends {Container} unstated Container
@@ -12,17 +16,51 @@ export default class EditorContainer extends Container {
     this.appContainer = appContainer;
     this.appContainer.registerContainer(this);
 
+    const mainContent = document.querySelector('#content-main');
+
+    if (mainContent == null) {
+      logger.debug('#content-main element is not exists');
+      return;
+    }
+
     this.state = {
       tags: [],
+
+      isSlackEnabled: false,
+      slackChannels: mainContent.getAttribute('data-slack-channels') || '',
+
+      grant: 1, // default: public
+      grantGroupId: null,
+      grantGroupName: null,
+
       editorOptions: {},
       previewOptions: {},
     };
 
-    this.initOptions('editorOptions', 'editorOptions', defaultEditorOptions);
-    this.initOptions('previewOptions', 'previewOptions', defaultPreviewOptions);
+    this.initStateGrant();
+
+    this.initEditorOptions('editorOptions', 'editorOptions', defaultEditorOptions);
+    this.initEditorOptions('previewOptions', 'previewOptions', defaultPreviewOptions);
   }
 
-  initOptions(stateKey, localStorageKey, defaultOptions) {
+  /**
+   * initialize state for page permission
+   */
+  initStateGrant() {
+    const elem = document.getElementById('save-page-controls');
+
+    if (elem) {
+      this.state.grant = +elem.dataset.grant;
+
+      const grantGroupId = elem.dataset.grantGroup;
+      if (grantGroupId != null && grantGroupId.length > 0) {
+        this.state.grantGroupId = grantGroupId;
+        this.state.grantGroupName = elem.dataset.grantGroupName;
+      }
+    }
+  }
+
+  initEditorOptions(stateKey, localStorageKey, defaultOptions) {
     // load from localStorage
     const optsStr = window.localStorage[localStorageKey];
 
@@ -60,4 +98,18 @@ export default class EditorContainer extends Container {
     }
   }
 
+  getCurrentOptionsToSave() {
+    const opt = {
+      isSlackEnabled: this.state.isSlackEnabled,
+      slackChannels: this.state.slackChannels,
+      grant: this.state.grant,
+    };
+
+    if (this.state.grantGroupId != null) {
+      opt.grantUserGroupId = this.state.grantGroupId;
+    }
+
+    return opt;
+  }
+
 }

+ 0 - 39
src/client/js/services/PageContainer.js

@@ -38,13 +38,6 @@ export default class PageContainer extends Container {
       tags: [],
       templateTagData: mainContent.getAttribute('data-template-tags') || '',
 
-      isSlackEnabled: false,
-      slackChannels: mainContent.getAttribute('data-slack-channels') || '',
-
-      grant: 1, // default: public
-      grantGroupId: null,
-      grantGroupName: null,
-
       // latest(on remote) information
       remoteRevisionId: revisionId,
       revisionIdHackmdSynced: mainContent.getAttribute('data-page-revision-id-hackmd-synced'),
@@ -55,7 +48,6 @@ export default class PageContainer extends Container {
     };
 
     this.initStateMarkdown();
-    this.initStateGrant();
     this.initDrafts();
 
     this.addWebSocketEventHandlers = this.addWebSocketEventHandlers.bind(this);
@@ -77,23 +69,6 @@ export default class PageContainer extends Container {
     this.state.markdown = markdown;
   }
 
-  /**
-   * initialize state for page permission
-   */
-  initStateGrant() {
-    const elem = document.getElementById('save-page-controls');
-
-    if (elem) {
-      this.state.grant = +elem.dataset.grant;
-
-      const grantGroupId = elem.dataset.grantGroup;
-      if (grantGroupId != null && grantGroupId.length > 0) {
-        this.state.grantGroupId = grantGroupId;
-        this.state.grantGroupName = elem.dataset.grantGroupName;
-      }
-    }
-  }
-
   /**
    * initialize state for drafts
    */
@@ -119,20 +94,6 @@ export default class PageContainer extends Container {
     }
   }
 
-  getCurrentOptionsToSave() {
-    const opt = {
-      isSlackEnabled: this.state.isSlackEnabled,
-      slackChannels: this.state.slackChannels,
-      grant: this.state.grant,
-    };
-
-    if (this.state.grantGroupId != null) {
-      opt.grantUserGroupId = this.state.grantGroupId;
-    }
-
-    return opt;
-  }
-
   setLatestRemotePageData(page, user) {
     this.setState({
       remoteRevisionId: page.revision._id,