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

Merge remote-tracking branch 'origin/reactify-admin/CustomizePage' into create-apiV3-update-codehighlight

# Conflicts:
#	src/server/routes/apiv3/customize-setting.js
itizawa 6 лет назад
Родитель
Сommit
b517b2a5ed

+ 1 - 0
resource/locales/en-US/translation.json

@@ -682,6 +682,7 @@
     "update_behavior_success": "Succeeded to update behavior",
     "update_behavior_success": "Succeeded to update behavior",
     "update_function_success": "Succeeded to update function",
     "update_function_success": "Succeeded to update function",
     "update_highlight_success": "Succeeded to update code highlight",
     "update_highlight_success": "Succeeded to update code highlight",
+    "update_customHeader_success": "Succeeded to update customize html header",
     "update_customCss_success": "Succeeded to update customize css",
     "update_customCss_success": "Succeeded to update customize css",
     "update_script_success": "Succeeded to update custom script",
     "update_script_success": "Succeeded to update custom script",
     "layout_description":{
     "layout_description":{

+ 1 - 0
resource/locales/ja/translation.json

@@ -666,6 +666,7 @@
     "update_behavior_success": "動作を更新しました",
     "update_behavior_success": "動作を更新しました",
     "update_function_success": "機能を更新しました",
     "update_function_success": "機能を更新しました",
     "update_highlight_success": "コードハイライトを更新しました",
     "update_highlight_success": "コードハイライトを更新しました",
+    "update_customHeader_success": "カスタムHTMLヘッダーを更新しました",
     "update_customCss_success": "カスタムCSSを更新しました",
     "update_customCss_success": "カスタムCSSを更新しました",
     "update_script_success": "カスタムスクリプトを更新しました",
     "update_script_success": "カスタムスクリプトを更新しました",
     "layout_description":{
     "layout_description":{

+ 0 - 12
src/client/js/app.jsx

@@ -35,7 +35,6 @@ import UserPictureList from './components/User/UserPictureList';
 import TableOfContents from './components/TableOfContents';
 import TableOfContents from './components/TableOfContents';
 
 
 import UserGroupDetailPage from './components/Admin/UserGroupDetail/UserGroupDetailPage';
 import UserGroupDetailPage from './components/Admin/UserGroupDetail/UserGroupDetailPage';
-import CustomHeaderEditor from './components/Admin/CustomHeaderEditor';
 import MarkdownSetting from './components/Admin/MarkdownSetting/MarkDownSetting';
 import MarkdownSetting from './components/Admin/MarkdownSetting/MarkDownSetting';
 import UserManagement from './components/Admin/UserManagement';
 import UserManagement from './components/Admin/UserManagement';
 import ManageExternalAccount from './components/Admin/ManageExternalAccount';
 import ManageExternalAccount from './components/Admin/ManageExternalAccount';
@@ -209,17 +208,6 @@ if (adminUserGroupDetailElem != null) {
   );
   );
 }
 }
 
 
-const customHeaderEditorElem = document.getElementById('custom-header-editor');
-if (customHeaderEditorElem != null) {
-  // get input[type=hidden] element
-  const customHeaderInputElem = document.getElementById('inputCustomHeader');
-
-  ReactDOM.render(
-    <CustomHeaderEditor inputElem={customHeaderInputElem} />,
-    customHeaderEditorElem,
-  );
-}
-
 const adminUserGroupPageElem = document.getElementById('admin-user-group-page');
 const adminUserGroupPageElem = document.getElementById('admin-user-group-page');
 if (adminUserGroupPageElem != null) {
 if (adminUserGroupPageElem != null) {
   const isAclEnabled = adminUserGroupPageElem.getAttribute('data-isAclEnabled') === 'true';
   const isAclEnabled = adminUserGroupPageElem.getAttribute('data-isAclEnabled') === 'true';

+ 4 - 5
src/client/js/components/Admin/CustomHeaderEditor.jsx

@@ -14,12 +14,10 @@ require('jquery-ui/ui/widgets/resizable');
 export default class CustomHeaderEditor extends React.Component {
 export default class CustomHeaderEditor extends React.Component {
 
 
   render() {
   render() {
-    // get initial value from inputElem
-    const value = this.props.inputElem.value;
 
 
     return (
     return (
       <CodeMirror
       <CodeMirror
-        value={value}
+        value={this.props.value}
         autoFocus
         autoFocus
         options={{
         options={{
           mode: 'htmlmixed',
           mode: 'htmlmixed',
@@ -41,7 +39,7 @@ export default class CustomHeaderEditor extends React.Component {
           });
           });
         }}
         }}
         onChange={(editor, data, value) => {
         onChange={(editor, data, value) => {
-          this.props.inputElem.value = value;
+          this.props.onChange(value);
         }}
         }}
       />
       />
     );
     );
@@ -50,5 +48,6 @@ export default class CustomHeaderEditor extends React.Component {
 }
 }
 
 
 CustomHeaderEditor.propTypes = {
 CustomHeaderEditor.propTypes = {
-  inputElem: PropTypes.object.isRequired,
+  value: PropTypes.string.isRequired,
+  onChange: PropTypes.func.isRequired,
 };
 };

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

@@ -12,6 +12,7 @@ import CustomizeFunctionSetting from './CustomizeFunctionSetting';
 import CustomizeHighlightSetting from './CustomizeHighlightSetting';
 import CustomizeHighlightSetting from './CustomizeHighlightSetting';
 import CustomizeCssSetting from './CustomizeCssSetting';
 import CustomizeCssSetting from './CustomizeCssSetting';
 import CustomizeScriptSetting from './CustomizeScriptSetting';
 import CustomizeScriptSetting from './CustomizeScriptSetting';
+import CustomizeHeaderSetting from './CustomizeHeaderSetting';
 
 
 class Customize extends React.Component {
 class Customize extends React.Component {
 
 
@@ -34,6 +35,9 @@ class Customize extends React.Component {
         </div>
         </div>
         <legend>{t('customize_page.custom_title')}</legend>
         <legend>{t('customize_page.custom_title')}</legend>
         {/* カスタムタイトルフォームの react componentをここで呼ぶ(GW-278) */}
         {/* カスタムタイトルフォームの react componentをここで呼ぶ(GW-278) */}
+        <div className="my-3">
+          <CustomizeHeaderSetting />
+        </div>
         <div className="my-3">
         <div className="my-3">
           <CustomizeCssSetting />
           <CustomizeCssSetting />
         </div>
         </div>

+ 1 - 1
src/client/js/components/Admin/Customize/CustomizeCssSetting.jsx

@@ -51,7 +51,7 @@ class CustomizeCssSetting extends React.Component {
             <CustomCssEditor
             <CustomCssEditor
               // The value passed must be immutable
               // The value passed must be immutable
               value={appContainer.config.customizeCss}
               value={appContainer.config.customizeCss}
-              onChange={(inputValue) => { adminCustomizeContainer.changeCustomCss(inputValue) }}
+              onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeCss(inputValue) }}
             />
             />
           </div>
           </div>
           <div className="col-xs-12">
           <div className="col-xs-12">

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

@@ -0,0 +1,91 @@
+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 AdminCustomizeContainer from '../../../services/AdminCustomizeContainer';
+import AdminUpdateButtonRow from '../Common/AdminUpdateButtonRow';
+import CustomHeaderEditor from '../CustomHeaderEditor';
+
+const logger = loggerFactory('growi:Customize');
+
+class CustomizeHeaderSetting extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.onClickSubmit = this.onClickSubmit.bind(this);
+  }
+
+  async onClickSubmit() {
+    const { t, adminCustomizeContainer } = this.props;
+
+    try {
+      await adminCustomizeContainer.updateCustomizeHeader();
+      toastSuccess(t('customize_page.update_customHeader_success'));
+    }
+    catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
+  }
+
+  render() {
+    const { t, appContainer, adminCustomizeContainer } = this.props;
+
+    return (
+      <React.Fragment>
+        <h2>{t('customize_page.custom_header')}</h2>
+
+        <p
+          className="well"
+          // eslint-disable-next-line react/no-danger
+          dangerouslySetInnerHTML={{ __html: t('customize_page.custom_header_detail', '&lt;header&gt;', '&lt;script&gt;') }}
+        />
+
+        <div className="help-block">
+          { t('Example') }:
+          <pre className="hljs">
+            {/* eslint-disable-next-line react/no-unescaped-entities */}
+            <code>&lt;script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@9.13.0/build/languages/yaml.min.js" defer&gt;&lt;/script&gt;</code>
+          </pre>
+        </div>
+
+        <div className="col-xs-12">
+          <CustomHeaderEditor
+              // The value passed must be immutable
+            value={appContainer.config.customizeHeader}
+            onChange={(inputValue) => { adminCustomizeContainer.changeCustomizeHeader(inputValue) }}
+          />
+        </div>
+        <div className="col-xs-12">
+          <p className="help-block text-right">
+            <i className="fa fa-fw fa-keyboard-o" aria-hidden="true"></i>
+            { t('customize_page.ctrl_space') }
+          </p>
+        </div>
+
+        <AdminUpdateButtonRow onClick={this.onClickSubmit} />
+      </React.Fragment>
+    );
+  }
+
+}
+
+const CustomizeHeaderSettingWrapper = (props) => {
+  return createSubscribedElement(CustomizeHeaderSetting, props, [AppContainer, AdminCustomizeContainer]);
+};
+
+CustomizeHeaderSetting.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminCustomizeContainer: PropTypes.instanceOf(AdminCustomizeContainer).isRequired,
+};
+
+export default withTranslation()(CustomizeHeaderSettingWrapper);

+ 5 - 5
src/client/js/components/Admin/Customize/CustomizeHighlightSetting.jsx

@@ -58,14 +58,14 @@ class CustomizeHighlightSetting extends React.Component {
     const options = adminCustomizeContainer.state.highlightJsCssSelectorOptions;
     const options = adminCustomizeContainer.state.highlightJsCssSelectorOptions;
     const menuItem = [];
     const menuItem = [];
 
 
-    Object.keys(options).forEach((key) => {
-      const styleId = key;
-      const styleName = options[key].name;
-      const isBorderEnable = options[key].border;
+    Object.entries(options).forEach((option) => {
+      const styleId = option[0];
+      const styleName = option[1].name;
+      const isBorderEnable = option[1].border;
 
 
       menuItem.push(
       menuItem.push(
         <li key={styleId} role="presentation" type="button" onClick={() => adminCustomizeContainer.switchHighlightJsStyle(styleId, styleName, isBorderEnable)}>
         <li key={styleId} role="presentation" type="button" onClick={() => adminCustomizeContainer.switchHighlightJsStyle(styleId, styleName, isBorderEnable)}>
-          <a role="menuitem" tabIndex="-1">{styleName}</a>
+          <a role="menuitem">{styleName}</a>
         </li>,
         </li>,
       );
       );
     });
     });

+ 40 - 7
src/client/js/services/AdminCustomizeContainer.js

@@ -29,11 +29,23 @@ export default class AdminCustomizeContainer extends Container {
       currentRecentCreatedLimit: appContainer.config.recentCreatedLimit,
       currentRecentCreatedLimit: appContainer.config.recentCreatedLimit,
       currentHighlightJsStyleId: appContainer.config.highlightJsStyle,
       currentHighlightJsStyleId: appContainer.config.highlightJsStyle,
       isHighlightJsStyleBorderEnabled: appContainer.config.highlightJsStyleBorder,
       isHighlightJsStyleBorderEnabled: appContainer.config.highlightJsStyleBorder,
+      currentCustomizeHeader: appContainer.config.customizeHeader,
       currentCustomizeCss: appContainer.config.customizeCss,
       currentCustomizeCss: appContainer.config.customizeCss,
       currentCustomizeScript: appContainer.config.customizeScript,
       currentCustomizeScript: appContainer.config.customizeScript,
+      /* eslint-disable quote-props, no-multi-spaces */
       highlightJsCssSelectorOptions: {
       highlightJsCssSelectorOptions: {
-
+        'github':           { name: '[Light] GitHub',         border: false },
+        'github-gist':      { name: '[Light] GitHub Gist',    border: true },
+        'atom-one-light':   { name: '[Light] Atom One Light', border: true },
+        'xcode':            { name: '[Light] Xcode',          border: true },
+        'vs':               { name: '[Light] Vs',             border: true },
+        'atom-one-dark':    { name: '[Dark] Atom One Dark',   border: false },
+        'hybrid':           { name: '[Dark] Hybrid',          border: false },
+        'monokai':          { name: '[Dark] Monokai',         border: false },
+        'tomorrow-night':   { name: '[Dark] Tomorrow Night',  border: false },
+        'vs2015':           { name: '[Dark] Vs 2015',         border: false },
       },
       },
+      /* eslint-enable quote-props, no-multi-spaces */
     };
     };
 
 
     this.init();
     this.init();
@@ -53,7 +65,6 @@ export default class AdminCustomizeContainer extends Container {
   async init() {
   async init() {
     // TODO GW-575 fetch data with apiV3
     // TODO GW-575 fetch data with apiV3
     try {
     try {
-      await this.fetchHighLightTheme();
       // search style name from object for display
       // search style name from object for display
       this.setState({ currentHighlightJsStyleName: this.state.highlightJsCssSelectorOptions[this.state.currentHighlightJsStyleId].name });
       this.setState({ currentHighlightJsStyleName: this.state.highlightJsCssSelectorOptions[this.state.currentHighlightJsStyleId].name });
     }
     }
@@ -144,9 +155,16 @@ export default class AdminCustomizeContainer extends Container {
   }
   }
 
 
   /**
   /**
-   * Change custom css
+   * Change customize Html header
+   */
+  changeCustomizeHeader(inputValue) {
+    this.setState({ currentCustomizeHeader: inputValue });
+  }
+
+  /**
+   * Change customize css
    */
    */
-  changeCustomCss(inputValue) {
+  changeCustomizeCss(inputValue) {
     this.setState({ currentCustomizeCss: inputValue });
     this.setState({ currentCustomizeCss: inputValue });
   }
   }
 
 
@@ -215,6 +233,14 @@ export default class AdminCustomizeContainer extends Container {
     return customizedParams;
     return customizedParams;
   }
   }
 
 
+  /**
+   * Update customHeader
+   * @memberOf AdminCustomizeContainer
+   * @return {string} Customize html header
+   */
+  async updateCustomizeHeader() {
+    // TODO GW-601 create apiV3
+  }
 
 
   /**
   /**
    * Update customCss
    * Update customCss
@@ -222,7 +248,11 @@ export default class AdminCustomizeContainer extends Container {
    * @return {string} Customize css
    * @return {string} Customize css
    */
    */
   async updateCustomizeCss() {
   async updateCustomizeCss() {
-    // TODO GW-534 create apiV3
+    const response = await this.appContainer.apiv3.put('/customize-setting/customizeCss', {
+      customizeCss: this.state.currentCustomizeCss,
+    });
+    const { customizedParams } = response.data;
+    return customizedParams;
   }
   }
 
 
   /**
   /**
@@ -231,8 +261,11 @@ export default class AdminCustomizeContainer extends Container {
    * @return {string} Customize scripts
    * @return {string} Customize scripts
    */
    */
   async updateCustomizeScript() {
   async updateCustomizeScript() {
-    // TODO GW-538 create apiV3
+    const response = await this.appContainer.apiv3.put('/customize-setting/customizeScript', {
+      customizeScript: this.state.currentCustomizeScript,
+    });
+    const { customizedParams } = response.data;
+    return customizedParams;
   }
   }
 
 
-
 }
 }

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

@@ -184,6 +184,7 @@ module.exports = function(crowi) {
       attrWhiteList: crowi.xssService.getAttrWhiteList(),
       attrWhiteList: crowi.xssService.getAttrWhiteList(),
       highlightJsStyle: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyle'),
       highlightJsStyle: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyle'),
       highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
       highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
+      customizeHeader: crowi.configManager.getConfig('crowi', 'customize:header'),
       customizeCss: crowi.configManager.getConfig('crowi', 'customize:css'),
       customizeCss: crowi.configManager.getConfig('crowi', 'customize:css'),
       isSavedStatesOfTabChanges: crowi.configManager.getConfig('crowi', 'customize:isSavedStatesOfTabChanges'),
       isSavedStatesOfTabChanges: crowi.configManager.getConfig('crowi', 'customize:isSavedStatesOfTabChanges'),
       isEnabledAttachTitleHeader: crowi.configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader'),
       isEnabledAttachTitleHeader: crowi.configManager.getConfig('crowi', 'customize:isEnabledAttachTitleHeader'),

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

@@ -196,6 +196,7 @@ module.exports = function(crowi, app) {
   actions.customize.index = function(req, res) {
   actions.customize.index = function(req, res) {
     const settingForm = configManager.getConfigByPrefix('crowi', 'customize:');
     const settingForm = configManager.getConfigByPrefix('crowi', 'customize:');
 
 
+    // TODO delete after apiV3
     /* eslint-disable quote-props, no-multi-spaces */
     /* eslint-disable quote-props, no-multi-spaces */
     const highlightJsCssSelectorOptions = {
     const highlightJsCssSelectorOptions = {
       'github':           { name: '[Light] GitHub',         border: false },
       'github':           { name: '[Light] GitHub',         border: false },

+ 150 - 23
src/server/routes/apiv3/customize-setting.js

@@ -18,6 +18,35 @@ const validator = {};
  *    name: CustomizeSetting
  *    name: CustomizeSetting
  */
  */
 
 
+/**
+ * @swagger
+ *
+ *  components:
+ *    schemas:
+ *      CustomizeStatus:
+ *        type: object
+ *        properties:
+ *          layoutType:
+ *            type: string
+ *          themeType:
+ *            type: string
+ *          behaviorType
+ *            type: string
+ *          isEnabledTimeline:
+ *            type: boolean
+ *          isSavedStatesOfTabChanges:
+ *            type: boolean
+ *          isEnabledAttachTitleHeader:
+ *            type: boolean
+ *          recentCreatedLimit:
+ *            type: number
+ *          customizeCss:
+ *            type: string
+ *          customizeScript:
+ *            type: string
+ *          customizeScript:
+ *            type: string
+ */
 module.exports = (crowi) => {
 module.exports = (crowi) => {
   const loginRequiredStrictly = require('../../middleware/login-required')(crowi);
   const loginRequiredStrictly = require('../../middleware/login-required')(crowi);
   const adminRequired = require('../../middleware/admin-required')(crowi);
   const adminRequired = require('../../middleware/admin-required')(crowi);
@@ -44,29 +73,19 @@ module.exports = (crowi) => {
       body('highlightJsStyle').isString(),
       body('highlightJsStyle').isString(),
       body('highlightJsStyleBorder').isBoolean(),
       body('highlightJsStyleBorder').isBoolean(),
     ],
     ],
+    customizeCss: [
+      body('customizeCss').isString(),
+    ],
+    customizeScript: [
+      body('customizeScript').isString(),
+    ],
   };
   };
 
 
   // TODO GW-575 writte swagger
   // TODO GW-575 writte swagger
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
   router.get('/', loginRequiredStrictly, adminRequired, async(req, res) => {
 
 
     // TODO GW-575 return others customize settings
     // TODO GW-575 return others customize settings
-
-    /* eslint-disable quote-props, no-multi-spaces */
-    const highlightJsCssSelectorOptions = {
-      'github':           { name: '[Light] GitHub',         border: false },
-      'github-gist':      { name: '[Light] GitHub Gist',    border: true },
-      'atom-one-light':   { name: '[Light] Atom One Light', border: true },
-      'xcode':            { name: '[Light] Xcode',          border: true },
-      'vs':               { name: '[Light] Vs',             border: true },
-      'atom-one-dark':    { name: '[Dark] Atom One Dark',   border: false },
-      'hybrid':           { name: '[Dark] Hybrid',          border: false },
-      'monokai':          { name: '[Dark] Monokai',         border: false },
-      'tomorrow-night':   { name: '[Dark] Tomorrow Night',  border: false },
-      'vs2015':           { name: '[Dark] Vs 2015',         border: false },
-    };
-    /* eslint-enable quote-props, no-multi-spaces */
-
-    return res.apiv3({ highlightJsCssSelectorOptions });
+    return res.apiv3();
   });
   });
 
 
   /**
   /**
@@ -90,8 +109,14 @@ module.exports = (crowi) => {
    *                    description: type of theme
    *                    description: type of theme
    *                    type: string
    *                    type: string
    *      responses:
    *      responses:
-   *          200:
-   *            description: Succeeded to update layout and theme
+   *        200:
+   *          description: Succeeded to update layout and theme
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  customizedParams:
+   *                    $ref: '#/components/schemas/CustomizeStatus'
    */
    */
   router.put('/layoutTheme', loginRequiredStrictly, adminRequired, csrf, validator.layoutTheme, ApiV3FormValidator, async(req, res) => {
   router.put('/layoutTheme', loginRequiredStrictly, adminRequired, csrf, validator.layoutTheme, ApiV3FormValidator, async(req, res) => {
     const requestParams = {
     const requestParams = {
@@ -132,8 +157,14 @@ module.exports = (crowi) => {
    *                    description: type of behavior
    *                    description: type of behavior
    *                    type: string
    *                    type: string
    *      responses:
    *      responses:
-   *          200:
-   *            description: Succeeded to update behavior
+   *        200:
+   *          description: Succeeded to update behavior
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  customizedParams:
+   *                    $ref: '#/components/schemas/CustomizeStatus'
    */
    */
   router.put('/behavior', loginRequiredStrictly, adminRequired, csrf, validator.behavior, ApiV3FormValidator, async(req, res) => {
   router.put('/behavior', loginRequiredStrictly, adminRequired, csrf, validator.behavior, ApiV3FormValidator, async(req, res) => {
     const requestParams = {
     const requestParams = {
@@ -181,8 +212,14 @@ module.exports = (crowi) => {
    *                    description: limit of recent created
    *                    description: limit of recent created
    *                    type: number
    *                    type: number
    *      responses:
    *      responses:
-   *          200:
-   *            description: Succeeded to update function
+   *        200:
+   *          description: Succeeded to update function
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  customizedParams:
+   *                    $ref: '#/components/schemas/CustomizeStatus'
    */
    */
   router.put('/function', loginRequiredStrictly, adminRequired, csrf, validator.function, ApiV3FormValidator, async(req, res) => {
   router.put('/function', loginRequiredStrictly, adminRequired, csrf, validator.function, ApiV3FormValidator, async(req, res) => {
     const requestParams = {
     const requestParams = {
@@ -254,5 +291,95 @@ module.exports = (crowi) => {
     }
     }
   });
   });
 
 
+  /**
+   * @swagger
+   *
+   *    /customize-setting/customizeCss:
+   *      put:
+   *        tags: [CustomizeSetting]
+   *        description: Update customizeCss
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schama:
+   *                type: object
+   *                properties:
+   *                  customizeCss:
+   *                    description: customize css
+   *                    type: string
+   *      responses:
+   *        200:
+   *          description: Succeeded to update customize css
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  customizedParams:
+   *                    $ref: '#/components/schemas/CustomizeStatus'
+   */
+  router.put('/customize-css', loginRequiredStrictly, adminRequired, csrf, validator.customizeCss, ApiV3FormValidator, async(req, res) => {
+    const requestParams = {
+      'customize:css': req.body.customizeCss,
+    };
+    try {
+      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      const customizedParams = {
+        customizeCss: await crowi.configManager.getConfig('crowi', 'customize:css'),
+      };
+      return res.apiv3({ customizedParams });
+    }
+    catch (err) {
+      const msg = 'Error occurred in updating customizeCss';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'update-customizeCss-failed'));
+    }
+  });
+
+  /**
+   * @swagger
+   *
+   *    /customize-setting/customizeScript:
+   *      put:
+   *        tags: [CustomizeSetting]
+   *        description: Update customizeScript
+   *        requestBody:
+   *          required: true
+   *          content:
+   *            application/json:
+   *              schama:
+   *                type: object
+   *                properties:
+   *                  customizeScript:
+   *                    description: customize script
+   *                    type: string
+   *      responses:
+   *        200:
+   *          description: Succeeded to update customize script
+   *          content:
+   *            application/json:
+   *              schema:
+   *                properties:
+   *                  customizedParams:
+   *                    $ref: '#/components/schemas/CustomizeStatus'
+   */
+  router.put('/customize-script', loginRequiredStrictly, adminRequired, csrf, validator.customizeScript, ApiV3FormValidator, async(req, res) => {
+    const requestParams = {
+      'customize:script': req.body.customizeScript,
+    };
+    try {
+      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
+      const customizedParams = {
+        customizeScript: await crowi.configManager.getConfig('crowi', 'customize:script'),
+      };
+      return res.apiv3({ customizedParams });
+    }
+    catch (err) {
+      const msg = 'Error occurred in updating customizeScript';
+      logger.error('Error', err);
+      return res.apiv3Err(new ErrorV3(msg, 'update-customizeScript-failed'));
+    }
+  });
+
   return router;
   return router;
 };
 };