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

Merge branch 'imprv/reactify-admin' into create-usesContainer

# Conflicts:
#	src/client/js/app.jsx
itizawa 6 лет назад
Родитель
Сommit
0c2af1aa96

+ 15 - 1
src/client/js/app.jsx

@@ -55,6 +55,7 @@ import TagContainer from './services/TagContainer';
 import UserGroupDetailContainer from './services/UserGroupDetailContainer';
 import UsersContainer from './services/UsersContainer';
 import WebsocketContainer from './services/WebsocketContainer';
+import MarkDownSettingContainer from './services/MarkDownSettingContainer';
 
 const logger = loggerFactory('growi:app');
 
@@ -107,7 +108,6 @@ let componentMappings = {
   'user-created-list': <RecentCreated />,
   'user-draft-list': <MyDraftList />,
 
-  'admin-markdown-setting': <MarkdownSetting />,
   'admin-full-text-search-management': <FullTextSearchManagement />,
   'admin-customize': <Customize />,
 
@@ -184,6 +184,20 @@ if (adminUserGroupDetailElem != null) {
     adminUserGroupDetailElem,
   );
 }
+
+const adminMarkDownSettingElem = document.getElementById('admin-markdown-setting');
+if (adminMarkDownSettingElem != null) {
+  const markDownSettingContainer = new MarkDownSettingContainer(appContainer);
+  ReactDOM.render(
+    <Provider inject={[injectableContainers, markDownSettingContainer]}>
+      <I18nextProvider i18n={i18n}>
+        <MarkdownSetting />
+      </I18nextProvider>
+    </Provider>,
+    adminMarkDownSettingElem,
+  );
+}
+
 const customCssEditorElem = document.getElementById('custom-css-editor');
 if (customCssEditorElem != null) {
   // get input[type=hidden] element

+ 3 - 4
src/client/js/components/Admin/MarkdownSetting/MarkDownSetting.jsx

@@ -37,6 +37,7 @@ class MarkdownSetting extends React.Component {
     const { t } = this.props;
 
     return (
+      // TODO GW-322 adjust layout
       <React.Fragment>
         <div>
           {/* Line Break Setting */}
@@ -99,10 +100,8 @@ class MarkdownSetting extends React.Component {
         </div>
         {/* XSS Setting */}
         <div className="row my-3">
-          <div className="form-group">
-            <legend>{ t('markdown_setting.XSS_setting') }</legend>
-            <p className="well">{ t('markdown_setting.XSS_setting_desc') }</p>
-          </div>
+          <h2>{ t('markdown_setting.XSS_setting') }</h2>
+          <p className="well">{ t('markdown_setting.XSS_setting_desc') }</p>
           <XssForm />
         </div>
       </React.Fragment>

+ 43 - 10
src/client/js/components/Admin/MarkdownSetting/WhiteListInput.jsx

@@ -1,11 +1,12 @@
-/* eslint-disable max-len */
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
+import { tags, attrs } from '../../../../../lib/service/xss/recommended-whitelist';
 
 import AppContainer from '../../../services/AppContainer';
+import MarkDownSettingContainer from '../../../services/MarkDownSettingContainer';
 
 class WhiteListInput extends React.Component {
 
@@ -19,9 +20,28 @@ class WhiteListInput extends React.Component {
     );
   }
 
+  renderTagValue() {
+    const { customizable, markDownSettingContainer } = this.props;
+
+    if (customizable) {
+      return markDownSettingContainer.state.tagWhiteList;
+    }
+
+    return tags;
+  }
+
+  renderAttrValue() {
+    const { customizable, markDownSettingContainer } = this.props;
+
+    if (customizable) {
+      return markDownSettingContainer.state.attrWhiteList;
+    }
+
+    return attrs;
+  }
+
   render() {
-    const { t, customizable } = this.props;
-    const { onChangeTagWhiteList, onChangeAttrWhiteList } = this.props;
+    const { t, customizable, markDownSettingContainer } = this.props;
 
     return (
       <>
@@ -30,16 +50,30 @@ class WhiteListInput extends React.Component {
             { t('markdown_setting.Tag names') }
             {customizable && this.renderRecommendBtn()}
           </div>
-          {/* TODO GW-304 fetch correct defaultValue */}
-          <textarea className="form-control xss-list" name="recommendedTags" rows="6" cols="40" readOnly={!customizable} defaultValue="recommendedWhitelist.tags" onChange={(e) => { onChangeTagWhiteList(e.target.value) }} />
+          <textarea
+            className="form-control xss-list"
+            name="recommendedTags"
+            rows="6"
+            cols="40"
+            readOnly={!customizable}
+            value={this.renderTagValue()}
+            onChange={(e) => { markDownSettingContainer.setState({ tagWhiteList: e.target.value }) }}
+          />
         </div>
         <div className="m-t-15">
           <div className="d-flex justify-content-between">
             { t('markdown_setting.Tag attributes') }
             {customizable && this.renderRecommendBtn()}
           </div>
-          {/* TODO GW-304 fetch correct defaultValue */}
-          <textarea className="form-control xss-list" name="recommendedAttrs" rows="6" cols="40" readOnly={!customizable} defaultValue="recommendedWhitelist.attrs" onChange={(e) => { onChangeAttrWhiteList(e.target.value) }} />
+          <textarea
+            className="form-control xss-list"
+            name="recommendedAttrs"
+            rows="6"
+            cols="40"
+            readOnly={!customizable}
+            value={this.renderAttrValue()}
+            onChange={(e) => { markDownSettingContainer.setState({ attrWhiteList: e.target.value }) }}
+          />
         </div>
       </>
     );
@@ -48,16 +82,15 @@ class WhiteListInput extends React.Component {
 }
 
 const WhiteListWrapper = (props) => {
-  return createSubscribedElement(WhiteListInput, props, [AppContainer]);
+  return createSubscribedElement(WhiteListInput, props, [AppContainer, MarkDownSettingContainer]);
 };
 
 WhiteListInput.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  markDownSettingContainer: PropTypes.instanceOf(MarkDownSettingContainer).isRequired,
 
   customizable: PropTypes.bool.isRequired,
-  onChangeTagWhiteList: PropTypes.func,
-  onChangeAttrWhiteList: PropTypes.func,
 };
 
 export default withTranslation()(WhiteListWrapper);

+ 33 - 51
src/client/js/components/Admin/MarkdownSetting/XssForm.jsx

@@ -1,5 +1,3 @@
-/* eslint-disable react/no-unused-state */
-/* eslint-disable max-len */
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
@@ -7,6 +5,8 @@ import { withTranslation } from 'react-i18next';
 import { createSubscribedElement } from '../../UnstatedUtils';
 
 import AppContainer from '../../../services/AppContainer';
+import MarkDownSettingContainer from '../../../services/MarkDownSettingContainer';
+
 import WhiteListInput from './WhiteListInput';
 
 class XssForm extends React.Component {
@@ -14,58 +14,27 @@ class XssForm extends React.Component {
   constructor(props) {
     super(props);
 
-    const { appContainer } = this.props;
-
-    this.state = {
-      // TODO GW-304 fetch correct value
-      isEnabledXss: false,
-      XssOption: 1,
-      tagWhiteList: appContainer.config.tagWhiteList,
-      attrWhiteList: '',
-    };
-
-    this.onChangeEnableXss = this.onChangeEnableXss.bind(this);
-    this.onChangeXssOption = this.onChangeXssOption.bind(this);
-    this.onChangeTagWhiteList = this.onChangeTagWhiteList.bind(this);
-    this.onChangeAttrWhiteList = this.onChangeAttrWhiteList.bind(this);
     this.onClickSubmit = this.onClickSubmit.bind(this);
   }
 
-  onChangeEnableXss() {
-    this.setState({ isEnabledXss: !this.state.isEnabledXss });
-  }
-
-  onChangeXssOption(value) {
-    this.setState({ XssOption: value });
-  }
-
-  onChangeTagWhiteList(value) {
-    this.setState({ tagWhiteList: value });
-  }
-
-  onChangeAttrWhiteList(value) {
-    this.setState({ attrWhiteList: value });
-  }
-
-  async componentDidMount() {
-    await this.syncXssSettings();
-  }
-
   async onClickSubmit() {
     // TODO GW-303 create apiV3 of update setting
   }
 
-  async syncXssSettings() {
-    // TODO GW-304 createApiV3
-  }
-
   xssOptions() {
-    const { t } = this.props;
+    const { t, markDownSettingContainer } = this.props;
+    const { xssOption } = markDownSettingContainer.state;
 
     return (
       <fieldset className="form-group col-xs-12 my-3">
         <div className="col-xs-4 radio radio-primary">
-          <input type="radio" id="xssOption1" name="XssOption" onChange={() => { this.onChangeXssOption(1) }} />
+          <input
+            type="radio"
+            id="xssOption1"
+            name="XssOption"
+            checked={xssOption === 1}
+            onChange={() => { markDownSettingContainer.setState({ xssOption: 1 }) }}
+          />
           <label htmlFor="xssOption1">
             <p className="font-weight-bold">{ t('markdown_setting.Ignore all tags') }</p>
             <div className="m-t-15">
@@ -75,7 +44,13 @@ class XssForm extends React.Component {
         </div>
 
         <div className="col-xs-4 radio radio-primary">
-          <input type="radio" id="xssOption2" name="XssOption" onChange={() => { this.onChangeXssOption(2) }} />
+          <input
+            type="radio"
+            id="xssOption2"
+            name="XssOption"
+            checked={xssOption === 2}
+            onChange={() => { markDownSettingContainer.setState({ xssOption: 2 }) }}
+          />
           <label htmlFor="xssOption2">
             <p className="font-weight-bold">{ t('markdown_setting.Recommended setting') }</p>
             <WhiteListInput customizable={false} />
@@ -83,10 +58,16 @@ class XssForm extends React.Component {
         </div>
 
         <div className="col-xs-4 radio radio-primary">
-          <input type="radio" id="xssOption3" name="XssOption" onChange={() => { this.onChangeXssOption(3) }} />
+          <input
+            type="radio"
+            id="xssOption3"
+            name="XssOption"
+            checked={xssOption === 3}
+            onChange={() => { markDownSettingContainer.setState({ xssOption: 3 }) }}
+          />
           <label htmlFor="xssOption3">
             <p className="font-weight-bold">{ t('markdown_setting.Custom Whitelist') }</p>
-            <WhiteListInput customizable onChangeTagWhiteList={this.onChangeTagWhiteList} onChangeAttrWhiteList={this.onChangeAttrWhiteList} />
+            <WhiteListInput customizable />
           </label>
         </div>
       </fieldset>
@@ -94,21 +75,22 @@ class XssForm extends React.Component {
   }
 
   render() {
-    const { t } = this.props;
+    const { t, markDownSettingContainer } = this.props;
+    const { isEnabledXss } = markDownSettingContainer.state;
 
     return (
       <React.Fragment>
         <form className="row">
           <div className="form-group">
             <div className="col-xs-4 text-right">
-              <div className="checkbox checkbox-success" onChange={this.onChangeEnableXss}>
-                <input type="checkbox" id="XssEnable" className="form-check-input" name="isEnabledXss" checked={this.state.isEnabledXss} />
+              <div className="checkbox checkbox-success" onChange={markDownSettingContainer.switchEnableXss}>
+                <input type="checkbox" id="XssEnable" className="form-check-input" name="isEnabledXss" checked={isEnabledXss} />
                 <label htmlFor="XssEnable">
                   { t('markdown_setting.Enable XSS prevention') }
                 </label>
               </div>
             </div>
-            {this.state.isEnabledXss && this.xssOptions()}
+            {isEnabledXss && this.xssOptions()}
           </div>
           <div className="form-group my-3">
             <div className="col-xs-offset-4 col-xs-5">
@@ -123,13 +105,13 @@ class XssForm extends React.Component {
 }
 
 const XssFormWrapper = (props) => {
-  return createSubscribedElement(XssForm, props, [AppContainer]);
+  return createSubscribedElement(XssForm, props, [AppContainer, MarkDownSettingContainer]);
 };
 
 XssForm.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-
+  markDownSettingContainer: PropTypes.instanceOf(MarkDownSettingContainer).isRequired,
 };
 
 export default withTranslation()(XssFormWrapper);

+ 41 - 0
src/client/js/services/MarkDownSettingContainer.js

@@ -0,0 +1,41 @@
+import { Container } from 'unstated';
+
+/**
+ * Service container for admin markdown setting page (MarkDownSetting.jsx)
+ * @extends {Container} unstated Container
+ */
+export default class MarkDownSettingContainer extends Container {
+
+  constructor(appContainer) {
+    super();
+
+    this.appContainer = appContainer;
+
+    this.state = {
+      isEnabledXss: (appContainer.config.xssOption != null),
+      xssOption: appContainer.config.xssOption,
+      tagWhiteList: appContainer.config.tagWhiteList || '',
+      attrWhiteList: appContainer.config.attrWhiteList || '',
+    };
+
+    this.switchEnableXss = this.switchEnableXss.bind(this);
+  }
+
+  /**
+   * Workaround for the mangling in production build to break constructor.name
+   */
+  static getClassName() {
+    return 'MarkDownSettingContainer';
+  }
+
+  /**
+   * Switch enableXss
+   */
+  switchEnableXss() {
+    if (this.state.isEnabledXss) {
+      this.setState({ xssOption: null });
+    }
+    this.setState({ isEnabledXss: !this.state.isEnabledXss });
+  }
+
+}

+ 2 - 0
src/server/routes/apiv3/index.js

@@ -13,6 +13,8 @@ module.exports = (crowi) => {
 
   router.use('/healthcheck', require('./healthcheck')(crowi));
 
+  router.use('/markdown-setting', require('./markdown-setting')(crowi));
+
   router.use('/users', require('./users')(crowi));
 
   router.use('/user-groups', require('./user-group')(crowi));

+ 26 - 0
src/server/routes/apiv3/markdown-setting.js

@@ -0,0 +1,26 @@
+/* eslint-disable no-unused-vars */
+const loggerFactory = require('@alias/logger');
+
+const logger = loggerFactory('growi:routes:apiv3:user-group');
+
+const express = require('express');
+
+const router = express.Router();
+
+/**
+ * @swagger
+ *  tags:
+ *    name: MarkDownSetting
+ */
+
+module.exports = (crowi) => {
+  const loginRequiredStrictly = require('../../middleware/login-required')(crowi);
+  const adminRequired = require('../../middleware/admin-required')(crowi);
+
+  const {
+    ErrorV3,
+    Config,
+  } = crowi.models;
+
+  return router;
+};

+ 1 - 1
src/server/routes/index.js

@@ -91,7 +91,7 @@ module.exports = function(crowi, app) {
   app.post('/passport/saml/callback'              , loginPassport.loginPassportSamlCallback);
 
   // markdown admin
-  app.get('/admin/markdown'                   , loginRequiredStrictly , adminRequired , admin.markdown.index);
+  app.get('/admin/markdown'                   , loginRequiredStrictly , adminRequired , admin.markdown.index); // TODO delete
   app.post('/_api/admin/markdown/lineBreaksSetting', loginRequiredStrictly , adminRequired , csrf, form.admin.markdown, admin.markdown.lineBreaksSetting); // change form name
   app.post('/admin/markdown/xss-setting'      , loginRequiredStrictly , adminRequired , csrf, form.admin.markdownXss, admin.markdown.xssSetting);
   app.post('/admin/markdown/presentationSetting', loginRequiredStrictly , adminRequired , csrf, form.admin.markdownPresentation, admin.markdown.presentationSetting);