Introduce note editing, allow deleting note.
authorAlexandre Segura <mex.zktk@gmail.com>
Fri, 23 Jun 2017 10:16:49 +0200
changeset 79 772b73e31069
parent 78 49c5ea36d0a4
child 80 b3a02ea6d097
Introduce note editing, allow deleting note.
client/src/App.scss
client/src/actions/notesActions.js
client/src/components/Note.js
client/src/components/NoteInput.js
client/src/components/SlateEditor.js
client/src/constants/actionTypes.js
client/src/reducers/notesReducer.js
--- a/client/src/App.scss	Thu Jun 22 12:37:53 2017 +0200
+++ b/client/src/App.scss	Fri Jun 23 10:16:49 2017 +0200
@@ -132,22 +132,28 @@
 .note {
     display: flex;
     position: relative;
-    padding-left: 70px;
+    padding: 10px 10px 10px 80px;
     margin-bottom: 20px;
-    min-height: ($line-height-computed * 3);
+    cursor: pointer;
+    min-height: ($line-height-computed * 4);
+    border: 1px solid transparent;
 
     &:before {
         content: "";
         position: absolute;
         top: 0;
         bottom: 0;
-        left: 27px;
+        left: 37px;
         z-index: -1;
         display: block;
         width: 2px;
         background-color: #e6ebf1;
     }
 
+    &:hover {
+        border: 1px solid #efefef;
+    }
+
     .start, .finish {
         position: absolute;
         background-color: #fff;
@@ -156,10 +162,12 @@
     .start {
         top: 0;
         left: 0;
+        padding: 10px 0 0 10px;
     }
     .finish {
         bottom: 0;
         left: 0;
+        padding: 0 0 10px 10px;
     }
 
     &-content {
--- a/client/src/actions/notesActions.js	Thu Jun 22 12:37:53 2017 +0200
+++ b/client/src/actions/notesActions.js	Fri Jun 23 10:16:49 2017 +0200
@@ -43,3 +43,10 @@
     }
   };
 }
+
+export const deleteNote = (note) => {
+  return {
+    type: types.DELETE_NOTE,
+    note
+  };
+}
--- a/client/src/components/Note.js	Thu Jun 22 12:37:53 2017 +0200
+++ b/client/src/components/Note.js	Fri Jun 23 10:16:49 2017 +0200
@@ -1,22 +1,80 @@
-import React from 'react';
+import React, { Component } from 'react';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
 import PropTypes from 'prop-types';
-import {formatTimestamp} from '../utils';
+import { formatTimestamp } from '../utils';
+import SlateEditor from './SlateEditor';
+import * as notesActions from '../actions/notesActions';
+
+class Note extends Component {
+
+  state = {
+    edit: false
+  }
+
+  toggleEditMode = () => {
+    const { edit } = this.state;
+    this.setState({ edit: !edit })
+  }
+
+  onClickDelete = (e) => {
+    e.preventDefault();
+    e.stopPropagation();
+
+    this.props.notesActions.deleteNote(this.props.note);
+  }
 
-const Note = ({note}) => {
-  return (
-    <div id={"note-" + note._id} className="note">
-      <span className="start">{formatTimestamp(note.startedAt)}</span>
-      <span className="finish">{formatTimestamp(note.finishedAt)}</span>
-      <div className="note-content" dangerouslySetInnerHTML={{ __html: note.html }} />
-      <div className="note-margin-comment">
-        <small className="text-muted">{ note.marginComment }</small>
+  renderNoteContent() {
+    if (this.state.edit) {
+      return (
+        <div className="note-content">
+          <SlateEditor ref="editor"
+            onChange={this.onEditorChange}
+            onEnterKeyDown={this.onAddNoteClick}
+            onButtonClick={this.onAddNoteClick}
+            onCheckboxChange={this.onCheckboxChange}
+            isChecked={this.props.autoSubmit}
+            isButtonDisabled={this.state.buttonDisabled}
+            withButtons={false}
+            note={this.props.note} />
+        </div>
+      )
+    }
+
+    return (
+      <div className="note-content" dangerouslySetInnerHTML={{ __html: this.props.note.html }} />
+    )
+  }
+
+  render() {
+    return (
+      <div id={"note-" + this.props.note._id} className="note" onClick={ this.toggleEditMode }>
+        <span className="start">{ formatTimestamp(this.props.note.startedAt) }</span>
+        <span className="finish">{ formatTimestamp(this.props.note.finishedAt) }</span>
+        { this.renderNoteContent() }
+        <div className="note-margin-comment">
+          <small className="text-muted">{ this.props.note.marginComment }</small>
+        </div>
+        <a onClick={this.onClickDelete}>
+          <span className="material-icons">delete</span>
+        </a>
       </div>
-    </div>
-  );
-};
+    );
+  };
+}
 
 Note.propTypes = {
   note: PropTypes.object.isRequired
 };
 
-export default Note;
+function mapStateToProps(state, props) {
+  return props;
+}
+
+function mapDispatchToProps(dispatch) {
+  return {
+    notesActions: bindActionCreators(notesActions, dispatch),
+  }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(Note);
--- a/client/src/components/NoteInput.js	Thu Jun 22 12:37:53 2017 +0200
+++ b/client/src/components/NoteInput.js	Fri Jun 23 10:16:49 2017 +0200
@@ -68,7 +68,8 @@
               onButtonClick={this.onAddNoteClick}
               onCheckboxChange={this.onCheckboxChange}
               isChecked={this.props.autoSubmit}
-              isButtonDisabled={this.state.buttonDisabled} />
+              isButtonDisabled={this.state.buttonDisabled}
+              withButtons={true} />
           </div>
           <div className="editor-right">
             <FormControl
--- a/client/src/components/SlateEditor.js	Thu Jun 22 12:37:53 2017 +0200
+++ b/client/src/components/SlateEditor.js	Fri Jun 23 10:16:49 2017 +0200
@@ -77,7 +77,7 @@
     plugins.push(annotationPlugin);
 
     this.state = {
-      state: Plain.deserialize(''),
+      state: props.note ? Raw.deserialize(props.note.raw) : Plain.deserialize(''),
       startedAt: null,
       finishedAt: null,
       currentSelectionText: '',
@@ -420,18 +420,31 @@
 
         {this.renderBlockButton('numbered-list', 'format_list_numbered')}
         {this.renderBlockButton('bulleted-list', 'format_list_bulleted')}
-        <div>
-          <div className="checkbox">
-            <label>
-              <input type="checkbox" checked={this.props.isChecked} onChange={this.onCheckboxChange} /> <kbd>Enter</kbd> = add note
-            </label>
-          </div>
-          <Button bsStyle="primary" disabled={this.props.isButtonDisabled} onClick={this.onButtonClick}>Add note</Button>
-        </div>
+
+        {this.renderToolbarButtons()}
       </div>
     )
   }
 
+  renderToolbarButtons = () => {
+    if (!this.props.withButtons) {
+      return (
+        <div />
+      )
+    }
+
+    return (
+      <div>
+        <div className="checkbox">
+          <label>
+            <input type="checkbox" checked={this.props.isChecked} onChange={this.onCheckboxChange} /> <kbd>Enter</kbd> = add note
+          </label>
+        </div>
+        <Button bsStyle="primary" disabled={this.props.isButtonDisabled} onClick={this.onButtonClick}>Add note</Button>
+      </div>
+    );
+  }
+
   /**
    * Render a mark-toggling toolbar button.
    *
--- a/client/src/constants/actionTypes.js	Thu Jun 22 12:37:53 2017 +0200
+++ b/client/src/constants/actionTypes.js	Fri Jun 23 10:16:49 2017 +0200
@@ -1,6 +1,7 @@
 export const NOOP = 'NOOP';
 
 export const ADD_NOTE = 'ADD_NOTE';
+export const DELETE_NOTE = 'DELETE_NOTE';
 
 export const CREATE_SESSION = 'CREATE_SESSION';
 export const UPDATE_SESSION = 'UPDATE_SESSION';
--- a/client/src/reducers/notesReducer.js	Thu Jun 22 12:37:53 2017 +0200
+++ b/client/src/reducers/notesReducer.js	Fri Jun 23 10:16:49 2017 +0200
@@ -6,6 +6,9 @@
   switch (action.type) {
     case types.ADD_NOTE:
       return state.push(new NoteRecord(action.note));
+    case types.DELETE_NOTE:
+      const noteIndex = state.findIndex((note) => note.get('_id') === action.note.get('_id'));
+      return state.delete(noteIndex);
     default:
       return state;
   }