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

+ 12 - 18
src/client/js/app.js

@@ -248,7 +248,7 @@ const saveWithSubmitButton = function(submitOpts) {
 
   let revisionId = pageRevisionId;
   // get options
-  const options = componentInstances.savePageControls.getCurrentOptionsToSave();
+  const options = pageContainer.getCurrentOptionsToSave();
   options.socketClientId = socketClientId;
   options.pageTags = pageTags;
 
@@ -397,25 +397,19 @@ if (likerListElem) {
 let savePageControls = null;
 const savePageControlsElem = document.getElementById('save-page-controls');
 if (savePageControlsElem) {
-  const grant = +savePageControlsElem.dataset.grant;
-  const grantGroupId = savePageControlsElem.dataset.grantGroup;
-  const grantGroupName = savePageControlsElem.dataset.grantGroupName;
   ReactDOM.render(
     <I18nextProvider i18n={i18n}>
-      <SavePageControls
-        crowi={crowi}
-        onSubmit={saveWithSubmitButton}
-        ref={(elem) => {
-            if (savePageControls == null) {
-              savePageControls = elem;
-            }
-          }}
-        pageId={pageId}
-        slackChannels={slackChannels}
-        grant={grant}
-        grantGroupId={grantGroupId}
-        grantGroupName={grantGroupName}
-      />
+      <Provider inject={[pageContainer]}>
+        <SavePageControls
+          crowi={crowi}
+          onSubmit={saveWithSubmitButton}
+          ref={(elem) => {
+              if (savePageControls == null) {
+                savePageControls = elem;
+              }
+            }}
+        />
+      </Provider>
     </I18nextProvider>,
     savePageControlsElem,
   );

+ 32 - 34
src/client/js/components/SavePageControls.jsx

@@ -1,11 +1,15 @@
+/* eslint-disable react/no-multi-comp */
 import React from 'react';
 import PropTypes from 'prop-types';
+import { Subscribe } from 'unstated';
 import { withTranslation } from 'react-i18next';
 
 import ButtonToolbar from 'react-bootstrap/es/ButtonToolbar';
 import SplitButton from 'react-bootstrap/es/SplitButton';
 import MenuItem from 'react-bootstrap/es/MenuItem';
 
+import PageContainer from '../services/PageContainer';
+
 import SlackNotification from './SlackNotification';
 import GrantSelector from './SavePageControls/GrantSelector';
 
@@ -14,15 +18,10 @@ class SavePageControls extends React.PureComponent {
   constructor(props) {
     super(props);
 
-    this.state = {
-      pageId: this.props.pageId,
-    };
-
     const config = this.props.crowi.getConfig();
     this.hasSlackConfig = config.hasSlackConfig;
     this.isAclEnabled = config.isAclEnabled;
 
-    this.getCurrentOptionsToSave = this.getCurrentOptionsToSave.bind(this);
     this.submit = this.submit.bind(this);
     this.submitAndOverwriteScopesOfDescendants = this.submitAndOverwriteScopesOfDescendants.bind(this);
   }
@@ -30,22 +29,6 @@ class SavePageControls extends React.PureComponent {
   componentWillMount() {
   }
 
-  getCurrentOptionsToSave() {
-    let currentOptions = this.grantSelector.getCurrentOptionsToSave();
-    if (this.hasSlackConfig) {
-      currentOptions = Object.assign(currentOptions, this.slackNotification.getCurrentOptionsToSave());
-    }
-    return currentOptions;
-  }
-
-  /**
-   * update pageId of state
-   * @param {string} pageId
-   */
-  setPageId(pageId) {
-    this.setState({ pageId });
-  }
-
   submit() {
     this.props.crowi.setIsDocSaved(true);
     this.props.onSubmit();
@@ -57,7 +40,7 @@ class SavePageControls extends React.PureComponent {
 
   render() {
     const { t } = this.props;
-    const labelSubmitButton = this.state.pageId == null ? t('Create') : t('Update');
+    const labelSubmitButton = this.props.pageContainer.state.pageId == null ? t('Create') : t('Update');
     const labelOverwriteScopes = t('page_edit.overwrite_scopes', { operation: labelSubmitButton });
 
     return (
@@ -68,7 +51,6 @@ class SavePageControls extends React.PureComponent {
             <SlackNotification
               ref={(c) => { this.slackNotification = c }}
               isSlackEnabled={false}
-              slackChannels={this.props.slackChannels}
             />
           </div>
           )
@@ -84,9 +66,6 @@ class SavePageControls extends React.PureComponent {
                     this.grantSelector = elem;
                   }
                 }}
-              grant={this.props.grant}
-              grantGroupId={this.props.grantGroupId}
-              grantGroupName={this.props.grantGroupName}
             />
           </div>
           )
@@ -112,17 +91,36 @@ class SavePageControls extends React.PureComponent {
 
 }
 
+
+/**
+ * Wrapper component for using unstated
+ */
+class SavePageControlsWrapper extends React.PureComponent {
+
+  render() {
+    return (
+      <Subscribe to={[PageContainer]}>
+        { pageContainer => (
+          // eslint-disable-next-line arrow-body-style
+          <SavePageControls pageContainer={pageContainer} {...this.props} />
+        )}
+      </Subscribe>
+    );
+  }
+
+}
+
 SavePageControls.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  crowi: PropTypes.object.isRequired,
+  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+  onSubmit: PropTypes.func.isRequired,
+};
+
+SavePageControlsWrapper.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   crowi: PropTypes.object.isRequired,
   onSubmit: PropTypes.func.isRequired,
-  pageId: PropTypes.string,
-  // for SlackNotification
-  slackChannels: PropTypes.string,
-  // for GrantSelector
-  grant: PropTypes.number,
-  grantGroupId: PropTypes.string,
-  grantGroupName: PropTypes.string,
 };
 
-export default withTranslation(null, { withRef: true })(SavePageControls);
+export default withTranslation(null, { withRef: true })(SavePageControlsWrapper);

+ 46 - 23
src/client/js/components/SavePageControls/GrantSelector.jsx

@@ -1,5 +1,7 @@
+/* eslint-disable react/no-multi-comp */
 import React from 'react';
 import PropTypes from 'prop-types';
+import { Subscribe } from 'unstated';
 import { withTranslation } from 'react-i18next';
 
 import FormGroup from 'react-bootstrap/es/FormGroup';
@@ -8,6 +10,8 @@ import ListGroup from 'react-bootstrap/es/ListGroup';
 import ListGroupItem from 'react-bootstrap/es/ListGroupItem';
 import Modal from 'react-bootstrap/es/Modal';
 
+import PageContainer from '../../services/PageContainer';
+
 const SPECIFIED_GROUP_VALUE = 'specifiedGroup';
 
 /**
@@ -41,22 +45,23 @@ class GrantSelector extends React.Component {
       }, // appeared only one of these 'grant: 5'
     ];
 
+    const { pageContainer } = this.props;
+
     this.state = {
-      grant: this.props.grant || 1, // default: 1
       userRelatedGroups: [],
       isSelectGroupModalShown: false,
+      grantGroup: null,
     };
-    if (this.props.grantGroupId !== '') {
+    if (pageContainer.state.grantGroupId != null) {
       this.state.grantGroup = {
-        _id: this.props.grantGroupId,
-        name: this.props.grantGroupName,
+        _id: pageContainer.state.grantGroupId,
+        name: pageContainer.state.grantGroupName,
       };
     }
 
     // retrieve xss library from window
     this.xss = window.xss;
 
-    this.getCurrentOptionsToSave = this.getCurrentOptionsToSave.bind(this);
     this.showSelectGroupModal = this.showSelectGroupModal.bind(this);
     this.hideSelectGroupModal = this.hideSelectGroupModal.bind(this);
 
@@ -85,16 +90,6 @@ class GrantSelector extends React.Component {
 
   }
 
-  getCurrentOptionsToSave() {
-    const options = {
-      grant: this.state.grant,
-    };
-    if (this.state.grantGroup != null) {
-      options.grantUserGroupId = this.state.grantGroup._id;
-    }
-    return options;
-  }
-
   showSelectGroupModal() {
     this.retrieveUserGroupRelations();
     this.setState({ isSelectGroupModalShown: true });
@@ -129,6 +124,8 @@ class GrantSelector extends React.Component {
    * change event handler for grant selector
    */
   changeGrantHandler() {
+    const { pageContainer } = this.props;
+
     const grant = +this.grantSelectorInputEl.value;
 
     // select group
@@ -141,11 +138,15 @@ class GrantSelector extends React.Component {
       return;
     }
 
-    this.setState({ grant, grantGroup: null });
+    this.setState({ grantGroup: null });
+    pageContainer.setState({ grant, grantGroupId: null, grantGroupName: null });
   }
 
   groupListItemClickHandler(grantGroup) {
-    this.setState({ grant: 5, grantGroup });
+    const { pageContainer } = this.props;
+
+    this.setState({ grantGroup });
+    pageContainer.setState({ grant: 5, grantGroupId: grantGroup._id, grantGroupName: grantGroup.name });
 
     // hide modal
     this.hideSelectGroupModal();
@@ -157,10 +158,10 @@ class GrantSelector extends React.Component {
    * @memberof GrantSelector
    */
   renderGrantSelector() {
-    const { t } = this.props;
+    const { t, pageContainer } = this.props;
 
     let index = 0;
-    let selectedValue = this.state.grant;
+    let selectedValue = pageContainer.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>;
@@ -280,12 +281,34 @@ class GrantSelector extends React.Component {
 
 }
 
+
+/**
+ * Wrapper component for using unstated
+ */
+class GrantSelectorWrapper extends React.PureComponent {
+
+  render() {
+    return (
+      <Subscribe to={[PageContainer]}>
+        { pageContainer => (
+          // eslint-disable-next-line arrow-body-style
+          <GrantSelector pageContainer={pageContainer} {...this.props} />
+        )}
+      </Subscribe>
+    );
+  }
+
+}
+
+
 GrantSelector.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   crowi: PropTypes.object.isRequired,
-  grant: PropTypes.number,
-  grantGroupId: PropTypes.string,
-  grantGroupName: PropTypes.string,
+  pageContainer: PropTypes.instanceOf(PageContainer).isRequired,
+};
+GrantSelectorWrapper.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  crowi: PropTypes.object.isRequired,
 };
 
-export default withTranslation(null, { withRef: true })(GrantSelector);
+export default withTranslation(null, { withRef: true })(GrantSelectorWrapper);

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

@@ -1,5 +1,8 @@
 import React from 'react';
-import PropTypes from 'prop-types';
+
+import { Subscribe } from 'unstated';
+
+import PageContainer from '../services/PageContainer';
 
 /**
  *
@@ -15,76 +18,55 @@ export default class SlackNotification extends React.Component {
   constructor(props) {
     super(props);
 
-    this.state = {
-      isSlackEnabled: this.props.isSlackEnabled,
-      slackChannels: this.props.slackChannels,
-    };
-
-    this.updateState = this.updateState.bind(this);
     this.updateStateCheckbox = this.updateStateCheckbox.bind(this);
   }
 
-  componentWillReceiveProps(nextProps) {
-    this.setState({
-      isSlackEnabled: nextProps.isSlackEnabled,
-      slackChannels: nextProps.slackChannels,
-    });
-  }
-
-  getCurrentOptionsToSave() {
-    return Object.assign({}, this.state);
-  }
-
-  updateState(value) {
-    this.setState({ slackChannels: value });
-    // dispatch event
-    if (this.props.onChannelChange != null) {
-      this.props.onChannelChange(value);
-    }
+  updateStateCheckbox(event, pageContainer) {
+    const value = event.target.checked;
+    pageContainer.setState({ isSlackEnabled: value });
   }
 
-  updateStateCheckbox(event) {
-    const value = event.target.checked;
-    this.setState({ isSlackEnabled: value });
-    // dispatch event
-    if (this.props.onEnabledFlagChange != null) {
-      this.props.onEnabledFlagChange(value);
-    }
+  updateStateChannels(slackChannels, pageContainer) {
+    pageContainer.setState({ slackChannels });
   }
 
   render() {
     return (
-      <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={this.state.isSlackEnabled} onChange={this.updateStateCheckbox} />
-        </label>
-        <input
-          className="form-control"
-          type="text"
-          value={this.state.slackChannels}
-          placeholder="slack channel name"
-          data-toggle="popover"
-          title="Slack通知"
-          data-content="通知するにはチェックを入れてください。カンマ区切りで複数チャンネルに通知することができます。"
-          data-trigger="focus"
-          data-placement="top"
-          onChange={(e) => { return this.updateState(e.target.value) }}
-        />
-      </div>
+      <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" />
+
+              <input
+                type="checkbox"
+                value="1"
+                checked={pageContainer.state.isSlackEnabled}
+                onChange={(e) => { this.updateStateCheckbox(e, pageContainer) }}
+              />
+
+            </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>
     );
   }
 
 }
 
 SlackNotification.propTypes = {
-  isSlackEnabled: PropTypes.bool,
-  slackChannels: PropTypes.string,
-  onChannelChange: PropTypes.func,
-  onEnabledFlagChange: PropTypes.func,
-};
-
-SlackNotification.defaultProps = {
-  slackChannels: '',
 };

+ 49 - 8
src/client/js/services/PageContainer.js

@@ -17,15 +17,8 @@ export default class PageContainer extends Container {
       return;
     }
 
-    let pageContent = '';
-    const rawText = document.getElementById('raw-text-original');
-    if (rawText) {
-      pageContent = rawText.innerHTML;
-    }
 
     this.state = {
-      markdown: entities.decodeHTML(pageContent),
-
       pageId: mainContent.getAttribute('data-page-id'),
       revisionId: mainContent.getAttribute('data-page-revision-id'),
       revisionCreatedAt: +mainContent.getAttribute('data-page-revision-created'),
@@ -33,10 +26,58 @@ export default class PageContainer extends Container {
       pageIdOnHackmd: mainContent.getAttribute('data-page-id-on-hackmd'),
       hasDraftOnHackmd: !!mainContent.getAttribute('data-page-has-draft-on-hackmd'),
       path: mainContent.getAttribute['data-path'],
-      slackChannels: mainContent.getAttribute('data-slack-channels') || '',
       templateTagData: mainContent.getAttribute('data-template-tags') || '',
+
+      isSlackEnabled: false,
+      slackChannels: mainContent.getAttribute('data-slack-channels') || '',
+
+      grant: 1, // default: public
+      grantGroupId: null,
+      grantGroupName: null,
+    };
+
+    this.initStateMarkdown();
+    this.initStateGrant();
+  }
+
+  initStateMarkdown() {
+    let pageContent = '';
+
+    const rawText = document.getElementById('raw-text-original');
+    if (rawText) {
+      pageContent = rawText.innerHTML;
+    }
+    const markdown = entities.decodeHTML(pageContent);
+
+    this.state.markdown = markdown;
+  }
+
+  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;
+      }
+    }
+  }
+
+  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;
   }
 
 }