├── edit.html ├── index.html ├── scripts ├── notes-app.js ├── notes-edit.js ├── notes-functions.js └── uuid.js └── styles └── styles.css /edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Edit Note 9 | 10 | 11 |
12 |
13 |

Notes App

14 |

Take notes and never forget

15 |
16 |
17 |
18 |
19 | Home 20 |
21 |
22 |
23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Notes App 9 | 10 | 11 |
12 |
13 |

Notes App

14 |

Take notes and never forget

15 |
16 |
17 |
18 |
19 | 20 | 25 |
26 |
27 |
28 |
29 | 30 |
31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /scripts/notes-app.js: -------------------------------------------------------------------------------- 1 | let notes = getSavedNotes(); 2 | const timeStamp = moment().valueOf(); 3 | const filters = { 4 | searchText: '', 5 | sortBy: 'byEdited' 6 | }; 7 | 8 | renderNotes(notes, filters); 9 | 'use strict' 10 | 11 | document.querySelector('#create-note').addEventListener('click', () => { 12 | const id = uuidv4(); 13 | notes.push({ 14 | id: id, 15 | title: '', 16 | body: '', 17 | createdAt: timeStamp, 18 | updatedAt: timeStamp, 19 | }); 20 | saveNotes(notes); 21 | location.assign(`./edit.html#${id}`); 22 | }); 23 | 24 | document.querySelector('#search-text').addEventListener('input', (e) => { 25 | filters.searchText = e.target.value; 26 | renderNotes(notes, filters); 27 | }); 28 | 29 | document.querySelector('#filter-by').addEventListener('change', (e) => { 30 | filters.sortBy = e.target.value; 31 | renderNotes(notes, filters); 32 | }) 33 | 34 | window.addEventListener('storage', (e) => { 35 | if (e.key === 'notes'){ 36 | notes = JSON.parse(e.newValue); 37 | renderNotes(notes, filters); 38 | } 39 | }) 40 | -------------------------------------------------------------------------------- /scripts/notes-edit.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const titleElement = document.querySelector('#note-title'); 4 | const timeElement = document.querySelector('#time-stamp'); 5 | const bodyElement = document.querySelector('#note-body'); 6 | const noteId = location.hash.substr(1); 7 | let notes = getSavedNotes(); 8 | let note = notes.find( (note) => note.id === noteId); 9 | 10 | if (!note){ 11 | location.assign('./index.html'); 12 | } 13 | 14 | //Get the existing note's info from the page 15 | timeElement.textContent = generateLastEdited(note.updatedAt); 16 | titleElement.value = note.title; 17 | bodyElement.value = note.body; 18 | 19 | titleElement.addEventListener('input', () => { 20 | note.title = titleElement.value; 21 | note.updatedAt = moment().valueOf(); 22 | timeElement.textContent = generateLastEdited(note.updatedAt); 23 | saveNotes(notes); 24 | }) 25 | 26 | bodyElement.addEventListener('input', () => { 27 | note.body = bodyElement.value; 28 | note.updatedAt = moment().valueOf(); 29 | timeElement.textContent = generateLastEdited(note.updatedAt); 30 | saveNotes(notes); 31 | }) 32 | 33 | document.querySelector('#remove-note').addEventListener('click', () =>{ 34 | removeNote(note.id); 35 | saveNotes(notes); 36 | location.assign('./index.html'); 37 | }) 38 | 39 | window.addEventListener('storage', (e) =>{ 40 | if (e.key === 'notes'){ 41 | notes = JSON.parse(e.newValue); 42 | note = notes.find( (note) => note.id === noteId); 43 | 44 | if (!note){ 45 | location.assign('./index.html'); 46 | } 47 | timeElement.textContent = `Last edited ${moment(note.updatedAt).fromNow()}`; 48 | titleElement.value = note.title; 49 | bodyElement.value = note.body; 50 | } 51 | }) 52 | 53 | -------------------------------------------------------------------------------- /scripts/notes-functions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Read exisiting notes from local storage 4 | const getSavedNotes = () => { 5 | const notesJSON = localStorage.getItem('notes'); 6 | 7 | try{ 8 | return notesJSON ? JSON.parse(notesJSON) : []; 9 | } catch (e){ 10 | return []; 11 | } 12 | } 13 | 14 | // Save notes to localStorage 15 | const saveNotes = (notes) => { 16 | localStorage.setItem('notes', JSON.stringify(notes)); 17 | } 18 | //remove notes by id 19 | const removeNote = (id) => { 20 | const index = notes.findIndex((note) => note.id === id) 21 | 22 | if (index > -1) { 23 | notes.splice(index,1); 24 | } 25 | } 26 | 27 | // Generate the DOM structure for a note 28 | const generateNoteDOM = (note) => { 29 | const noteEl = document.createElement('a'); 30 | const textEl = document.createElement('p'); 31 | const statusEl = document.createElement('p') 32 | 33 | // Setup the note title text 34 | if (note.title.length > 0){ 35 | textEl.textContent = note.title; 36 | } else { 37 | textEl.textContent = 'Unnamed note'; 38 | } 39 | textEl.classList.add('list-item__title') 40 | noteEl.appendChild(textEl); 41 | 42 | //Setup the link 43 | noteEl.setAttribute('href', `./edit.html#${note.id}`) 44 | noteEl.classList.add('list-item') 45 | 46 | //Setup the status message 47 | statusEl.textContent = generateLastEdited(note.updatedAt) 48 | statusEl.classList.add('list-item__subtitle') 49 | noteEl.appendChild(statusEl) 50 | 51 | 52 | return noteEl; 53 | } 54 | 55 | const sortNotes = (notes, sortBy) => { 56 | if (sortBy === 'byEdited'){ 57 | return notes.sort((a,b) => { 58 | if (a.updatedAt > b.updatedAt){ 59 | return -1; 60 | } else if (a.updatedAt < b.updatedAt){ 61 | return 1; 62 | } else { 63 | return 0; 64 | } 65 | }) 66 | } else if (sortBy === 'byCreated') { 67 | return notes.sort( (a,b) => { 68 | if (a.createdAt > b.createdAt){ 69 | return -1; 70 | } else if (a.createdAt < b.createdAt){ 71 | return 1; 72 | } else { 73 | return 0; 74 | } 75 | }) 76 | } else if (sortBy === 'alphabetical'){ 77 | return notes.sort( (a,b) => { 78 | if (a.title.toLowerCase() < b.title.toLowerCase()){ 79 | return -1; 80 | } else if (a.title.toLowerCase() > b.title.toLowerCase()){ 81 | return 1; 82 | } else { 83 | return 0; 84 | } 85 | }) 86 | } else { 87 | return notes; 88 | } 89 | } 90 | 91 | // Render application notes 92 | const renderNotes = (notes, filters) => { 93 | const notesEl = document.querySelector('#notes') 94 | notes = sortNotes(notes, filters.sortBy); 95 | const filteredNotes = notes.filter( (note) => { 96 | const title = note.title.toLowerCase(); 97 | const filter = filters.searchText.toLowerCase(); 98 | return title.includes(filter) // || body.includes(filter); 99 | }) 100 | 101 | notesEl.innerHTML = ''; 102 | 103 | if (filteredNotes.length > 0){ 104 | filteredNotes.forEach( (note) => { 105 | const p = generateNoteDOM(note); 106 | notesEl.appendChild(p); 107 | }) 108 | } else { 109 | const emptyMessage = document.createElement('p') 110 | emptyMessage.textContent = 'No notes to show' 111 | emptyMessage.classList.add('empty-message') 112 | notesEl.appendChild(emptyMessage) 113 | } 114 | }; 115 | 116 | // Generate the last edited message 117 | const generateLastEdited = (timestamp) => `Last edited ${moment(timestamp).fromNow()}`; -------------------------------------------------------------------------------- /scripts/uuid.js: -------------------------------------------------------------------------------- 1 | !function(r){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=r();else if("function"==typeof define&&define.amd)define([],r);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.uuidv4=r()}}(function(){return function r(e,n,t){function o(f,u){if(!n[f]){if(!e[f]){var a="function"==typeof require&&require;if(!u&&a)return a(f,!0);if(i)return i(f,!0);var d=new Error("Cannot find module '"+f+"'");throw d.code="MODULE_NOT_FOUND",d}var p=n[f]={exports:{}};e[f][0].call(p.exports,function(r){var n=e[f][1][r];return o(n?n:r)},p,p.exports,r,e,n,t)}return n[f].exports}for(var i="function"==typeof require&&require,f=0;f>>((3&e)<<3)&255;return i}}},{}],3:[function(r,e,n){function t(r,e,n){var t=e&&n||0;"string"==typeof r&&(e="binary"===r?new Array(16):null,r=null),r=r||{};var f=r.random||(r.rng||o)();if(f[6]=15&f[6]|64,f[8]=63&f[8]|128,e)for(var u=0;u<16;++u)e[t+u]=f[u];return e||i(f)}var o=r("./lib/rng"),i=r("./lib/bytesToUuid");e.exports=t},{"./lib/bytesToUuid":1,"./lib/rng":2}]},{},[3])(3)}); -------------------------------------------------------------------------------- /styles/styles.css: -------------------------------------------------------------------------------- 1 | /* Base Styles */ 2 | 3 | * { 4 | box-sizing: border-box; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | html { 10 | font-size: 62.5%; 11 | } 12 | 13 | body { 14 | color: #333333; 15 | font-family: Helvetica, Arial, sans-serif; 16 | font-size: 1.6rem; 17 | } 18 | 19 | button { 20 | cursor: pointer; 21 | } 22 | 23 | a { 24 | color: #396684; 25 | } 26 | 27 | /* Container */ 28 | 29 | .container { 30 | max-width: 60rem; 31 | margin: 0 auto; 32 | padding: 0 1.6rem; 33 | } 34 | 35 | /* Header */ 36 | 37 | .header { 38 | background: #43799c; 39 | color: white; 40 | padding: 1.6rem 0; 41 | } 42 | 43 | .header__title { 44 | font-size: 3.2rem; 45 | margin-bottom: .4rem; 46 | } 47 | 48 | .header__subtitle { 49 | font-size: 1.6rem; 50 | font-weight: 300; 51 | } 52 | 53 | /* Actions Bar */ 54 | 55 | .actions { 56 | background-color: #F7F7F7; 57 | border-bottom: 1px solid #dedfe0; 58 | padding: .8rem 59 | } 60 | 61 | .actions__container { 62 | align-items: center; 63 | display: flex; 64 | max-width: 60rem; 65 | margin: 0 auto; 66 | min-height: 3rem; 67 | padding: 0 1.6rem; 68 | } 69 | 70 | .actions__container--spaced { 71 | justify-content: space-between 72 | } 73 | 74 | /* Form Inputs */ 75 | 76 | .input { 77 | border: 1px solid #DEDFE0; 78 | border-radius: 5px; 79 | font-size: 1.4rem; 80 | font-weight: 300; 81 | height: 3rem; 82 | margin-right: .8rem; 83 | padding: .4rem .8rem; 84 | } 85 | 86 | .dropdown { 87 | border-radius: 0; 88 | border: 1px solid #DEDFE0; 89 | background: white; 90 | font-size: 1.4rem; 91 | font-weight: 300; 92 | height: 3rem; 93 | margin-right: .8rem; 94 | } 95 | 96 | .button { 97 | background: #43799c; 98 | border: none; 99 | border-bottom: 2px solid #396684; 100 | color: white; 101 | font-size: 1.4rem; 102 | font-weight: 300; 103 | padding: .8rem; 104 | transition: background .3s ease; 105 | } 106 | 107 | .button:hover { 108 | background: #396684; 109 | } 110 | 111 | .button--secondary { 112 | background: #888888; 113 | border-bottom: 2px solid #717171; 114 | } 115 | 116 | .button--secondary:hover { 117 | background: #6E6E6E; 118 | } 119 | 120 | /* Note editor */ 121 | 122 | .title-input { 123 | border: 1px solid #DEDFE0; 124 | font-size: 2rem; 125 | font-weight: 300; 126 | display: block; 127 | margin: 2.4rem 0; 128 | padding: .8rem; 129 | width: 100%; 130 | } 131 | 132 | .body-input { 133 | border: 1px solid #DEDFE0; 134 | font-family: inherit; 135 | font-size: 1.6rem; 136 | font-weight: 300; 137 | display: block; 138 | margin: 2.4rem 0; 139 | min-height: 15rem; 140 | padding: .8rem; 141 | width: 100%; 142 | } 143 | 144 | /* Note List Item */ 145 | 146 | .list-item { 147 | text-decoration: none; 148 | color: #333333; 149 | background: #F7F7F7; 150 | border: 1px solid #dedfe0; 151 | margin: 1.6rem 0; 152 | padding: 1.6rem; 153 | display: block; 154 | transition: background .3s ease; 155 | } 156 | 157 | .list-item:hover { 158 | background: #eeeeee; 159 | } 160 | 161 | .list-item__title { 162 | font-size: 1.8rem; 163 | margin-bottom: .4rem 164 | } 165 | 166 | .list-item__subtitle { 167 | color: #666; 168 | font-size: 1.4rem; 169 | font-weight: 300; 170 | font-style: italic; 171 | } 172 | 173 | .empty-message { 174 | text-align: center; 175 | margin: 3.2rem 0; 176 | } --------------------------------------------------------------------------------