Skip to content

W-the-V/WhatEverNote

Repository files navigation

PythonFlaskNodejsReactRedux

Whatevernote: An Evernote Clone

Whatevernote is a pixel-perfect clone of Evernote.com, with React Quill. Create sortable notes and notebooks. Customize your creations with the React Quill text editor toolbar.
Explore the docs »

View Site · Report Bug · Request Feature

About this project

Create Notes

Create Notebooks

New notebooks are created with a default first note.

Edit notes

Notes are saved in the database as HTML strings. Images are converted to base64 encoded blob strings and save on the database as well.

Autosave Notes

Using focus and blur, notes are autosaved to the Redux Store and posted to the database upon logout.

Delete Notes

Deleted notes are stored in the trash as a cookie for 30 days.

* Autosave for React Quill - coming soon as a package to npmjs.com

Folder Structure

.
├── dev-requirements.txt
├── requirements.txt            
├── Dockerfile                  # Instructions to create image layer                   
├── Pipfile                     
├── Pifile.lock                  
├── README.md
├── app                         # Python & Flask backend folder
├── react-app                   # React with Redux frontend folder
├── images

Run From Source

Use these commands install and run the development version of Whavernote:

git clone https://github.com/W-the-V/WhatEverNote.git


cd app

flask run

cd ..

cd react-app

npm start

Code Highlights

Notebook API Routes
#------------------------------------------------------------------------------
#                         Notebook Operation Functions
#------------------------------------------------------------------------------

def get_one_notebook(notebook_id):
    notebook = Notebook.query.filter_by(id = notebook_id).first()
    return notebook

def get_all_notebooks(user_id):
    notebooks = Notebook.query.filter_by(user_id = user_id).all()
    return jsonify({"notebooks": [notebook.to_dict() for notebook in notebooks]})

def add_notebook(user_id):
    notebook_data = json.loads(request.data.decode("utf-8"))

    notebook = Notebook(name = notebook_data,
                        user_id = current_user.id)
    
    db.session.add(notebook)
    db.session.commit()
    return jsonify(notebook.to_dict())

def delete_notebook(notebook_id):
    notebook = Notebook.query.filter_by(id = notebook_id).first()
    db.session.delete(notebook)
    db.session.commit()
    return jsonify({"message": "Notebook successfully deleted"})

def edit_notebook(notebook_id):
    edit_notebook_data = json.loads(request.data.decode("utf-8"))
    notebook = get_one_notebook(notebook_id)
    print(edit_notebook_data)
    if notebook.name is not edit_notebook_data["name"]:
        notebook.name = edit_notebook_data["name"]
    if notebook.user_id is not edit_notebook_data["user_id"]:
        notebook.user_id = edit_notebook_data["user_id"]
    
    notebook.default_notebook = edit_notebook_data["default_notebook"]
    
    db.session.commit()
    return jsonify(notebook.to_dict())
    
#------------------------------------------------------------------------------
#                    RESTful Routes -- Notebooks
#------------------------------------------------------------------------------

#get_all
#add_notebook
@notebook_routes.route("/notebooks", methods=['GET', 'POST'])
def get_or_add_notebooks(user_id):
    if request.method == 'GET':
        return get_all_notebooks(user_id)
    elif request.method == 'POST':
        return add_notebook(user_id)

#delete
@notebook_routes.route("/notebooks/<int:notebook_id>", methods = ['DELETE'])
def delete_user_note(user_id, notebook_id):
    return delete_notebook(notebook_id)

#edit
@notebook_routes.route("/notebooks/<int:notebook_id>", methods=['PUT'])
def edit_user_notebook(user_id, notebook_id):
    return edit_notebook(notebook_id)


Tag Model

class Tag(db.Model):
  __tablename__ = 'tags'

  id = db.Column(db.Integer, primary_key=True)
  user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
  name = db.Column(db.String(30), nullable=True)
  notes = db.relationship("Note", back_populates='tags',
                         secondary="notes_to_tags")

  def to_dict(self):
      return {
          "id": self.id,
          "user_id": self.user_id,
          "name": self.name,
      }

  def other_to_dict(self):
      return {
          "id": self.id,
          "user_id": self.user_id,
          "name": self.name,
          "notes": [note.to_dict() for note in self.notes]
      }


Note Redux Store
import * as deepcopy from "deepcopy";
const GET_NOTES = "notes/GET_NOTES";
const REMOVE_NOTE = "notes/REMOVE_NOTE";
const EDIT_NOTE = "notes/EDIT_NOTE";
const ADD_NOTE = "notes/ADD_NOTE";
const SAVE_NOTE = "notes/SAVE_NOTE";

const get = (notes) => ({
 type: GET_NOTES,
 notes,
});

const edit = (note) => ({
 type: EDIT_NOTE,
 note,
});

const add = (note) => ({
 type: ADD_NOTE,
 note,
});

export const saveNote = (note) => ({
 type: SAVE_NOTE,
 note,
});

const remove = (userId, noteId) => ({
 type: REMOVE_NOTE,
 noteId,
 userId,
});


export const getNotes = (userId) => async (dispatch) => {
 const response = await fetch(`/api/user/${userId}/notes`);

 if (response.ok) {
   const notes = await response.json();
   dispatch(get(notes));
 }
};

export const createNote = (data, userId) => async (dispatch) => {
 const response = await fetch(`/api/user/${userId}/notes`, {
   method: "post",
   headers: {
     "Content-Type": "application/json",
   },
   body: JSON.stringify(data),
 });

 if (response.ok) {
   const note = await response.json();
   dispatch(add(note));
   return note;
 }
};

export const editNote = (data) => async (dispatch) => {
 const response = await fetch(`/api/user/${data.user_id}/notes/${data.id}`, {
   method: "put",
   headers: {
     "Content-Type": "application/json",
   },
   body: JSON.stringify(data),
 });

 if (response.ok) {
   const notes = await response.json();
   dispatch(edit(notes));
   return notes;
 }
};

export const deleteNote = (userId, noteId) => async (dispatch) => {
 const response = await fetch(`/api/user/${userId}/notes/${noteId}`, {
   method: "delete",
 });

 if (response.ok) {
   const note = await response.json();
   dispatch(remove(note.id, note.userId));
 }
};

const initialState = {};
let newState;
const notesReducer = (state = {}, action) => {
 switch (action.type) {
   case GET_NOTES: {
     newState = deepcopy(state);
     newState = action.notes;
     return newState;
   }
   case REMOVE_NOTE: {
     const newState = { ...state };
     delete newState[action.noteId];
     return newState;
   }
   case SAVE_NOTE: {
     const newState = deepcopy(state);
     newState.savedNote = action.note;
     return newState;
   }
   case EDIT_NOTE: {
     return action.note
     
   }
   default:
     return state;
 }
};

export default notesReducer;

Contributions? - Autosave Package Coming Soon

Want to contribute to Whatevernote? We're working on an open source release of our autosave functionality for use in all React Quill products. Create an issue to get started.

Database - Flask & SQLAlchemy

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •  

Languages