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

Merge pull request #246 from weseek/feat/theme-selector

Feat/theme selector
Yuki Takei 8 лет назад
Родитель
Сommit
755969077d

+ 2 - 0
CHANGES.md

@@ -3,6 +3,7 @@ CHANGES
 
 ## 2.3.6-RC
 
+* Feature: Theme Selector for Editor
 * Improvement: Remove unportalize button from crowi-plus layout
 * Fix: CSS for admin pages
 
@@ -11,6 +12,7 @@ CHANGES
 * Feature: Enhanced Editor by CodeMirror
 * Feature: Emoji AutoComplete
 * Feature: Add keyboard shortcuts
+* Improvement: Attaching file with Dropzone.js
 * Fix: DOMs that has `.alert-info` class don't be displayed
 * Support: Switch and upgrade libs
     * 8fold-marked -> marked

+ 4 - 0
lib/views/_form.html

@@ -27,6 +27,10 @@
       ファイルを追加 ...
     </button>#}
 
+    <div class="pull-left">
+      <div id="page-editor-theme-selector"></div>
+    </div>
+
     <div class="pull-right form-inline page-form-setting" id="page-form-setting" data-slack-configured="{{ slackConfigured() }}">
       {% if slackConfigured() %}
       <span class="input-group extended-setting">

+ 6 - 0
resource/css/_form.scss

@@ -208,6 +208,12 @@
     border-top: solid 1px #ccc;
     margin-bottom: 0;
   }
+
+  #page-editor-theme-selector .theme-selector {
+    label {
+      margin-right: 0.5em;
+    }
+  }
 } // }}}
 
 .crowi.main-container .main .page-list.content-main { // {{{ Edit Form of Page List

+ 30 - 2
resource/js/app.js

@@ -7,6 +7,7 @@ import CrowiRenderer from './util/CrowiRenderer';
 import HeaderSearchBox  from './components/HeaderSearchBox';
 import SearchPage       from './components/SearchPage';
 import PageEditor       from './components/PageEditor';
+import ThemeSelector    from './components/PageEditor/ThemeSelector';
 import PageListSearch   from './components/PageListSearch';
 import PageHistory      from './components/PageHistory';
 import PageComments     from './components/PageComments';
@@ -76,8 +77,6 @@ const onSaveSuccess = function(page) {
 const componentMappings = {
   'search-top': <HeaderSearchBox crowi={crowi} />,
   'search-page': <SearchPage crowi={crowi} />,
-  'page-editor': <PageEditor crowi={crowi} pageId={pageId} revisionId={pageRevisionId} pagePath={pagePath} markdown={entities.decodeHTML(pageContent)}
-                              onSaveSuccess={onSaveSuccess}/>,
   'page-list-search': <PageListSearch crowi={crowi} />,
   'page-comments-list': <PageComments pageId={pageId} revisionId={pageRevisionId} revisionCreatedAt= {pageRevisionCreatedAt} crowi={crowi} />,
   'page-attachment': <PageAttachment pageId={pageId} pageContent={pageContent} crowi={crowi} />,
@@ -106,6 +105,35 @@ if (elem) {
   ReactDOM.render(<PageCommentFormBehavior crowi={crowi} pageComments={componentInstances['page-comments-list']} />, elem);
 }
 
+/*
+ * PageEditor
+ */
+let pageEditor = null;
+// load editorTheme
+const editorTheme = crowi.loadEditorTheme();
+// render PageEditor
+const pageEditorElem = document.getElementById('page-editor');
+if (pageEditorElem) {
+  pageEditor = ReactDOM.render(
+    <PageEditor crowi={crowi} pageId={pageId} revisionId={pageRevisionId} pagePath={pagePath}
+        markdown={entities.decodeHTML(pageContent)} editorTheme={editorTheme}
+        onSaveSuccess={onSaveSuccess} />,
+    pageEditorElem
+  );
+}
+// render ThemeSelector
+const themeSelectorElem = document.getElementById('page-editor-theme-selector');
+if (themeSelectorElem) {
+  ReactDOM.render(
+    <ThemeSelector value={editorTheme}
+        onChange={(value) => { // set onChange event handler
+          pageEditor.setEditorTheme(value);
+          crowi.saveEditorTheme(value);
+        }} />,
+    themeSelectorElem
+  );
+}
+
 // render for admin
 const customCssEditorElem = document.getElementById('custom-css-editor');
 if (customCssEditorElem != null) {

+ 3 - 1
resource/js/components/HeaderSearchBox/SearchForm.js

@@ -1,5 +1,7 @@
 import React from 'react';
-import { FormGroup, Button, InputGroup } from 'react-bootstrap';
+import FormGroup from 'react-bootstrap/es/FormGroup';
+import Button from 'react-bootstrap/es/Button';
+import InputGroup from 'react-bootstrap/es/InputGroup';
 
 import { AsyncTypeahead } from 'react-bootstrap-typeahead';
 

+ 2 - 1
resource/js/components/PageAttachment/DeleteAttachmentModal.js

@@ -1,5 +1,6 @@
 import React from 'react';
-import { Button, Modal } from 'react-bootstrap';
+import Button from 'react-bootstrap/es/Button';
+import Modal from 'react-bootstrap/es/Modal';
 
 import Icon from '../Common/Icon';
 import User from '../User/User';

+ 3 - 1
resource/js/components/PageComment/DeleteCommentModal.js

@@ -1,7 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
-import { Button, Modal } from 'react-bootstrap';
+import Button from 'react-bootstrap/es/Button';
+import Modal from 'react-bootstrap/es/Modal';
+
 import dateFnsFormat from 'date-fns/format';
 
 import ReactUtils from '../ReactUtils';

+ 14 - 0
resource/js/components/PageEditor.js

@@ -21,6 +21,7 @@ export default class PageEditor extends React.Component {
       markdown: this.props.markdown,
       isUploadable,
       isUploadableFile,
+      editorTheme: this.props.editorTheme,
     };
 
     this.setCaretLine = this.setCaretLine.bind(this);
@@ -60,6 +61,14 @@ export default class PageEditor extends React.Component {
     this.refs.editor.setCaretLine(line);
   }
 
+  /**
+   * set theme (used from the outside)
+   * @param {string} theme theme name
+   */
+  setEditorTheme(theme) {
+    this.setState({ editorTheme: theme });
+  }
+
   /**
    * the change event handler for `markdown` state
    * @param {string} value
@@ -182,6 +191,9 @@ export default class PageEditor extends React.Component {
     return top;
   };
 
+  /*
+   * methods for draft
+   */
   restoreDraft() {
     // restore draft when the first time to edit
     const draft = this.props.crowi.findDraft(this.props.pagePath);
@@ -277,6 +289,7 @@ export default class PageEditor extends React.Component {
           <Editor ref="editor" value={this.state.markdown}
               isUploadable={this.state.isUploadable}
               isUploadableFile={this.state.isUploadableFile}
+              theme={this.state.editorTheme}
               onScroll={this.onEditorScroll}
               onChange={this.onMarkdownChanged}
               onSave={this.onSave}
@@ -298,4 +311,5 @@ PageEditor.propTypes = {
   revisionId: PropTypes.string,
   pagePath: PropTypes.string,
   onSaveSuccess: PropTypes.func,
+  editorTheme: PropTypes.string,
 };

+ 11 - 2
resource/js/components/PageEditor/Editor.js

@@ -21,7 +21,14 @@ require('codemirror/addon/fold/foldgutter.css');
 require('codemirror/addon/fold/markdown-fold');
 require('codemirror/addon/fold/brace-fold');
 require('codemirror/mode/gfm/gfm');
-require('codemirror/theme/eclipse.css');
+
+require('codemirror/theme/elegant.css');
+require('codemirror/theme/neo.css');
+require('codemirror/theme/mdn-like.css');
+require('codemirror/theme/material.css');
+require('codemirror/theme/monokai.css');
+require('codemirror/theme/twilight.css');
+
 
 import Dropzone from 'react-dropzone';
 
@@ -256,6 +263,7 @@ export default class Editor extends React.Component {
       height: 'calc(100% - 20px)'
     }
 
+    const theme = this.props.theme || 'elegant';
     return (
       <div style={flexContainer}>
         <Dropzone
@@ -282,7 +290,7 @@ export default class Editor extends React.Component {
             value={this.state.value}
             options={{
               mode: 'gfm',
-              theme: 'eclipse',
+              theme: theme,
               lineNumbers: true,
               tabSize: 4,
               indentUnit: 4,
@@ -339,6 +347,7 @@ export default class Editor extends React.Component {
 
 Editor.propTypes = {
   value: PropTypes.string,
+  theme: PropTypes.string,
   isUploadable: PropTypes.bool,
   isUploadableFile: PropTypes.bool,
   onChange: PropTypes.func,

+ 57 - 0
resource/js/components/PageEditor/ThemeSelector.js

@@ -0,0 +1,57 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import FormGroup from 'react-bootstrap/es/FormGroup';
+import FormControl from 'react-bootstrap/es/FormControl';
+import ControlLabel from 'react-bootstrap/es/ControlLabel';
+
+export default class ThemeSelector extends React.Component {
+
+  constructor(props) {
+    super(props);
+
+    this.availableThemes = [
+      'elegant', 'neo', 'mdn-like', 'material', 'monokai', 'twilight'
+    ]
+    
+    this.onChange = this.onChange.bind(this);
+  }
+
+  componentDidMount() {
+    this.init(this.props.value || this.availableThemes[0]);
+  }
+
+  init(value) {
+    this.inputEl.value = value;
+  }
+
+  onChange() {
+    if (this.props.onChange != null) {
+      this.props.onChange(this.inputEl.value);
+    }
+  }
+
+  render() {
+    const options = this.availableThemes.map((theme) => {
+      return <option key={theme} value={theme}>{theme}</option>;
+    });
+
+    return (
+      <FormGroup controlId="formControlsSelect" bsClass="theme-selector">
+        <ControlLabel>Theme:</ControlLabel>
+        <FormControl componentClass="select" placeholder="select"
+            onChange={this.onChange}
+            inputRef={ el => this.inputEl=el }>
+
+          {options}
+
+        </FormControl>
+      </FormGroup>
+    )
+  }
+}
+
+ThemeSelector.propTypes = {
+  value: PropTypes.string,
+  onChange: PropTypes.func,
+};

+ 3 - 1
resource/js/components/SearchPage/DeletePageListModal.js

@@ -1,7 +1,9 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 
-import { Button, Modal, Checkbox } from 'react-bootstrap';
+import { Button } from 'react-bootstrap/es/Button';
+import { Modal } from 'react-bootstrap/es/Modal';
+import { Checkbox } from 'react-bootstrap/es/Checkbox';
 
 import ReactUtils from '../ReactUtils';
 

+ 14 - 2
resource/js/util/Crowi.js

@@ -118,11 +118,15 @@ export default class Crowi {
   }
 
   setCaretLine(line) {
-    this.pageEditor.setCaretLine(line);
+    if (this.pageEditor != null) {
+      this.pageEditor.setCaretLine(line);
+    }
   }
 
   focusToEditor() {
-    this.pageEditor.focusToEditor();
+    if (this.pageEditor != null) {
+      this.pageEditor.focusToEditor();
+    }
   }
 
   clearDraft(path) {
@@ -143,6 +147,14 @@ export default class Crowi {
     return null;
   }
 
+  saveEditorTheme(theme) {
+    this.localStorage.setItem('editorTheme', theme);
+  }
+
+  loadEditorTheme() {
+    return this.localStorage.getItem('editorTheme');
+  }
+
   findUserById(userId) {
     if (this.userById && this.userById[userId]) {
       return this.userById[userId];