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

Merge pull request #1376 from weseek/reactify-admin/markDownSettings

Reactify admin/mark down settings
Yuki Takei 6 лет назад
Родитель
Сommit
f900b7d0b7

+ 6 - 3
resource/locales/en-US/translation.json

@@ -602,9 +602,9 @@
     "line_break_setting": "Line Break Setting",
     "line_break_setting_desc": "You can change line break settings.",
     "Enable Line Break": "Enable Line Break",
-    "Enable Line Break desc": "Treat line break in the text page as <code>&lt;br&gt;</code> in HTML",
+    "Enable Line Break desc": "Treat line break in the text page as<code>&lt;br&gt;</code>in HTML",
     "Enable Line Break for comment": "Enable Line Break in comment",
-    "Enable Line Break for comment desc": "Treat line break in comment as <code>&lt;br&gt;</code> in HTML",
+    "Enable Line Break for comment desc": "Treat line break in comment as<code>&lt;br&gt;</code>in HTML",
     "presentation_setting": "Presentation Setting",
     "presentation_setting_desc": "You can change presentation settings.",
     "Page break setting": "Page break Setting",
@@ -625,7 +625,10 @@
     "Custom Whitelist": "Custom Whitelist",
     "Tag names":"Tag names",
     "Tag attributes":"Tag attributes",
-    "import_recommended": "Import recommended %s"
+    "import_recommended": "Import recommended %s",
+    "updated_lineBreak": "Succeeded to update line braek setting",
+    "updated_presentation": "Succeeded to update presentation setting",
+    "updated_xss": "Succeeded to update XSS setting"
   },
 
   "notification_setting": {

+ 4 - 2
resource/locales/ja/translation.json

@@ -609,7 +609,10 @@
     "Custom Whitelist": "カスタムホワイトリスト",
     "Tag names": "タグ名のホワイトリスト",
     "Tag attributes": "タグ属性のホワイトリスト",
-    "import_recommended": "おすすめをインポート"
+    "import_recommended": "おすすめをインポート",
+    "updated_lineBreak": "改行設定を更新しました",
+    "updated_presentation": "プレゼンテーション設定を更新しました",
+    "updated_xss": "XSS設定を更新しました"
   },
 
   "notification_setting": {
@@ -705,7 +708,6 @@
     "deactivate_user_success": "{{username}}を無効化しました",
     "remove_user_success": "{{username}}を削除しました",
     "remove_external_user_success": "{{accountId}}を削除しました "
-
   },
 
   "user_group_management": {

+ 0 - 0
src/client/js/components/Admin/Customize/CustomizeLayoutSetting.jsx


+ 122 - 0
src/client/js/components/Admin/MarkdownSetting/LineBreakForm.jsx

@@ -0,0 +1,122 @@
+/* eslint-disable react/no-danger */
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+import loggerFactory from '@alias/logger';
+
+import { createSubscribedElement } from '../../UnstatedUtils';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
+
+import AppContainer from '../../../services/AppContainer';
+import MarkDownSettingContainer from '../../../services/MarkDownSettingContainer';
+
+const logger = loggerFactory('growi:importer');
+
+class LineBreakForm extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.onClickSubmit = this.onClickSubmit.bind(this);
+  }
+
+
+  async onClickSubmit() {
+    const { t } = this.props;
+
+    try {
+      await this.props.markDownSettingContainer.updateLineBreakSetting();
+      toastSuccess(t('markdown_setting.updated_lineBreak'));
+    }
+    catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
+  }
+
+  renderLineBreakOption() {
+    const { t, markDownSettingContainer } = this.props;
+    const { isEnabledLinebreaks } = markDownSettingContainer.state;
+
+    const helpLineBreak = { __html: t('markdown_setting.Enable Line Break desc') };
+
+    return (
+      <div className="form-group row">
+        <div className="col-xs-offset-4 col-xs-6 text-left">
+          <div className="checkbox checkbox-success">
+            <input
+              type="checkbox"
+              id="isEnabledLinebreaks"
+              checked={isEnabledLinebreaks}
+              onChange={() => { markDownSettingContainer.setState({ isEnabledLinebreaks: !isEnabledLinebreaks }) }}
+            />
+            <label htmlFor="isEnabledLinebreaks">
+              { t('markdown_setting.Enable Line Break') }
+            </label>
+          </div>
+          <p className="help-block" dangerouslySetInnerHTML={helpLineBreak} />
+        </div>
+      </div>
+    );
+  }
+
+  renderLineBreakInCommentOption() {
+    const { t, markDownSettingContainer } = this.props;
+    const { isEnabledLinebreaksInComments } = markDownSettingContainer.state;
+
+    const helpLineBreakInComment = { __html: t('markdown_setting.Enable Line Break for comment desc') };
+
+    return (
+      <div className="form-group row">
+        <div className="col-xs-offset-4 col-xs-6 text-left">
+          <div className="checkbox checkbox-success">
+            <input
+              type="checkbox"
+              id="isEnabledLinebreaksInComments"
+              checked={isEnabledLinebreaksInComments}
+              onChange={() => { markDownSettingContainer.setState({ isEnabledLinebreaksInComments: !isEnabledLinebreaksInComments }) }}
+            />
+            <label htmlFor="isEnabledLinebreaksInComments">
+              { t('markdown_setting.Enable Line Break for comment') }
+            </label>
+          </div>
+          <p className="help-block" dangerouslySetInnerHTML={helpLineBreakInComment} />
+        </div>
+      </div>
+    );
+  }
+
+  render() {
+    const { t } = this.props;
+
+    return (
+      <React.Fragment>
+        <fieldset className="row">
+          {this.renderLineBreakOption()}
+          {this.renderLineBreakInCommentOption()}
+        </fieldset>
+        <div className="form-group my-3">
+          <div className="col-xs-offset-4 col-xs-5">
+            <button type="submit" className="btn btn-primary" onClick={this.onClickSubmit}>{ t('Update') }</button>
+          </div>
+        </div>
+      </React.Fragment>
+    );
+  }
+
+}
+
+/**
+ * Wrapper component for using unstated
+ */
+const LineBreakFormWrapper = (props) => {
+  return createSubscribedElement(LineBreakForm, props, [AppContainer, MarkDownSettingContainer]);
+};
+
+LineBreakForm.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  markDownSettingContainer: PropTypes.instanceOf(MarkDownSettingContainer).isRequired,
+};
+
+export default withTranslation()(LineBreakFormWrapper);

+ 0 - 115
src/client/js/components/Admin/MarkdownSetting/LineBreakSetting.jsx

@@ -1,115 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { withTranslation } from 'react-i18next';
-import loggerFactory from '@alias/logger';
-
-import { createSubscribedElement } from '../../UnstatedUtils';
-import { toastSuccess, toastError } from '../../../util/apiNotification';
-
-import AppContainer from '../../../services/AppContainer';
-
-const logger = loggerFactory('growi:importer');
-
-class LineBreakSetting extends React.Component {
-
-  constructor(props) {
-    super(props);
-
-    const { appContainer } = this.props;
-
-    this.state = {
-      isEnabledLinebreaks: appContainer.config.isEnabledLinebreaks,
-      isEnabledLinebreaksInComments: appContainer.config.isEnabledLinebreaksInComments,
-    };
-    this.onChangeEnableLineBreaks = this.onChangeEnableLineBreaks.bind(this);
-    this.onChangeEnableLineBreaksInComments = this.onChangeEnableLineBreaksInComments.bind(this);
-    this.changeLineBreakSettings = this.changeLineBreakSettings.bind(this);
-  }
-
-
-  onChangeEnableLineBreaks() {
-    this.setState({ isEnabledLinebreaks: !this.state.isEnabledLinebreaks });
-  }
-
-  onChangeEnableLineBreaksInComments() {
-    this.setState({ isEnabledLinebreaksInComments: !this.state.isEnabledLinebreaksInComments });
-  }
-
-  async changeLineBreakSettings() {
-    const { appContainer } = this.props;
-    const params = {
-      isEnabledLinebreaks: this.state.isEnabledLinebreaks,
-      isEnabledLinebreaksInComments: this.state.isEnabledLinebreaksInComments,
-    };
-    try {
-      await appContainer.apiPost('/admin/markdown/lineBreaksSetting', { params });
-      toastSuccess('Success change line braek setting');
-    }
-    catch (err) {
-      toastError(err);
-      logger.error(err);
-    }
-  }
-
-
-  render() {
-    const { t } = this.props;
-
-    return (
-      <React.Fragment>
-        <div className="row my-3">
-          <div className="form-group">
-            <legend>{ t('markdown_setting.line_break_setting') }</legend>
-            <p className="well">{ t('markdown_setting.line_break_setting_desc') }</p>
-            <fieldset className="row">
-              <div className="form-group">
-                <div className="col-xs-4 text-right">
-                  <div className="checkbox checkbox-success" onChange={this.onChangeEnableLineBreaks}>
-                    <input type="checkbox" name="isEnabledLinebreaks" checked={this.state.isEnabledLinebreaks} />
-                    <label>
-                      { t('markdown_setting.Enable Line Break') }
-                    </label>
-                    <p className="help-block">{ t('markdown_setting.Enable Line Break desc') }</p>
-                  </div>
-                </div>
-              </div>
-            </fieldset>
-            <fieldset className="row">
-              <div className="form-group my-3">
-                <div className="col-xs-4 text-right">
-                  <div className="checkbox checkbox-success" onChange={this.onChangeEnableLineBreaksInComments}>
-                    <input type="checkbox" name="isEnabledLinebreaksInComments" checked={this.state.isEnabledLinebreaksInComments} />
-                    <label>
-                      { t('markdown_setting.Enable Line Break for comment') }
-                    </label>
-                    <p className="help-block">{ t('markdown_setting.Enable Line Break for comment desc') }</p>
-                  </div>
-                </div>
-              </div>
-            </fieldset>
-          </div>
-          <div className="form-group my-3">
-            <div className="col-xs-offset-4 col-xs-5">
-              <button type="submit" className="btn btn-primary" onClick={this.changeLineBreakSettings}>{ t('Update') }</button>
-            </div>
-          </div>
-        </div>
-      </React.Fragment>
-    );
-  }
-
-}
-
-/**
- * Wrapper component for using unstated
- */
-const LineBreakSettingWrapper = (props) => {
-  return createSubscribedElement(LineBreakSetting, props, [AppContainer]);
-};
-
-LineBreakSetting.propTypes = {
-  t: PropTypes.func.isRequired, // i18next
-  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
-};
-
-export default withTranslation()(LineBreakSettingWrapper);

+ 15 - 83
src/client/js/components/Admin/MarkdownSetting/MarkDownSetting.jsx

@@ -1,4 +1,3 @@
-/* eslint-disable max-len */
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
@@ -6,101 +5,34 @@ import { withTranslation } from 'react-i18next';
 import { createSubscribedElement } from '../../UnstatedUtils';
 
 import AppContainer from '../../../services/AppContainer';
-import LineBreakSetting from './LineBreakSetting';
+import LineBreakForm from './LineBreakForm';
+import PresentationForm from './PresentationForm';
 import XssForm from './XssForm';
 
 class MarkdownSetting extends React.Component {
 
-  constructor(props) {
-    super(props);
-
-    this.state = {
-      // TODO GW-220 get correct BreakOption value
-      pageBreakOption: 1,
-      // TODO GW-258 get correct custom regular expression
-      customRegularExpression: '',
-    };
-
-    this.handleInputChange = this.handleInputChange.bind(this);
-  }
-
-  // TODO Delete after component split
-  handleInputChange(e) {
-    const target = e.target;
-    const value = target.type === 'checkbox' ? target.checked : target.value;
-    const name = target.name;
-
-    this.setState({ [name]: value });
-  }
-
   render() {
     const { t } = this.props;
 
     return (
-      // TODO GW-322 adjust layout
       <React.Fragment>
-        <div>
-          {/* Line Break Setting */}
-          <LineBreakSetting />
+        {/* Line Break Setting */}
+        <div className="row mb-5">
+          <h2 className="border-bottom">{ t('markdown_setting.line_break_setting') }</h2>
+          <p className="well">{ t('markdown_setting.line_break_setting_desc') }</p>
+          <LineBreakForm />
         </div>
 
-        <div className="row my-3">
-          <div className="form-group">
-            <legend>{ t('markdown_setting.presentation_setting') }</legend>
-            <p className="well">{ t('markdown_setting.presentation_setting_desc') }</p>
-          </div>
-          <fieldset className="form-group row my-2">
-
-            <label className="col-xs-3 control-label text-right">
-              { t('markdown_setting.Page break setting') }
-            </label>
-
-            <div className="col-xs-3 radio radio-primary">
-              <input type="radio" id="pageBreakOption1" name="pageBreakOption" value="1" checked={this.state.pageBreakOption === 1} onChange={this.handleInputChange} />
-              <label htmlFor="pageBreakOption1">
-                <p className="font-weight-bold">{ t('markdown_setting.Preset one separator') }</p>
-                <p className="mt-3">
-                  { t('markdown_setting.Preset one separator desc') }
-                  <pre><code>{ t('markdown_setting.Preset one separator value') }</code></pre>
-                </p>
-              </label>
-            </div>
-
-            <div className="col-xs-3 radio radio-primary mt-3">
-              <input type="radio" id="pageBreakOption2" name="pageBreakOption" value="2" checked={this.state.pageBreakOption === 2} onChange={this.handleInputChange} />
-              <label htmlFor="pageBreakOption2">
-                <p className="font-weight-bold">{ t('markdown_setting.Preset two separator') }</p>
-                <p className="mt-3">
-                  { t('markdown_setting.Preset two separator desc') }
-                  <pre><code>{ t('markdown_setting.Preset two separator value') }</code></pre>
-                </p>
-              </label>
-            </div>
-
-            <div className="col-xs-3 radio radio-primary mt-3">
-              <input type="radio" id="pageBreakOption3" name="pageBreakOption" value="3" checked={this.state.pageBreakOption === 3} onChange={this.handleInputChange} />
-              <label htmlFor="pageBreakOption3">
-                <p className="font-weight-bold">{ t('markdown_setting.Custom separator') }</p>
-                <p className="mt-3">
-                  { t('markdown_setting.Custom separator desc') }
-                  <div>
-                    <input className="form-control" name="customRegularExpression" value={this.state.customRegularExpression} onChange={this.handleInputChange} />
-                  </div>
-                </p>
-              </label>
-            </div>
-
-            <div className="form-group my-3">
-              <div className="col-xs-offset-4 col-xs-5">
-                <button type="submit" className="btn btn-primary">{ t('Update') }</button>
-              </div>
-            </div>
-
-          </fieldset>
+        {/* Presentation Setting */}
+        <div className="row mb-5">
+          <h2 className="border-bottom">{ t('markdown_setting.presentation_setting') }</h2>
+          <p className="well">{ t('markdown_setting.presentation_setting_desc') }</p>
+          <PresentationForm />
         </div>
+
         {/* XSS Setting */}
-        <div className="row my-3">
-          <h2>{ t('markdown_setting.XSS_setting') }</h2>
+        <div className="row mb-5">
+          <h2 className="border-bottom">{ t('markdown_setting.XSS_setting') }</h2>
           <p className="well">{ t('markdown_setting.XSS_setting_desc') }</p>
           <XssForm />
         </div>

+ 122 - 0
src/client/js/components/Admin/MarkdownSetting/PresentationForm.jsx

@@ -0,0 +1,122 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+import loggerFactory from '@alias/logger';
+
+import { createSubscribedElement } from '../../UnstatedUtils';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
+
+import AppContainer from '../../../services/AppContainer';
+import MarkDownSettingContainer from '../../../services/MarkDownSettingContainer';
+
+const logger = loggerFactory('growi:markdown:presentation');
+
+class PresentationForm extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.onClickSubmit = this.onClickSubmit.bind(this);
+  }
+
+  async onClickSubmit() {
+    const { t } = this.props;
+
+    try {
+      await this.props.markDownSettingContainer.updatePresentationSetting();
+      toastSuccess(t('markdown_setting.updated_presentation'));
+    }
+    catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
+  }
+
+
+  render() {
+    const { t, markDownSettingContainer } = this.props;
+    const { pageBreakSeparator, pageBreakCustomSeparator } = markDownSettingContainer.state;
+
+    return (
+      <fieldset className="form-group row my-2">
+
+        <label className="col-xs-3 control-label text-right">
+          { t('markdown_setting.Page break setting') }
+        </label>
+
+        <div className="col-xs-3 radio radio-primary">
+          <input
+            type="radio"
+            id="pageBreakOption1"
+            checked={pageBreakSeparator === 1}
+            onChange={() => markDownSettingContainer.switchPageBreakSeparator(1)}
+          />
+          <label htmlFor="pageBreakOption1">
+            <p className="font-weight-bold">{ t('markdown_setting.Preset one separator') }</p>
+            <div className="mt-3">
+              { t('markdown_setting.Preset one separator desc') }
+              <pre><code>{ t('markdown_setting.Preset one separator value') }</code></pre>
+            </div>
+          </label>
+        </div>
+
+        <div className="col-xs-3 radio radio-primary mt-3">
+          <input
+            type="radio"
+            id="pageBreakOption2"
+            checked={pageBreakSeparator === 2}
+            onChange={() => markDownSettingContainer.switchPageBreakSeparator(2)}
+          />
+          <label htmlFor="pageBreakOption2">
+            <p className="font-weight-bold">{ t('markdown_setting.Preset two separator') }</p>
+            <div className="mt-3">
+              { t('markdown_setting.Preset two separator desc') }
+              <pre><code>{ t('markdown_setting.Preset two separator value') }</code></pre>
+            </div>
+          </label>
+        </div>
+
+        <div className="col-xs-3 radio radio-primary mt-3">
+          <input
+            type="radio"
+            id="pageBreakOption3"
+            checked={pageBreakSeparator === 3}
+            onChange={() => markDownSettingContainer.switchPageBreakSeparator(3)}
+          />
+          <label htmlFor="pageBreakOption3">
+            <p className="font-weight-bold">{ t('markdown_setting.Custom separator') }</p>
+            <div className="mt-3">
+              { t('markdown_setting.Custom separator desc') }
+              <input
+                className="form-control"
+                value={pageBreakCustomSeparator}
+                onChange={(e) => { markDownSettingContainer.setPageBreakCustomSeparator(e.target.value) }}
+              />
+            </div>
+          </label>
+        </div>
+
+        <div className="form-group my-3">
+          <div className="col-xs-offset-4 col-xs-5">
+            <div className="btn btn-primary" onClick={this.onClickSubmit}>{ t('Update') }</div>
+          </div>
+        </div>
+
+      </fieldset>
+    );
+  }
+
+}
+
+const PresentationFormWrapper = (props) => {
+  return createSubscribedElement(PresentationForm, props, [AppContainer, MarkDownSettingContainer]);
+};
+
+PresentationForm.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  markDownSettingContainer: PropTypes.instanceOf(MarkDownSettingContainer).isRequired,
+
+};
+
+export default withTranslation()(PresentationFormWrapper);

+ 86 - 0
src/client/js/components/Admin/MarkdownSetting/PresentationLineBreakOptions.jsx

@@ -0,0 +1,86 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { withTranslation } from 'react-i18next';
+
+import { createSubscribedElement } from '../../UnstatedUtils';
+
+import AppContainer from '../../../services/AppContainer';
+import MarkDownSettingContainer from '../../../services/MarkDownSettingContainer';
+
+class PresentationLineBreakOptions extends React.Component {
+
+  render() {
+    const { t, markDownSettingContainer } = this.props;
+    const { pageBreakOption, customRegularExpression } = markDownSettingContainer.state;
+
+    return (
+      <Fragment>
+        <div className="col-xs-3 radio radio-primary">
+          <input
+            type="radio"
+            id="pageBreakOption1"
+            checked={pageBreakOption === 1}
+            onChange={() => { markDownSettingContainer.setState({ pageBreakOption: 1 }) }}
+          />
+          <label htmlFor="pageBreakOption1">
+            <p className="font-weight-bold">{ t('markdown_setting.Preset one separator') }</p>
+            <div className="mt-3">
+              { t('markdown_setting.Preset one separator desc') }
+              <pre><code>{ t('markdown_setting.Preset one separator value') }</code></pre>
+            </div>
+          </label>
+        </div>
+
+        <div className="col-xs-3 radio radio-primary mt-3">
+          <input
+            type="radio"
+            id="pageBreakOption2"
+            checked={pageBreakOption === 2}
+            onChange={() => { markDownSettingContainer.setState({ pageBreakOption: 2 }) }}
+          />
+          <label htmlFor="pageBreakOption2">
+            <p className="font-weight-bold">{ t('markdown_setting.Preset two separator') }</p>
+            <div className="mt-3">
+              { t('markdown_setting.Preset two separator desc') }
+              <pre><code>{ t('markdown_setting.Preset two separator value') }</code></pre>
+            </div>
+          </label>
+        </div>
+
+        <div className="col-xs-3 radio radio-primary mt-3">
+          <input
+            type="radio"
+            id="pageBreakOption3"
+            checked={pageBreakOption === 3}
+            onChange={() => { markDownSettingContainer.setState({ pageBreakOption: 3 }) }}
+          />
+          <label htmlFor="pageBreakOption3">
+            <p className="font-weight-bold">{ t('markdown_setting.Custom separator') }</p>
+            <div className="mt-3">
+              { t('markdown_setting.Custom separator desc') }
+              <input
+                className="form-control"
+                value={customRegularExpression}
+                onChange={(e) => { markDownSettingContainer.setState({ customRegularExpression: e.target.value }) }}
+              />
+            </div>
+          </label>
+        </div>
+      </Fragment>
+    );
+  }
+
+}
+
+const PresentationLineBreakOptionsWrapper = (props) => {
+  return createSubscribedElement(PresentationLineBreakOptions, props, [AppContainer, MarkDownSettingContainer]);
+};
+
+PresentationLineBreakOptions.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  markDownSettingContainer: PropTypes.instanceOf(MarkDownSettingContainer).isRequired,
+
+};
+
+export default withTranslation()(PresentationLineBreakOptionsWrapper);

+ 15 - 5
src/client/js/components/Admin/MarkdownSetting/WhiteListInput.jsx

@@ -10,16 +10,26 @@ import MarkDownSettingContainer from '../../../services/MarkDownSettingContainer
 
 class WhiteListInput extends React.Component {
 
-  renderRecommendBtn() {
-    const { t } = this.props;
+  renderRecommendTagBtn() {
+    const { t, markDownSettingContainer } = this.props;
 
     return (
-      <p id="btn-import-tags" className="btn btn-xs btn-primary">
+      <p id="btn-import-tags" className="btn btn-xs btn-primary" onClick={() => { markDownSettingContainer.setState({ tagWhiteList: tags }) }}>
         { t('markdown_setting.import_recommended', 'tags') }
       </p>
     );
   }
 
+  renderRecommendAttrBtn() {
+    const { t, markDownSettingContainer } = this.props;
+
+    return (
+      <p id="btn-import-tags" className="btn btn-xs btn-primary" onClick={() => { markDownSettingContainer.setState({ attrWhiteList: attrs }) }}>
+        { t('markdown_setting.import_recommended', 'Attrs') }
+      </p>
+    );
+  }
+
   renderTagValue() {
     const { customizable, markDownSettingContainer } = this.props;
 
@@ -48,7 +58,7 @@ class WhiteListInput extends React.Component {
         <div className="m-t-15">
           <div className="d-flex justify-content-between">
             { t('markdown_setting.Tag names') }
-            {customizable && this.renderRecommendBtn()}
+            {customizable && this.renderRecommendTagBtn()}
           </div>
           <textarea
             className="form-control xss-list"
@@ -63,7 +73,7 @@ class WhiteListInput extends React.Component {
         <div className="m-t-15">
           <div className="d-flex justify-content-between">
             { t('markdown_setting.Tag attributes') }
-            {customizable && this.renderRecommendBtn()}
+            {customizable && this.renderRecommendAttrBtn()}
           </div>
           <textarea
             className="form-control xss-list"

+ 24 - 4
src/client/js/components/Admin/MarkdownSetting/XssForm.jsx

@@ -1,14 +1,18 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
+import loggerFactory from '@alias/logger';
 
 import { createSubscribedElement } from '../../UnstatedUtils';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
 
 import AppContainer from '../../../services/AppContainer';
 import MarkDownSettingContainer from '../../../services/MarkDownSettingContainer';
 
 import WhiteListInput from './WhiteListInput';
 
+const logger = loggerFactory('growi:importer');
+
 class XssForm extends React.Component {
 
   constructor(props) {
@@ -18,7 +22,16 @@ class XssForm extends React.Component {
   }
 
   async onClickSubmit() {
-    // TODO GW-303 create apiV3 of update setting
+    const { t } = this.props;
+
+    try {
+      await this.props.markDownSettingContainer.updateXssSetting();
+      toastSuccess(t('markdown_setting.updated_xss'));
+    }
+    catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
   }
 
   xssOptions() {
@@ -82,9 +95,16 @@ class XssForm extends React.Component {
       <React.Fragment>
         <form className="row">
           <div className="form-group">
-            <div className="col-xs-4 text-right">
-              <div className="checkbox checkbox-success" onChange={markDownSettingContainer.switchEnableXss}>
-                <input type="checkbox" id="XssEnable" className="form-check-input" name="isEnabledXss" checked={isEnabledXss} />
+            <div className="col-xs-offset-4 col-xs-4 text-left">
+              <div className="checkbox checkbox-success">
+                <input
+                  type="checkbox"
+                  id="XssEnable"
+                  className="form-check-input"
+                  name="isEnabledXss"
+                  checked={isEnabledXss}
+                  onChange={markDownSettingContainer.switchEnableXss}
+                />
                 <label htmlFor="XssEnable">
                   { t('markdown_setting.Enable XSS prevention') }
                 </label>

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

@@ -12,6 +12,12 @@ export default class MarkDownSettingContainer extends Container {
     this.appContainer = appContainer;
 
     this.state = {
+      isEnabledLinebreaks: appContainer.config.isEnabledLinebreaks,
+      isEnabledLinebreaksInComments: appContainer.config.isEnabledLinebreaksInComments,
+      pageBreakSeparator: appContainer.config.pageBreakSeparator,
+      pageBreakCustomSeparator: appContainer.config.pageBreakCustomSeparator || '',
+      // pageBreakOption: appContainer.config.pageBreakOption,
+      customRegularExpression: appContainer.config.customRegularExpression || '',
       isEnabledXss: (appContainer.config.xssOption != null),
       xssOption: appContainer.config.xssOption,
       tagWhiteList: appContainer.config.tagWhiteList || '',
@@ -28,6 +34,20 @@ export default class MarkDownSettingContainer extends Container {
     return 'MarkDownSettingContainer';
   }
 
+  /**
+   * Switch PageBreakSeparator
+   */
+  switchPageBreakSeparator(pageBreakSeparator) {
+    this.setState({ pageBreakSeparator });
+  }
+
+  /**
+   * Set PageBreakCustomSeparator
+   */
+  setPageBreakCustomSeparator(pageBreakCustomSeparator) {
+    this.setState({ pageBreakCustomSeparator });
+  }
+
   /**
    * Switch enableXss
    */
@@ -38,4 +58,49 @@ export default class MarkDownSettingContainer extends Container {
     this.setState({ isEnabledXss: !this.state.isEnabledXss });
   }
 
+  /**
+   * Update LineBreak Setting
+   */
+  async updateLineBreakSetting() {
+
+    const response = await this.appContainer.apiv3.put('/markdown-setting/lineBreak', {
+      isEnabledLinebreaks: this.state.isEnabledLinebreaks,
+      isEnabledLinebreaksInComments: this.state.isEnabledLinebreaksInComments,
+    });
+
+    return response;
+  }
+
+  /**
+   * Update Xss Setting
+   */
+  async updateXssSetting() {
+
+    const response = await this.appContainer.apiv3.put('/markdown-setting/xss', {
+      isEnabledXss: this.state.isEnabledXss,
+      xssOption: this.state.xssOption,
+      tagWhiteList: this.state.tagWhiteList,
+      attrWhiteList: this.state.attrWhiteList,
+    });
+
+    return response;
+  }
+
+  /**
+   * Update Presentation Setting
+   */
+  async updatePresentationSetting() {
+
+    const response = await this.appContainer.apiv3.put('/markdown-setting/presentation', {
+      pageBreakSeparator: this.state.pageBreakSeparator,
+      pageBreakCustomSeparator: this.state.pageBreakCustomSeparator,
+    });
+
+    this.setState({
+      pageBreakSeparator: response.data.presentationParams.pageBreakSeparator,
+      pageBreakCustomSeparator: response.data.presentationParams.pageBreakCustomSeparator,
+    });
+    return response;
+  }
+
 }

+ 2 - 0
src/server/models/config.js

@@ -176,6 +176,8 @@ module.exports = function(crowi) {
       layoutType: crowi.configManager.getConfig('crowi', 'customize:layout'),
       isEnabledLinebreaks: crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
       isEnabledLinebreaksInComments: crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
+      pageBreakSeparator: crowi.configManager.getConfig('markdown', 'markdown:presentation:pageBreakSeparator'),
+      pageBreakCustomSeparator: crowi.configManager.getConfig('markdown', 'markdown:presentation:pageBreakCustomSeparator'),
       isEnabledXssPrevention: crowi.configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention'),
       isEnabledTimeline: crowi.configManager.getConfig('crowi', 'customize:isEnabledTimeline'),
       xssOption: crowi.configManager.getConfig('markdown', 'markdown:xss:option'),

+ 0 - 39
src/server/routes/admin.js

@@ -137,22 +137,6 @@ module.exports = function(crowi, app) {
     });
   };
 
-  // app.post('/admin/markdown/lineBreaksSetting' , admin.markdown.lineBreaksSetting);
-  actions.markdown.lineBreaksSetting = async function(req, res) {
-
-    const markdownSetting = req.form.markdownSetting;
-
-    if (req.form.isValid) {
-      await configManager.updateConfigsInTheSameNamespace('markdown', markdownSetting);
-      req.flash('successMessage', ['Successfully updated!']);
-    }
-    else {
-      req.flash('errorMessage', req.form.errors);
-    }
-    return res.redirect('/admin/markdown');
-
-  };
-
   // app.post('/admin/markdown/presentationSetting' , admin.markdown.presentationSetting);
   actions.markdown.presentationSetting = async function(req, res) {
     const markdownSetting = req.form.markdownSetting;
@@ -168,29 +152,6 @@ module.exports = function(crowi, app) {
     return res.redirect('/admin/markdown');
   };
 
-  // app.post('/admin/markdown/xss-setting' , admin.markdown.xssSetting);
-  actions.markdown.xssSetting = async function(req, res) {
-    const xssSetting = req.form.markdownSetting;
-
-    xssSetting['markdown:xss:tagWhiteList'] = csvToArray(xssSetting['markdown:xss:tagWhiteList']);
-    xssSetting['markdown:xss:attrWhiteList'] = csvToArray(xssSetting['markdown:xss:attrWhiteList']);
-
-    if (req.form.isValid) {
-      await configManager.updateConfigsInTheSameNamespace('markdown', xssSetting);
-      req.flash('successMessage', ['Successfully updated!']);
-    }
-    else {
-      req.flash('errorMessage', req.form.errors);
-    }
-
-    return res.redirect('/admin/markdown');
-  };
-
-  const csvToArray = (string) => {
-    const array = string.split(',');
-    return array.map((item) => { return item.trim() });
-  };
-
   // app.get('/admin/customize' , admin.customize.index);
   actions.customize = {};
   actions.customize.index = function(req, res) {

+ 251 - 5
src/server/routes/apiv3/markdown-setting.js

@@ -7,19 +7,265 @@ const express = require('express');
 
 const router = express.Router();
 
+const { body } = require('express-validator/check');
+
+const ErrorV3 = require('../../models/vo/error-apiv3');
+
+const validator = {
+  lineBreak: [
+    body('isEnabledLinebreaks').isBoolean(),
+    body('isEnabledLinebreaksInComments').isBoolean(),
+  ],
+  presentationSetting: [
+    body('pageBreakSeparator').isInt().not().isEmpty(),
+  ],
+  xssSetting: [
+    body('isEnabledXss').isBoolean(),
+    body('tagWhiteList').toArray(),
+    body('attrWhiteList').toArray(),
+  ],
+};
+
+
 /**
  * @swagger
  *  tags:
  *    name: MarkDownSetting
  */
 
+/**
+ * @swagger
+ *
+ *  components:
+ *    schemas:
+ *      LineBreakParams:
+ *        type: object
+ *        properties:
+ *          isEnabledLinebreaks:
+ *            type: boolean
+ *            description: enable lineBreak
+ *          isEnabledLinebreaksInComments:
+ *            type: boolean
+ *            description: enable lineBreak in comment
+ *      PresentationParams:
+ *        type: object
+ *        properties:
+ *          pageBreakSeparator:
+ *            type: number
+ *            description: number of pageBreakSeparator
+ *          pageBreakCustomSeparator:
+ *            type: string
+ *            description: string of pageBreakCustomSeparator
+ *      XssParams:
+ *        type: object
+ *        properties:
+ *          isEnabledPrevention:
+ *            type: boolean
+ *            description: enable xss
+ *          xssOption:
+ *            type: number
+ *            description: number of xss option
+ *          tagWhiteList:
+ *            type: array
+ *            description: array of tag whiteList
+ *            items:
+ *              type: string
+ *              description: tag whitelist
+ *          attrWhiteList:
+ *            type: array
+ *            description: array of attr whiteList
+ *            items:
+ *              type: string
+ *              description: attr whitelist
+ */
+
 module.exports = (crowi) => {
-  // const loginRequiredStrictly = require('../../middleware/login-required')(crowi);
-  // const adminRequired = require('../../middleware/admin-required')(crowi);
+  const loginRequiredStrictly = require('../../middleware/login-required')(crowi);
+  const adminRequired = require('../../middleware/admin-required')(crowi);
+  const csrf = require('../../middleware/csrf')(crowi);
+
+  const { ApiV3FormValidator } = crowi.middlewares;
+
+  /**
+   * @swagger
+   *
+   *    /markdown-setting/lineBreak:
+   *      put:
+   *        tags: [MarkDownSetting]
+   *        description: Update lineBreak setting
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schema:
+   *                type: object
+   *                properties:
+   *                  isEnabledLinebreaks:
+   *                    description: enable lineBreak
+   *                    type: boolean
+   *                  isEnabledLinebreaksInComments:
+   *                    description: enable lineBreak in comment
+   *                    type: boolean
+   *        responses:
+   *          200:
+   *            description: Succeeded to update lineBreak setting
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    status:
+   *                      $ref: '#/components/schemas/lineBreakParams'
+   */
+  router.put('/lineBreak', loginRequiredStrictly, adminRequired, csrf, validator.lineBreak, ApiV3FormValidator, async(req, res) => {
+
+    const requestLineBreakParams = {
+      'markdown:isEnabledLinebreaks': req.body.isEnabledLinebreaks,
+      'markdown:isEnabledLinebreaksInComments': req.body.isEnabledLinebreaksInComments,
+    };
+
+    try {
+      await crowi.configManager.updateConfigsInTheSameNamespace('markdown', requestLineBreakParams);
+      const lineBreaksParams = {
+        isEnabledLinebreaks: await crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
+        isEnabledLinebreaksInComments: await crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
+      };
+      return res.apiv3({ lineBreaksParams });
+    }
+    catch (err) {
+      const msg = 'Error occurred in updating lineBreak';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'update-lineBreak-failed'));
+    }
+
+  });
+
+  /**
+   * @swagger
+   *
+   *    /markdown-setting/presentation:
+   *      put:
+   *        tags: [MarkDownSetting]
+   *        description: Update presentation
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schema:
+   *                type: object
+   *                properties:
+   *                  pageBreakSeparator:
+   *                    description: number of pageBreakSeparator
+   *                    type: number
+   *                  pageBreakCustomSeparator:
+   *                    description: string of pageBreakCustomSeparator
+   *                    type: string
+   *        responses:
+   *          200:
+   *            description: Succeeded to update presentation setting
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    status:
+   *                      $ref: '#/components/schemas/presentationParams'
+   */
+  router.put('/presentation', loginRequiredStrictly, adminRequired, csrf, validator.presentationSetting, ApiV3FormValidator, async(req, res) => {
+    if (req.body.pageBreakSeparator === 3 && req.body.pageBreakCustomSeparator === '') {
+      return res.apiv3Err(new ErrorV3('customRegularExpression is required'));
+    }
+
+    const requestPresentationParams = {
+      'markdown:presentation:pageBreakSeparator': req.body.pageBreakSeparator,
+      'markdown:presentation:pageBreakCustomSeparator': req.body.pageBreakCustomSeparator,
+    };
+
+    try {
+      await crowi.configManager.updateConfigsInTheSameNamespace('markdown', requestPresentationParams);
+      const presentationParams = {
+        pageBreakSeparator: await crowi.configManager.getConfig('markdown', 'markdown:presentation:pageBreakSeparator'),
+        pageBreakCustomSeparator: await crowi.configManager.getConfig('markdown', 'markdown:presentation:pageBreakCustomSeparator') || '',
+      };
+      return res.apiv3({ presentationParams });
+    }
+    catch (err) {
+      const msg = 'Error occurred in updating presentation';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'update-presentation-failed'));
+    }
+
+  });
+
+  /**
+   * @swagger
+   *
+   *    /markdown-setting/xss:
+   *      put:
+   *        tags: [MarkDownSetting]
+   *        description: Update xss
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schema:
+   *                type: object
+   *                properties:
+   *                  isEnabledPrevention:
+   *                    description: enable xss
+   *                    type: boolean
+   *                  xssOption:
+   *                    description: number of xss option
+   *                    type: number
+   *                  tagWhiteList:
+   *                    description: array of tag whiteList
+   *                    type: array
+   *                    items:
+   *                      type: string
+   *                      description: tag whitelist
+   *                  attrWhiteList:
+   *                    description: array of attr whiteList
+   *                    type: array
+   *                    items:
+   *                      type: string
+   *                      description: attr whitelist
+   *        responses:
+   *          200:
+   *            description: Succeeded to update xss setting
+   *            content:
+   *              application/json:
+   *                schema:
+   *                  properties:
+   *                    status:
+   *                      $ref: '#/components/schemas/xssParams'
+   */
+  router.put('/xss', loginRequiredStrictly, adminRequired, csrf, validator.xssSetting, ApiV3FormValidator, async(req, res) => {
+    if (req.body.isEnabledXss && req.body.xssOption == null) {
+      return res.apiv3Err(new ErrorV3('xss option is required'));
+    }
+
+    const reqestXssParams = {
+      'markdown:xss:isEnabledPrevention': req.body.isEnabledXss,
+      'markdown:xss:option': req.body.xssOption,
+      'markdown:xss:tagWhiteList': req.body.tagWhiteList,
+      'markdown:xss:attrWhiteList': req.body.attrWhiteList,
+    };
+
+    try {
+      await crowi.configManager.updateConfigsInTheSameNamespace('markdown', reqestXssParams);
+      const xssParams = {
+        isEnabledXss: await crowi.configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention'),
+        xssOption: await crowi.configManager.getConfig('markdown', 'markdown:xss:option'),
+        tagWhiteList: await crowi.configManager.getConfig('markdown', 'markdown:xss:tagWhiteList'),
+        attrWhiteList: await crowi.configManager.getConfig('markdown', 'markdown:xss:attrWhiteList'),
+      };
+      return res.apiv3({ xssParams });
+    }
+    catch (err) {
+      const msg = 'Error occurred in updating xss';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'update-xss-failed'));
+    }
 
-  // const {
-  //   Config,
-  // } = crowi.models;
+  });
 
   return router;
 };

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

@@ -91,10 +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); // TODO delete
-  app.post('/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);
+  app.get('/admin/markdown'                   , loginRequiredStrictly , adminRequired , admin.markdown.index);
 
   // markdown admin
   app.get('/admin/customize'                , loginRequiredStrictly , adminRequired , admin.customize.index);

+ 2 - 248
src/server/views/admin/markdown.html

@@ -6,7 +6,7 @@
 {% block content_header %}
 <div class="header-wrap">
   <header id="page-header">
-    <h1 id="admin-title" class="title">{{ t('Markdown settings') }}</h1>
+    <h1 id="admin-title" class="title">{{ t('Markdown Settings') }}</h1>
   </header>
 </div>
 {% endblock %}
@@ -17,257 +17,11 @@
     <div class="col-md-3">
       {% include './widget/menu.html' with {current: 'markdown'} %}
     </div>
-    <!-- TODO reactify admin -->
-    <div class="col-md-9">
-      {% set smessage = req.flash('successMessage') %}
-      {% if smessage.length %}
-      <div class="alert alert-success">
-        {% for e in smessage %}
-          {{ e }}<br>
-        {% endfor %}
-      </div>
-      {% endif %}
 
-      {% set emessage = req.flash('errorMessage') %}
-      {% if emessage.length %}
-      <div class="alert alert-danger">
-        {% for e in emessage %}
-        {{ e }}<br>
-        {% endfor %}
-      </div>
-      {% endif %}
-
-      <form action="/admin/markdown/lineBreaksSetting" method="post" class="form-horizontal" id="markdownSettingForm" role="form">
-        <fieldset>
-          <legend>{{ t('markdown_setting.line_break_setting') }}</legend>
-          <p class="well">{{ t("markdown_setting.line_break_setting_desc") }}</p>
-
-          <div class="form-group">
-            <label for="markdownSetting[markdown:isEnabledLinebreaks]" class="col-xs-4 control-label">
-              {{ t('markdown_setting.Enable Line Break') }}
-            </label>
-            <div class="col-xs-5">
-              <div class="btn-group btn-toggle" data-toggle="buttons">
-                <label class="btn btn-default btn-rounded btn-outline {% if markdownSetting['markdown:isEnabledLinebreaks'] %}active{% endif %}" data-active-class="primary">
-                  <input name="markdownSetting[markdown:isEnabledLinebreaks]" value="true" type="radio"
-                      {% if true === markdownSetting['markdown:isEnabledLinebreaks'] %}checked{% endif %}> ON
-                </label>
-                <label class="btn btn-default btn-rounded btn-outline {% if !markdownSetting['markdown:isEnabledLinebreaks'] %}active{% endif %}" data-active-class="default">
-                  <input name="markdownSetting[markdown:isEnabledLinebreaks]" value="false" type="radio"
-                      {% if !markdownSetting['markdown:isEnabledLinebreaks'] %}checked{% endif %}> OFF
-                </label>
-              </div>
-              <p class="help-block">{{ t("markdown_setting.Enable Line Break desc") }}</p>
-            </div>
-          </div>
-
-          <div class="form-group">
-            <label for="markdownSetting[markdown:isEnabledLinebreaksInComments]" class="col-xs-4 control-label">
-              {{ t("markdown_setting.Enable Line Break for comment") }}
-            </label>
-            <div class="col-xs-5">
-              <div class="btn-group btn-toggle" data-toggle="buttons">
-                <label class="btn btn-default btn-rounded btn-outline {% if markdownSetting['markdown:isEnabledLinebreaksInComments'] %}active{% endif %}" data-active-class="primary">
-                  <input name="markdownSetting[markdown:isEnabledLinebreaksInComments]" value="true" type="radio"
-                      {% if true === markdownSetting['markdown:isEnabledLinebreaksInComments'] %}checked{% endif %}> ON
-                </label>
-                <label class="btn btn-default btn-rounded btn-outline {% if !markdownSetting['markdown:isEnabledLinebreaksInComments'] %}active{% endif %}" data-active-class="default">
-                  <input name="markdownSetting[markdown:isEnabledLinebreaksInComments]" value="false" type="radio"
-                      {% if !markdownSetting['markdown:isEnabledLinebreaksInComments'] %}checked{% endif %}> OFF
-                </label>
-              </div>
-              <p class="help-block">{{ t("markdown_setting.Enable Line Break for comment desc") }}</p>
-            </div>
-          </div>
-
-          <div class="form-group my-3">
-            <div class="col-xs-offset-4 col-xs-5">
-              <input type="hidden" name="_csrf" value="{{ csrf() }}">
-              <button type="submit" class="btn btn-primary">{{ t("Update") }}</button>
-            </div>
-          </div>
-        </fieldset>
-      </form>
-
-      <form action="/admin/markdown/presentationSetting" method="post" class="form-horizontal" id="markdownSettingForm" role="form">
-        <legend>{{ t('markdown_setting.presentation_setting') }}</legend>
-        <p class="well">{{ t("markdown_setting.presentation_setting_desc") }}</p>
-
-        <fieldset class="form-group row my-2">
-          {% set nameForPageBreakOption = "markdownSetting[markdown:presentation:pageBreakSeparator]" %}
-          {% set pageBreakSeparator = markdownSetting['markdown:presentation:pageBreakSeparator'] %}
-
-          <label class="col-xs-3 control-label">
-            {{ t('markdown_setting.Page break setting') }}
-          </label>
-
-          <div class="col-xs-3 radio radio-primary">
-            <input type="radio" id="pageBreakOption1" name="{{nameForPageBreakOption}}" value="1" {% if pageBreakSeparator === 1 %}checked{% endif %}>
-            <label for="pageBreakOption1">
-              <p class="font-weight-bold">{{ t('markdown_setting.Preset one separator') }}</p>
-              <p class="mt-3">
-                {{ t('markdown_setting.Preset one separator desc') }}
-                <pre><code>{{ t('markdown_setting.Preset one separator value') }}</code></pre>
-              </p>
-            </label>
-          </div>
-
-          <div class="col-xs-3 radio radio-primary">
-            <input type="radio" id="pageBreakOption2" name="{{nameForPageBreakOption}}" value="2" {% if pageBreakSeparator === 2 %}checked{% endif %}>
-            <label for="pageBreakOption2">
-              <p class="font-weight-bold">{{ t('markdown_setting.Preset two separator') }}</p>
-              <p class="mt-3">
-                {{ t('markdown_setting.Preset two separator desc') }}
-                <pre><code>{{ t('markdown_setting.Preset two separator value') }}</code></pre>
-              </p>
-            </label>
-          </div>
-
-          <div class="col-xs-3 radio radio-primary">
-            <input type="radio" id="pageBreakOption3" name="{{nameForPageBreakOption}}" value="3" {% if pageBreakSeparator === 3 %}checked{% endif %}>
-            <label for="pageBreakOption3">
-              <p class="font-weight-bold">{{ t('markdown_setting.Custom separator') }}</p>
-              <p class="mt-3">
-                {{ t('markdown_setting.Custom separator desc') }}
-                <div>
-                  <input class="form-control" name="markdownSetting[markdown:presentation:pageBreakCustomSeparator]" value="{{markdownSetting['markdown:presentation:pageBreakCustomSeparator']|default('') }}">
-                </div>
-              </p>
-            </label>
-          </div>
-
-        </fieldset>
-
-        <div class="form-group my-3">
-          <div class="col-xs-offset-4 col-xs-5">
-            <input type="hidden" name="_csrf" value="{{ csrf() }}">
-            <button type="submit" class="btn btn-primary">{{ t("Update") }}</button>
-          </div>
-        </div>
-      </form>
-
-      <form action="/admin/markdown/xss-setting" method="post" class="form-horizontal" id="markdownSettingForm" role="form">
-        {% set nameForIsXssEnabled = "markdownSetting[markdown:xss:isEnabledPrevention]" %}
-        {% set isXssEnabled = markdownSetting['markdown:xss:isEnabledPrevention'] %}
-
-        <legend>{{ t('markdown_setting.XSS_setting') }}</legend>
-        <p class="well">{{ t("markdown_setting.XSS_setting_desc") }}</p>
-
-        <fieldset class="row">
-          <div class="form-group">
-            <label for="markdownSetting[markdown:isEnabledLinebreaks]" class="col-xs-4 control-label">
-              {{ t('markdown_setting.Enable XSS prevention') }}
-            </label>
-            <div class="col-xs-5">
-              <div class="btn-group btn-toggle" data-toggle="buttons">
-                <label class="btn btn-default btn-rounded btn-outline {% if isXssEnabled %}active{% endif %}" data-active-class="primary">
-                  <input name="{{nameForIsXssEnabled}}" value="true" type="radio"
-                      {% if isXssEnabled %}checked{% endif %}> ON
-                </label>
-                <label class="btn btn-default btn-rounded btn-outline {% if !isXssEnabled %}active{% endif %}" data-active-class="default">
-                  <input name="{{nameForIsXssEnabled}}" value="false" type="radio"
-                      {% if !isXssEnabled %}checked{% endif %}> OFF
-                </label>
-              </div>
-            </div>
-          </div>
-        </fieldset>
-
-        <fieldset class="form-group row my-3" id="xss-hide-when-disabled" {% if !isXssEnabled %}style="display: none;"{% endif %}>
-          {% set nameForXssOption = "markdownSetting[markdown:xss:option]" %}
-          {% set xssOption = markdownSetting['markdown:xss:option'] %}
-
-          <div class="col-xs-4 radio radio-primary">
-            <input type="radio" id="xssOption1" name="{{nameForXssOption}}" value="1" {% if xssOption === 1 %}checked{% endif %}>
-            <label for="xssOption1">
-              <p class="font-weight-bold">{{ t('markdown_setting.Ignore all tags') }}</p>
-              <div class="m-t-15">
-                  {{ t('markdown_setting.Ignore all tags desc') }}
-              </div>
-            </label>
-          </div>
-
-          <div class="col-xs-4 radio radio-primary">
-            <input type="radio" id="xssOption2" name="{{nameForXssOption}}" value="2" {% if xssOption === 2 %}checked{% endif %}>
-            <label for="xssOption2">
-              <p class="font-weight-bold">{{ t('markdown_setting.Recommended setting') }}</p>
-              <div class="m-t-15">
-                {{ t('markdown_setting.Tag names') }}
-                <textarea class="form-control xss-list" name="recommendedTags" rows="6" cols="40" readonly>{{ recommendedWhitelist.tags }}</textarea>
-              </div>
-              <div class="m-t-15">
-                {{ t('markdown_setting.Tag attributes') }}
-                <textarea class="form-control xss-list" name="recommendedAttrs" rows="6" cols="40" readonly>{{ recommendedWhitelist.attrs }}</textarea>
-              </div>
-            </label>
-          </div>
-
-          <div class="col-xs-4 radio radio-primary">
-            <input type="radio" id="xssOption3" name="{{nameForXssOption}}" value="3" {% if xssOption === 3 %}checked{% endif %}>
-            <label for="xssOption3">
-              <p class="font-weight-bold">{{ t('markdown_setting.Custom Whitelist') }}</p>
-              <div class="m-t-15">
-                <div class="d-flex justify-content-between">
-                  {{ t('markdown_setting.Tag names') }}
-                  <p id="btn-import-tags" class="btn btn-xs btn-primary">
-                    {{ t('markdown_setting.import_recommended', 'tags') }}
-                  </p>
-                </div>
-                <textarea class="form-control xss-list" type="text" name="markdownSetting[markdown:xss:tagWhiteList]" rows="6" cols="40" placeholder="e.g. iframe, script, video...">{{ markdownSetting['markdown:xss:tagWhiteList'] }}</textarea>
-              </div>
-              <div class="m-t-15">
-                <div class="d-flex justify-content-between">
-                  {{ t('markdown_setting.Tag attributes') }}
-                  <p id="btn-import-attrs" class="btn btn-xs btn-primary">
-                    {{ t('markdown_setting.import_recommended', 'attributes') }}
-                  </p>
-                </div>
-                <textarea class="form-control xss-list" name="markdownSetting[markdown:xss:attrWhiteList]" rows="6" cols="40" placeholder="e.g. src, id, name...">{{ markdownSetting['markdown:xss:attrWhiteList'] }}</textarea>
-              </div>
-            </label>
-          </div>
-
-        </fieldset>
-
-        <div class="form-group row">
-          <div class="col-xs-12 d-flex justify-content-center">
-            <input type="hidden" name="_csrf" value="{{ csrf() }}">
-            <button type="submit" class="btn btn-primary">{{ t("Update") }}</button>
-          </div>
-        </div>
-
-      </form>
-    </div>
+    <div class="col-md-9" id="admin-markdown-setting"></div>
   </div>
 
 </div>
-
-<script>
-  // give a space between items in textarea(',' => ', ')
-  for (var i = 0; i < $('textarea.xss-list').length; i++) {
-    $($('textarea.xss-list')[i]).val($($('textarea.xss-list')[i]).val().replace(/,/g, ', '));
-  };
-
-  $('input[name="markdownSetting[markdown:xss:isEnabledPrevention]"]').change(function() {
-    if ($(this).val() === 'true') {
-      $('#xss-hide-when-disabled').slideDown();
-    }
-    else {
-      $('#xss-hide-when-disabled').slideUp();
-    }
-  });
-
-  $('#btn-import-tags').on('click', () => {
-    var $tagWhiteList = $('textarea[name="markdownSetting[markdown:xss:tagWhiteList]"]');
-    var $recommendedTagList = $('textarea[name="recommendedTags"]');
-    $tagWhiteList.val($recommendedTagList.val());
-  });
-  $('#btn-import-attrs').on('click', () => {
-    var $attrWhiteList = $('textarea[name="markdownSetting[markdown:xss:attrWhiteList]"]');
-    var $recommendedAttrList = $('textarea[name="recommendedAttrs"]');
-    $attrWhiteList.val($recommendedAttrList.val());
-  });
-</script>
 {% endblock content_main %}
 
 {% block content_footer %}