/grafnote

Full CRUD clone of Evernote with Obsidian-inspired styling and layout.

Primary LanguageJavaScript

React badge Express badge PSQL badge Heroku badge React badge

Grafnote link

This website is a MVP clone of evernote.com, with Obsidian-inspired styling & layout. Live website is here.

Main features

  • Notes
    • users can create new Notes (note icon on top left)
    • users can rename Notes (input field in the top line)
    • users can delete Notes (trash can icon on top right)
    • users can assign notes to folders (top line dropdown)
  • Folders
    • users can create new folders (folder icon on top left)
    • users can delete folders (trash can icon next to folder name)
    • users can rename folders (double click on folder name)
      • rename is saved when you unfocus the field (onblur)
  • Markdown editor
    • users can switch between READ and EDIT mode (toggle in top line)
    • in EDIT mode, the text is entered in Markdown format
    • in READ mode, the markdown text is rendered
  • Autosave of note title and note content
    • as the user types the content is automatically patched (with 1 sec debounce)

Feature not implemented (maybe in the future):

  • d3 visualizion of node connections (a graph!)
  • tokenized (ts-vector) search of note content

Design Docs & Database Schema

Can be viewed in the Project Wiki.

Deployment Instructions & Requirements

  • Requires a Postgres installations
  • Run the following commands (these instructions are incomplete)
     git clone https://github.com/ily123/grafnote
     cd grafnote
     npm install
     npm start
    
  • Make sure to create & populate the .env file following the example given in .env.example.

Tech Used

  • Express backend
  • React front-end with Redux state management
  • Vanilla CSS

Code Snippets

  • Example below is of two react functional components: a FileLink and a FolderLink navigation items for the SideBar component.
function FileLink ({ type, payload }) {
  const dispatch = useDispatch();
  return (
    <div
      className={type + '-link'}
      key={type + payload.id}
      onClick={() => dispatch(setActiveNoteId(payload.id))}
    >{payload.title}</div>
  );
}

function FolderLink ({ type, payload }) {
  const dispatch = useDispatch();
  const [title, setTitle] = useState(payload.title);
  const [readOnly, setReadOnly] = useState(true);
  if (!payload || !type) return null;

  const sendNewTitle = () => {
    setReadOnly(true);
    dispatch(editFolderTitle(payload.id, title));
  };

  return (
    <div
      className={type + '-link'}
      key={type + payload.id}
    >
      <i className="fas fa-folder" style={{ paddingRight: '5px' }}></i>
      <input type="text"
        value={title}
        readOnly={readOnly}
        className={readOnly ? 'inactive' : 'active'}
        onDoubleClick={() => setReadOnly(false)}
        onBlur={(e) => sendNewTitle(e.target.value)}
        onChange={(e) => setTitle(e.target.value)}
      ></input>
      <i className="far fa-trash-alt"
        onClick={() => dispatch(deleteFolder(payload.id))}
      ></i>
    </div>
  );
};

Screen shots

  • Main note display (read mode) read note display

  • Main note display (edit mode) Main note display (edit mode)

  • Note navigation sidebar Note navigation sidebar

Contributors

  • Ilya Novikov

Licence

Unlicense (https://en.wikipedia.org/wiki/Unlicense).