├── 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 |
17 |
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 |
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 | }
--------------------------------------------------------------------------------