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

Allow admin to control indent settings

- Preferred indent size
- Disallow users to change indent size
Tatsunori Uchino 5 лет назад
Родитель
Сommit
c6f26925e4

+ 8 - 0
resource/locales/en_US/admin/admin.json

@@ -78,6 +78,14 @@
       "enable_lineBreak_for_comment": "Enable line break in comment",
       "enable_lineBreak_for_comment_desc": "Convert line break in comment to<code>&lt;br&gt;</code>in HTML"
     },
+    "indent_header": "Indent setting",
+    "indent_desc": "You can change indent settings.",
+    "indent_options": {
+      "indentSize": "Default indent size",
+      "indentSize_desc": "You can change the default indent size for the Markdown editor",
+      "disallow_indent_change": "Disallow change of indent size by users",
+      "disallow_indent_change_desc": "You can force users to use a specific indent size."
+    },
     "presentation_header": "Presentation setting",
     "presentation_desc": "You can change presentation settings.",
     "presentation_options": {

+ 8 - 0
resource/locales/ja_JP/admin/admin.json

@@ -78,6 +78,14 @@
       "enable_lineBreak_for_comment": "コメント欄で Line Break を有効にする",
       "enable_lineBreak_for_comment_desc": "コメント中の改行を、HTML内で<code>&lt;br&gt;</code>として扱います"
     },
+    "indent_header": "インデント設定",
+    "indent_desc": "インデントの設定を変更できます。",
+    "indent_options": {
+      "indentSize": "既定のインデント幅",
+      "indentSize_desc": "マークダウンの既定のインデント幅を変更できます。",
+      "disallow_indent_change": "ユーザによるインデント幅変更を許可しない",
+      "disallow_indent_change_desc": "ユーザに単一のインデント幅使用を強制することができます。"
+    },
     "presentation_header": "プレゼンテーション設定",
     "presentation_desc": "プレゼンテーションの設定を変更できます。",
     "presentation_options": {

+ 9 - 1
resource/locales/zh_CN/admin/admin.json

@@ -77,7 +77,15 @@
 			"enable_lineBreak_for_comment": "注释中启用换行符",
 			"enable_lineBreak_for_comment_desc": "HTML中将注释中的换行符转换为<code>&lt;br&gt;</code>"
 		},
-		"presentation_header": "演示文稿设置",
+    "indent_header": "缩进设置",
+    "indent_desc": "您可以更改缩进设置。",
+    "indent_options": {
+      "indentSize": "默认的缩进值",
+      "indentSize_desc": "您可以更改Markdown编辑器的默认的缩进值。",
+      "disallow_indent_change": "不允许用户更改缩进值",
+      "disallow_indent_change_desc": "您可以不允许用户更改缩进值。"
+    },
+    "presentation_header": "演示文稿设置",
 		"presentation_desc": "您可以更改演示文稿设置。",
 		"presentation_options": {
 			"page_break_setting": "分页设置",

+ 112 - 0
src/client/js/components/Admin/MarkdownSetting/IndentForm.jsx

@@ -0,0 +1,112 @@
+/* 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 { withUnstatedContainers } from '../../UnstatedUtils';
+import { toastSuccess, toastError } from '../../../util/apiNotification';
+
+import AppContainer from '../../../services/AppContainer';
+import AdminMarkDownContainer from '../../../services/AdminMarkDownContainer';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
+import PagingSizeUncontrolledDropdown from '../Customize/PagingSizeUncontrolledDropdown';
+
+const logger = loggerFactory('growi:importer');
+
+class IndentForm extends React.Component {
+  constructor(props) {
+    super(props);
+
+    this.onClickSubmit = this.onClickSubmit.bind(this);
+  }
+
+  async onClickSubmit() {
+    const { t } = this.props;
+
+    try {
+      await this.props.adminMarkDownContainer.updateIndentSetting();
+      toastSuccess(t('toaster.update_successed', { target: t('admin:markdown_setting.indent_header') }));
+    } catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
+  }
+
+  renderIndentSizeOption() {
+    const { t, adminMarkDownContainer } = this.props;
+    const { adminPreferredIndentSize } = adminMarkDownContainer.state;
+
+    const helpIndent = { __html: t('admin:markdown_setting.indent_options.indentSize_desc') };
+
+    return (
+      <div className="col">
+        <div className="custom-control custom-checkbox custom-checkbox-success">
+          <PagingSizeUncontrolledDropdown
+            label={t('admin:markdown_setting.indent_options.indentSize')}
+            desc={t('admin:markdown_setting.indent_options.indentSize_desc')}
+            toggleLabel={adminPreferredIndentSize || 4}
+            dropdownItemSize={[2, 4]}
+            onChangeDropdownItem={adminMarkDownContainer.setAdminPreferredIndentSize}
+          />
+        </div>
+        {/* <p className="form-text text-muted" dangerouslySetInnerHTML={helpIndent} /> */}
+      </div>
+    );
+  }
+
+  renderIndentForceOption() {
+    const { t, adminMarkDownContainer } = this.props;
+    const { isIndentSizeForced } = adminMarkDownContainer.state;
+
+    const helpIndentInComment = { __html: t('admin:markdown_setting.indent_options.disallow_indent_change_desc') };
+
+    return (
+      <div className="col">
+        <div className="custom-control custom-checkbox custom-checkbox-success">
+          <input
+            type="checkbox"
+            className="custom-control-input"
+            id="isIndentSizeForced"
+            checked={isIndentSizeForced}
+            onChange={() => {
+              adminMarkDownContainer.setState({ isIndentSizeForced: !isIndentSizeForced });
+            }}
+          />
+          <label className="custom-control-label" htmlFor="isIndentSizeForced">
+            {t('admin:markdown_setting.indent_options.disallow_indent_change')}
+          </label>
+        </div>
+        <p className="form-text text-muted" dangerouslySetInnerHTML={helpIndentInComment} />
+      </div>
+    );
+  }
+
+  render() {
+    const { adminMarkDownContainer } = this.props;
+
+    return (
+      <React.Fragment>
+        <fieldset className="form-group row row-cols-1 row-cols-md-2 mx-3">
+          {this.renderIndentSizeOption()}
+          {this.renderIndentForceOption()}
+        </fieldset>
+        <AdminUpdateButtonRow onClick={this.onClickSubmit} disabled={adminMarkDownContainer.state.retrieveError != null} />
+      </React.Fragment>
+    );
+  }
+}
+
+/**
+ * Wrapper component for using unstated
+ */
+const IndentFormWrapper = withUnstatedContainers(IndentForm, [AppContainer, AdminMarkDownContainer]);
+
+IndentForm.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminMarkDownContainer: PropTypes.instanceOf(AdminMarkDownContainer).isRequired,
+};
+
+export default withTranslation()(IndentFormWrapper);

+ 8 - 0
src/client/js/components/Admin/MarkdownSetting/MarkDownSettingContents.jsx

@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
 import { withTranslation } from 'react-i18next';
 
 import LineBreakForm from './LineBreakForm';
+import IndentForm from './IndentForm';
 import PresentationForm from './PresentationForm';
 import XssForm from './XssForm';
 
@@ -21,6 +22,13 @@ class MarkDownSettingContents extends React.Component {
         </Card>
         <LineBreakForm />
 
+        {/* Indent Setting */}
+        <h2 className="admin-setting-header">{t('admin:markdown_setting.indent_header')}</h2>
+        <Card className="card well my-3">
+          <CardBody className="px-0 py-2">{t('admin:markdown_setting.indent_desc') }</CardBody>
+        </Card>
+        <IndentForm />
+
         {/* Presentation Setting */}
         <h2 className="admin-setting-header">{ t('admin:markdown_setting.presentation_header') }</h2>
         <Card className="card well my-3">

+ 2 - 1
src/client/js/components/PageEditor/OptionsSelector.jsx

@@ -287,7 +287,7 @@ class OptionsSelector extends React.Component {
   }
 
   renderIndentSizeSelector() {
-    const { editorContainer } = this.props;
+    const { appContainer, editorContainer } = this.props;
     const menuItems = this.typicalIndentSizes.map((indent) => {
       return <button key={indent} className="dropdown-item" type="button" onClick={() => this.onChangeIndentSize(indent)}>{indent}</button>;
     });
@@ -304,6 +304,7 @@ class OptionsSelector extends React.Component {
             aria-haspopup="true"
             aria-expanded="false"
             aria-describedby="igt-indent"
+            disabled={appContainer.config.isIndentSizeForced}
           >
             {editorContainer.state.indentSize}
           </button>

+ 22 - 0
src/client/js/services/AdminMarkDownContainer.js

@@ -18,6 +18,8 @@ export default class AdminMarkDownContainer extends Container {
       // set dummy value tile for using suspense
       isEnabledLinebreaks: this.dummyIsEnabledLinebreaks,
       isEnabledLinebreaksInComments: false,
+      adminPreferredIndentSize: 4,
+      isIndentSizeForced: true,
       pageBreakSeparator: 1,
       pageBreakCustomSeparator: '',
       isEnabledXss: false,
@@ -27,6 +29,7 @@ export default class AdminMarkDownContainer extends Container {
     };
 
     this.switchEnableXss = this.switchEnableXss.bind(this);
+    this.setAdminPreferredIndentSize = this.setAdminPreferredIndentSize.bind(this);
   }
 
   /**
@@ -46,6 +49,8 @@ export default class AdminMarkDownContainer extends Container {
     this.setState({
       isEnabledLinebreaks: markdownParams.isEnabledLinebreaks,
       isEnabledLinebreaksInComments: markdownParams.isEnabledLinebreaksInComments,
+      adminPreferredIndentSize: markdownParams.adminPreferredIndentSize,
+      isIndentSizeForced: markdownParams.isIndentSizeForced,
       pageBreakSeparator: markdownParams.pageBreakSeparator,
       pageBreakCustomSeparator: markdownParams.pageBreakCustomSeparator || '',
       isEnabledXss: markdownParams.isEnabledXss,
@@ -55,6 +60,10 @@ export default class AdminMarkDownContainer extends Container {
     });
   }
 
+  setAdminPreferredIndentSize(adminPreferredIndentSize) {
+    this.setState({ adminPreferredIndentSize });
+  }
+
   /**
    * Switch PageBreakSeparator
    */
@@ -92,6 +101,19 @@ export default class AdminMarkDownContainer extends Container {
     return response;
   }
 
+  /**
+   * Update
+   */
+  async updateIndentSetting() {
+
+    const response = await this.appContainer.apiv3.put('/markdown-setting/indent', {
+      adminPreferredIndentSize: this.state.adminPreferredIndentSize,
+      isIndentSizeForced: this.state.isIndentSizeForced,
+    });
+
+    return response;
+  }
+
   /**
    * Update Xss Setting
    */

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

@@ -35,7 +35,7 @@ export default class EditorContainer extends Container {
 
       editorOptions: {},
       previewOptions: {},
-      indentSize: 4,
+      indentSize: this.appContainer.config.adminPreferredIndentSize || 4,
     };
 
     this.isSetBeforeunloadEventHandler = false;

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

@@ -137,6 +137,8 @@ module.exports = function(crowi) {
       'markdown:xss:attrWhiteList': [],
       'markdown:isEnabledLinebreaks': false,
       'markdown:isEnabledLinebreaksInComments': true,
+      'markdown:adminPreferredIndentSize': 4,
+      'markdown:isIndentSizeForced': true,
       'markdown:presentation:pageBreakSeparator': 1,
       'markdown:presentation:pageBreakCustomSeparator': undefined,
     };
@@ -196,6 +198,8 @@ module.exports = function(crowi) {
       themeType: crowi.configManager.getConfig('crowi', 'customize:theme'),
       isEnabledLinebreaks: crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
       isEnabledLinebreaksInComments: crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
+      adminPreferredIndentSize: crowi.configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize'),
+      isIndentSizeForced: crowi.configManager.getConfig('markdown', 'markdown:isIndentSizeForced'),
       pageBreakSeparator: crowi.configManager.getConfig('markdown', 'markdown:presentation:pageBreakSeparator'),
       pageBreakCustomSeparator: crowi.configManager.getConfig('markdown', 'markdown:presentation:pageBreakCustomSeparator'),
       isEnabledXssPrevention: crowi.configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention'),

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

@@ -15,6 +15,10 @@ const validator = {
     body('isEnabledLinebreaks').isBoolean(),
     body('isEnabledLinebreaksInComments').isBoolean(),
   ],
+  indent: [
+    body('adminPreferredIndentSize').isInt({ max: 4, min: 2 }),
+    body('isIndentSizeForced').isBoolean(),
+  ],
   presentationSetting: [
     body('pageBreakSeparator').isInt().not().isEmpty(),
   ],
@@ -111,6 +115,8 @@ module.exports = (crowi) => {
     const markdownParams = {
       isEnabledLinebreaks: await crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaks'),
       isEnabledLinebreaksInComments: await crowi.configManager.getConfig('markdown', 'markdown:isEnabledLinebreaksInComments'),
+      adminPreferredIndentSize: await crowi.configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize'),
+      isIndentSizeForced: await crowi.configManager.getConfig('markdown', 'markdown:isIndentSizeForced'),
       pageBreakSeparator: await crowi.configManager.getConfig('markdown', 'markdown:presentation:pageBreakSeparator'),
       pageBreakCustomSeparator: await crowi.configManager.getConfig('markdown', 'markdown:presentation:pageBreakCustomSeparator'),
       isEnabledXss: await crowi.configManager.getConfig('markdown', 'markdown:xss:isEnabledPrevention'),
@@ -168,6 +174,29 @@ module.exports = (crowi) => {
 
   });
 
+  router.put('/indent', loginRequiredStrictly, adminRequired, csrf, validator.indent, apiV3FormValidator, async(req, res) => {
+
+    const requestIndentParams = {
+      'markdown:adminPreferredIndentSize': req.body.adminPreferredIndentSize,
+      'markdown:isIndentSizeForced': req.body.isIndentSizeForced,
+    };
+
+    try {
+      await crowi.configManager.updateConfigsInTheSameNamespace('markdown', requestIndentParams);
+      const indentParams = {
+        adminPreferredIndentSize: await crowi.configManager.getConfig('markdown', 'markdown:adminPreferredIndentSize'),
+        isIndentSizeForced: await crowi.configManager.getConfig('markdown', 'markdown:isIndentSizeForced'),
+      };
+      return res.apiv3({ indentParams });
+    }
+    catch (err) {
+      const msg = 'Error occurred in updating indent';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'update-indent-failed'));
+    }
+
+  });
+
   /**
    * @swagger
    *