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

Merge branch 'reactify-admin/CustomizePage' into create-apiV3-update-customizeCss

itizawa 6 лет назад
Родитель
Сommit
bd8e6e072a

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

@@ -681,6 +681,7 @@
     "update_layout_success": "Succeeded to update layout",
     "update_layout_success": "Succeeded to update layout",
     "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_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

@@ -665,6 +665,7 @@
     "update_layout_success": "レイアウトを更新しました",
     "update_layout_success": "レイアウトを更新しました",
     "update_behavior_success": "動作を更新しました",
     "update_behavior_success": "動作を更新しました",
     "update_function_success": "機能を更新しました",
     "update_function_success": "機能を更新しました",
+    "update_highlight_success": "コードハイライトを更新しました",
     "update_customCss_success": "カスタムCSSを更新しました",
     "update_customCss_success": "カスタムCSSを更新しました",
     "update_script_success": "カスタムスクリプトを更新しました",
     "update_script_success": "カスタムスクリプトを更新しました",
     "layout_description":{
     "layout_description":{

+ 38 - 64
src/client/js/app.jsx

@@ -155,45 +155,46 @@ Object.keys(componentMappings).forEach((key) => {
   }
   }
 });
 });
 
 
-const adminCustomizeElem = document.getElementById('admin-customize');
-if (adminCustomizeElem != null) {
-  const adminCustomizeContainer = new AdminCustomizeContainer(appContainer);
-  ReactDOM.render(
-    <Provider inject={[injectableContainers, adminCustomizeContainer]}>
-      <I18nextProvider i18n={i18n}>
-        <Customize />
-      </I18nextProvider>
-    </Provider>,
-    adminCustomizeElem,
-  );
-}
+// create unstated container instance for admin
+const adminCustomizeContainer = new AdminCustomizeContainer(appContainer);
+const adminUsersContainer = new AdminUsersContainer(appContainer);
+const adminExternalAccountsContainer = new AdminExternalAccountsContainer(appContainer);
+const adminMarkDownContainer = new MarkDownSettingContainer(appContainer);
+const adminContainers = {
+  'admin-customize': adminCustomizeContainer,
+  'admin-user-page': adminUsersContainer,
+  'admin-external-account-setting': adminExternalAccountsContainer,
+  'admin-markdown-setting': adminMarkDownContainer,
+  'admin-export-page': websocketContainer,
+};
 
 
-// render for admin
-const adminUsersElem = document.getElementById('admin-user-page');
-if (adminUsersElem != null) {
-  const adminUsersContainer = new AdminUsersContainer(appContainer);
-  ReactDOM.render(
-    <Provider inject={[injectableContainers, adminUsersContainer]}>
-      <I18nextProvider i18n={i18n}>
-        <UserManagement />
-      </I18nextProvider>
-    </Provider>,
-    adminUsersElem,
-  );
-}
+/**
+ * define components
+ *  key: id of element
+ *  value: React Element
+ */
+const adminComponentMappings = {
+  'admin-customize': <Customize />,
+  'admin-user-page': <UserManagement />,
+  'admin-external-account-setting': <ManageExternalAccount />,
+  'admin-markdown-setting': <MarkdownSetting />,
+  'admin-export-page': <ExportArchiveDataPage crowi={appContainer} />,
+};
 
 
-const adminExternalAccountsElem = document.getElementById('admin-external-account-setting');
-if (adminExternalAccountsElem != null) {
-  const adminExternalAccountsContainer = new AdminExternalAccountsContainer(appContainer);
-  ReactDOM.render(
-    <Provider inject={[injectableContainers, adminExternalAccountsContainer]}>
-      <I18nextProvider i18n={i18n}>
-        <ManageExternalAccount />
-      </I18nextProvider>
-    </Provider>,
-    adminExternalAccountsElem,
-  );
-}
+
+Object.keys(adminComponentMappings).forEach((key) => {
+  const adminElem = document.getElementById(key);
+  if (adminElem) {
+    ReactDOM.render(
+      <Provider inject={[injectableContainers, adminContainers[key]]}>
+        <I18nextProvider i18n={i18n}>
+          {adminComponentMappings[key]}
+        </I18nextProvider>
+      </Provider>,
+      adminElem,
+    );
+  }
+});
 
 
 const adminUserGroupDetailElem = document.getElementById('admin-user-group-detail');
 const adminUserGroupDetailElem = document.getElementById('admin-user-group-detail');
 if (adminUserGroupDetailElem != null) {
 if (adminUserGroupDetailElem != null) {
@@ -208,19 +209,6 @@ if (adminUserGroupDetailElem != null) {
   );
   );
 }
 }
 
 
-const adminMarkDownSettingElem = document.getElementById('admin-markdown-setting');
-if (adminMarkDownSettingElem != null) {
-  const markDownSettingContainer = new MarkDownSettingContainer(appContainer);
-  ReactDOM.render(
-    <Provider inject={[injectableContainers, markDownSettingContainer]}>
-      <I18nextProvider i18n={i18n}>
-        <MarkdownSetting />
-      </I18nextProvider>
-    </Provider>,
-    adminMarkDownSettingElem,
-  );
-}
-
 const customHeaderEditorElem = document.getElementById('custom-header-editor');
 const customHeaderEditorElem = document.getElementById('custom-header-editor');
 if (customHeaderEditorElem != null) {
 if (customHeaderEditorElem != null) {
   // get input[type=hidden] element
   // get input[type=hidden] element
@@ -249,20 +237,6 @@ if (adminUserGroupPageElem != null) {
   );
   );
 }
 }
 
 
-const adminExportPageElem = document.getElementById('admin-export-page');
-if (adminExportPageElem != null) {
-  ReactDOM.render(
-    <Provider inject={[appContainer, websocketContainer]}>
-      <I18nextProvider i18n={i18n}>
-        <ExportArchiveDataPage
-          crowi={appContainer}
-        />
-      </I18nextProvider>
-    </Provider>,
-    adminExportPageElem,
-  );
-}
-
 // うわーもうー (commented by Crowi team -- 2018.03.23 Yuki Takei)
 // うわーもうー (commented by Crowi team -- 2018.03.23 Yuki Takei)
 $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
 $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
   ReactDOM.render(
   ReactDOM.render(

+ 6 - 4
src/client/js/components/Admin/Common/AdminDropdownOption.jsx

@@ -18,9 +18,11 @@ class AdminDropdownOption extends React.PureComponent {
       <div className="my-0 btn-group">
       <div className="my-0 btn-group">
         <label>{this.props.label}</label>
         <label>{this.props.label}</label>
         <div className="dropdown">
         <div className="dropdown">
-          <button className="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-            {this.props.selectedValue}
-            <span className="ml-2 caret"></span>
+          <button className="btn btn-default dropdown-toggle w-100" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+            <span className="pull-left">{this.props.selectedValue}</span>
+            <span className="bs-caret pull-right">
+              <span className="caret" />
+            </span>
           </button>
           </button>
           {/* TODO adjust dropdown after BS4 */}
           {/* TODO adjust dropdown after BS4 */}
           <ul className="dropdown-menu" role="menu">
           <ul className="dropdown-menu" role="menu">
@@ -37,7 +39,7 @@ class AdminDropdownOption extends React.PureComponent {
 AdminDropdownOption.propTypes = {
 AdminDropdownOption.propTypes = {
   t: PropTypes.func.isRequired, // i18next
   t: PropTypes.func.isRequired, // i18next
 
 
-  selectedValue: PropTypes.number.isRequired,
+  selectedValue: PropTypes.oneOfType(PropTypes.string, PropTypes.number).isRequired,
   label: PropTypes.string.isRequired,
   label: PropTypes.string.isRequired,
   onChangeValue: PropTypes.func.isRequired,
   onChangeValue: PropTypes.func.isRequired,
   options: PropTypes.array.isRequired,
   options: PropTypes.array.isRequired,

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

@@ -9,6 +9,7 @@ import { createSubscribedElement } from '../../UnstatedUtils';
 import CustomizeLayoutSetting from './CustomizeLayoutSetting';
 import CustomizeLayoutSetting from './CustomizeLayoutSetting';
 import CustomizeBehaviorSetting from './CustomizeBehaviorSetting';
 import CustomizeBehaviorSetting from './CustomizeBehaviorSetting';
 import CustomizeFunctionSetting from './CustomizeFunctionSetting';
 import CustomizeFunctionSetting from './CustomizeFunctionSetting';
+import CustomizeHighlightSetting from './CustomizeHighlightSetting';
 import CustomizeCssSetting from './CustomizeCssSetting';
 import CustomizeCssSetting from './CustomizeCssSetting';
 import CustomizeScriptSetting from './CustomizeScriptSetting';
 import CustomizeScriptSetting from './CustomizeScriptSetting';
 
 
@@ -28,8 +29,9 @@ class Customize extends React.Component {
         <div className="my-3">
         <div className="my-3">
           <CustomizeFunctionSetting />
           <CustomizeFunctionSetting />
         </div>
         </div>
-        <legend>{t('customize_page.Code Highlight')}</legend>
-        {/* コードハイライトフォームの react componentをここで呼ぶ(GW-277) */}
+        <div className="my-3">
+          <CustomizeHighlightSetting />
+        </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">
         <div className="my-3">

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

@@ -0,0 +1,127 @@
+/* eslint-disable no-useless-escape */
+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 AdminDropdownOption from '../Common/AdminDropdownOption';
+
+const logger = loggerFactory('growi:customizeHighlight');
+
+class CustomizeHighlightSetting extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.onClickSubmit = this.onClickSubmit.bind(this);
+  }
+
+  componentDidMount() {
+    // TODO GW-524 fetch highlightTheme option
+  }
+
+  async onClickSubmit() {
+    const { t, adminCustomizeContainer } = this.props;
+
+    try {
+      await adminCustomizeContainer.updateHighlightJsStyle();
+      toastSuccess(t('customize_page.update_highlight_success'));
+    }
+    catch (err) {
+      toastError(err);
+      logger.error(err);
+    }
+  }
+
+  getDemoFunction() {
+    return `function $initHighlight(block, cls) {
+    try {
+
+      if (cls.search(/\bno\-highlight\b/) !== -1) {
+        return \`\${process(block, true, 0x0F)} class="\${cls}"\`;
+      }
+    }
+    catch (e) {
+      /* handle exception */
+    }
+    for (let i = 0 / 2; i < classes.length; i++) {
+      if (checkCondition(classes[i]) === undefined) { console.log('undefined') }
+    }
+  };`;
+  }
+
+  render() {
+    const { t, adminCustomizeContainer } = this.props;
+
+    return (
+      <React.Fragment>
+        <h2>{t('customize_page.Code Highlight')}</h2>
+
+        <div className="form-group row">
+          <div className="col-xs-offset-3 col-xs-6 text-left">
+            <AdminDropdownOption
+              label={t('customize_page.Theme')}
+              selectedValue={adminCustomizeContainer.state.currentHighlightJsStyle}
+              onChangeValue={(value) => { adminCustomizeContainer.switchHighlightJsStyle(value) }}
+              // TODO GW-524 hand over theme option
+              options={[10, 30, 50]}
+            >
+              {/* eslint-disable-next-line react/no-danger */}
+              <p className="help-block text-warning"><span dangerouslySetInnerHTML={{ __html:  t('customize_page.nocdn_desc') }} /></p>
+            </AdminDropdownOption>
+          </div>
+        </div>
+
+        <div className="form-group row">
+          <div className="col-xs-offset-3 col-xs-6 text-left">
+            <div className="checkbox checkbox-success">
+              <input
+                type="checkbox"
+                id="highlightBorder"
+                checked={adminCustomizeContainer.state.isHighlightJsStyleBorderEnabled}
+                onChange={() => { adminCustomizeContainer.switchHighlightJsStyleBorder() }}
+              />
+              <label htmlFor="highlightBorder">
+                <strong>Border</strong>
+              </label>
+            </div>
+          </div>
+        </div>
+
+        <div className="help-block">
+          <label>Examples:</label>
+          <div className="wiki">
+            <pre className={`hljs ${!adminCustomizeContainer.state.isHighlightJsStyleBorderEnabled && 'hljs-no-border'}`}>
+              <code className="highlightjs-demo">
+                {this.getDemoFunction()}
+              </code>
+            </pre>
+          </div>
+        </div>
+
+        <AdminUpdateButtonRow onClick={this.onClickSubmit} />
+      </React.Fragment>
+    );
+  }
+
+}
+
+const CustomizeHighlightSettingWrapper = (props) => {
+  return createSubscribedElement(CustomizeHighlightSetting, props, [AppContainer, AdminCustomizeContainer]);
+};
+
+CustomizeHighlightSetting.propTypes = {
+  t: PropTypes.func.isRequired, // i18next
+  appContainer: PropTypes.instanceOf(AppContainer).isRequired,
+  adminCustomizeContainer: PropTypes.instanceOf(AdminCustomizeContainer).isRequired,
+};
+
+export default withTranslation()(CustomizeHighlightSettingWrapper);

+ 28 - 2
src/client/js/services/AdminCustomizeContainer.js

@@ -19,6 +19,8 @@ export default class AdminCustomizeContainer extends Container {
       isSavedStatesOfTabChanges: appContainer.config.isSavedStatesOfTabChanges,
       isSavedStatesOfTabChanges: appContainer.config.isSavedStatesOfTabChanges,
       isEnabledAttachTitleHeader: appContainer.config.isEnabledAttachTitleHeader,
       isEnabledAttachTitleHeader: appContainer.config.isEnabledAttachTitleHeader,
       currentRecentCreatedLimit: appContainer.config.recentCreatedLimit,
       currentRecentCreatedLimit: appContainer.config.recentCreatedLimit,
+      currentHighlightJsStyle: appContainer.config.highlightJsStyle,
+      isHighlightJsStyleBorderEnabled: appContainer.config.highlightJsStyleBorder,
       currentCustomizeCss: appContainer.config.customizeCss,
       currentCustomizeCss: appContainer.config.customizeCss,
       currentCustomizeScript: appContainer.config.customizeScript,
       currentCustomizeScript: appContainer.config.customizeScript,
     };
     };
@@ -85,6 +87,20 @@ export default class AdminCustomizeContainer extends Container {
     this.setState({ currentRecentCreatedLimit: value });
     this.setState({ currentRecentCreatedLimit: value });
   }
   }
 
 
+  /**
+   * Switch highlightJsStyle
+   */
+  switchHighlightJsStyle(value) {
+    this.setState({ currentHighlightJsStyle: value });
+  }
+
+  /**
+   * Switch highlightJsStyleBorder
+   */
+  switchHighlightJsStyleBorder() {
+    this.setState({ isHighlightJsStyleBorderEnabled: !this.state.isHighlightJsStyleBorderEnabled });
+  }
+
   /**
   /**
    * Change custom css
    * Change custom css
    */
    */
@@ -143,10 +159,20 @@ export default class AdminCustomizeContainer extends Container {
     return customizedParams;
     return customizedParams;
   }
   }
 
 
+  /**
+   * Update code highlight
+   * @memberOf AdminCustomizeContainer
+   * @return {Array} Code highlight
+   */
+  async updateHighlightJsStyle() {
+    // TODO GW-515 create apiV3
+  }
+
+
   /**
   /**
    * Update customCss
    * Update customCss
    * @memberOf AdminCustomizeContainer
    * @memberOf AdminCustomizeContainer
-   * @return {string} css
+   * @return {string} Customize css
    */
    */
   async updateCustomizeCss() {
   async updateCustomizeCss() {
     const response = await this.appContainer.apiv3.put('/customize-setting/customizeCss', {
     const response = await this.appContainer.apiv3.put('/customize-setting/customizeCss', {
@@ -159,7 +185,7 @@ export default class AdminCustomizeContainer extends Container {
   /**
   /**
    * Update customize script
    * Update customize script
    * @memberOf AdminCustomizeContainer
    * @memberOf AdminCustomizeContainer
-   * @return {string} Scripts
+   * @return {string} Customize scripts
    */
    */
   async updateCustomizeScript() {
   async updateCustomizeScript() {
     // TODO GW-538 create apiV3
     // TODO GW-538 create apiV3

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

@@ -182,6 +182,7 @@ module.exports = function(crowi) {
       xssOption: crowi.configManager.getConfig('markdown', 'markdown:xss:option'),
       xssOption: crowi.configManager.getConfig('markdown', 'markdown:xss:option'),
       tagWhiteList: crowi.xssService.getTagWhiteList(),
       tagWhiteList: crowi.xssService.getTagWhiteList(),
       attrWhiteList: crowi.xssService.getAttrWhiteList(),
       attrWhiteList: crowi.xssService.getAttrWhiteList(),
+      highlightJsStyle: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyle'),
       highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
       highlightJsStyleBorder: crowi.configManager.getConfig('crowi', 'customize:highlightJsStyleBorder'),
       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'),