From 3590003d8719068aa83bc5722f2f2cf855733ab3 Mon Sep 17 00:00:00 2001 From: Danielle Andrews <62191468+DrAcula27@users.noreply.github.com> Date: Wed, 29 Jan 2025 14:56:25 -0800 Subject: [PATCH 01/11] Update README.md --- README.md | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5ab0326c..4e16864e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,31 @@ -npm install -add DB_STRING to .env file \ No newline at end of file +# ToDo List - EJS, MongoDB, Node + Express + +## Getting Started + +1. Go to the [todo-list-express](https://github.com/100devs/todo-list-express) repository and click `Fork` on the top right. This is basically "Save As"ing the project to your own GitHub. +1. In your forked repository, click `Code` -> `HTTPS` -> copy the link +1. Choose a location on your local PC where you want to create a copy of these files to work on. +1. Use the terminal to access that location, and type: `git clone [paste link copied during step 2]` + +Now you can work on the code. The assignment is to comment every line. Start with server.js, then move to index.ejs, main.js, and style.css if you want. + +## Running the App +> Note: This technically isn't part of the assignment. + +### Orginal README said: run `npm install` +- Run `npm install` from the terminal while in your repository's folder. + - This command goes through the `package.json` file and installs all dependencies listed there so the app can work. + +### Original README said: add DB_STRING to .env file +- Create a file called `.env`, and type `DB_STRING=your-MongoDB-connection-string`. +- **Note**: If you need help, follow along with this [article](https://zellwk.com/blog/crud-express-mongodb/#setting-up-mongodb-atlas) to get your connection string. +- Add your connection string to your `.env` file. It should look something like: + - `DB_STRING=mongodb+srv://...` + - Continue with your _full connection string_. This is sensitive information, which is why it's being stored in your `.env`, and your `.gitignore` file lists `.env` so that it is not shared to your public github repository. + +1. Start the node server by running this command in a terminal within your repository folder: `node server.js` + - **Note**: If running properly, the terminal will state: + - `Server running on port 2121` + - `Connected to todo Database` +1. Open the application by going to `http://127.0.0.1:2121/` or `http://localhost:2121/` + - The server is running on port 2121 on your local machine, which has the address of 127.0.0.1 (or localhost) when referring to itself. From 6c896e809b60f7f91d9c354cbd42c7d5c6e6a536 Mon Sep 17 00:00:00 2001 From: Danielle Date: Wed, 29 Jan 2025 15:13:50 -0800 Subject: [PATCH 02/11] delete vscode settings --- .vscode/settings.json | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index c3c81b8b..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "editor.fontSize": 42, - "terminal.integrated.fontSize": 62 -} \ No newline at end of file From 90e03dde8edd27bd66ad806a6136e836fcadcc5c Mon Sep 17 00:00:00 2001 From: Danielle Date: Wed, 29 Jan 2025 15:15:42 -0800 Subject: [PATCH 03/11] remove extra head/body tags, Prettier formatting, comment code to explain how each line/block works --- views/index.ejs | 72 +++++++++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/views/index.ejs b/views/index.ejs index a26617ae..535066b8 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -1,47 +1,49 @@ + + - - - - - Document - - - - - - - - - - - Document - - -

Todo List:

+ + + + + + ToDo List + + + + + +

Todo List:

Left to do: <%= left %>

Add A Todo:

- +
- - + +
- - - + + + From 7a0506aa5c30d3596fdc15f9f57a98097f26fb2d Mon Sep 17 00:00:00 2001 From: Danielle Date: Wed, 29 Jan 2025 15:16:13 -0800 Subject: [PATCH 04/11] Prettier formatting, comment code to explain how each rule works --- public/css/style.css | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/public/css/style.css b/public/css/style.css index 0475253a..a29ed28c 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1,7 +1,10 @@ -h1{ - color: red; +/* make all h1 HTML content red */ +h1 { + color: red; +} + +/* make any HTML element with a class of completed grey and with a line through it */ +.completed { + color: gray; + text-decoration: line-through; } -.completed{ - color: gray; - text-decoration: line-through; -} \ No newline at end of file From 975863d9e0cbe0370c1d4ec4cb9889d098409a7f Mon Sep 17 00:00:00 2001 From: Danielle Date: Wed, 29 Jan 2025 15:30:37 -0800 Subject: [PATCH 05/11] Prettier formatting, comment each line/block to explain what it does --- public/js/main.js | 166 +++++++++++++++++++++++++++++----------------- 1 file changed, 105 insertions(+), 61 deletions(-) diff --git a/public/js/main.js b/public/js/main.js index ff0eac39..133b4f86 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1,72 +1,116 @@ -const deleteBtn = document.querySelectorAll('.fa-trash') -const item = document.querySelectorAll('.item span') -const itemCompleted = document.querySelectorAll('.item span.completed') +// select items from the DOM +const deleteBtns = document.querySelectorAll('.fa-trash'); +const items = document.querySelectorAll('.item span'); +const completedItems = document.querySelectorAll( + '.item span.completed' +); -Array.from(deleteBtn).forEach((element)=>{ - element.addEventListener('click', deleteItem) -}) +// querySelectorAll returns a NodeList since it is pulling things from the DOM +// a NodeList is an 'array-like' and can be turned into an array for manipulation +// next 3 lines creates arrays from the NodeLists selected at the top of the file and adds click event listeners to them, each running a different callback function +Array.from(deleteBtns).forEach((element) => { + element.addEventListener('click', deleteItem); +}); -Array.from(item).forEach((element)=>{ - element.addEventListener('click', markComplete) -}) +Array.from(items).forEach((element) => { + element.addEventListener('click', markComplete); +}); -Array.from(itemCompleted).forEach((element)=>{ - element.addEventListener('click', markUnComplete) -}) +Array.from(completedItems).forEach((element) => { + element.addEventListener('click', markUnComplete); +}); -async function deleteItem(){ - const itemText = this.parentNode.childNodes[1].innerText - try{ - const response = await fetch('deleteItem', { - method: 'delete', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - 'itemFromJS': itemText - }) - }) - const data = await response.json() - console.log(data) - location.reload() +// deletes an item from the todo list +// must be async because fetching data from the server +async function deleteItem() { + // select the text of the current item + const itemText = this.parentNode.childNodes[1].innerText; + // try to ask the server to delete an item with the `itemText` as the identifier + try { + // since async, must await the fetch from the server + // there must be a route on the server that matches 'deleteItem' + const response = await fetch('deleteItem', { + // 'delete' is the action to remove the item from the database + method: 'delete', + // lets the API know to expect JSON data + headers: { 'Content-Type': 'application/json' }, + // converts the javascript to JSON + body: JSON.stringify({ + itemFromJS: itemText, + }), + }); + // wait for API response and save into `data` variable + const data = await response.json(); + // print the response to the console + console.log(data); + // refresh the page to cause client to sent new GET req to show that the item has been deleted + location.reload(); - }catch(err){ - console.log(err) - } + // if something goes wrong, print the error to the console + } catch (err) { + console.log(err); + } } -async function markComplete(){ - const itemText = this.parentNode.childNodes[1].innerText - try{ - const response = await fetch('markComplete', { - method: 'put', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - 'itemFromJS': itemText - }) - }) - const data = await response.json() - console.log(data) - location.reload() +// marks a todo item as complete +// must be async because fetching data from the server +async function markComplete() { + // select the text of the current item + const itemText = this.parentNode.childNodes[1].innerText; + // try to ask the server to update an item with the `itemText` as the identifier + try { + // since async, must await the fetch from the server + // there must be a route on the server that matches 'markComplete' + const response = await fetch('markComplete', { + // 'put' is the action to update the item in the database + method: 'put', + // lets the API know to expect JSON data + headers: { 'Content-Type': 'application/json' }, + // converts the javascript to JSON + body: JSON.stringify({ + itemFromJS: itemText, + }), + }); + // wait for API response and save into `data` variable + const data = await response.json(); + // print the response to the console + console.log(data); + // refresh the page to cause client to sent new GET req to show that the item has been marked as completed + location.reload(); - }catch(err){ - console.log(err) - } + // if something goes wrong, print the error to the console + } catch (err) { + console.log(err); + } } -async function markUnComplete(){ - const itemText = this.parentNode.childNodes[1].innerText - try{ - const response = await fetch('markUnComplete', { - method: 'put', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({ - 'itemFromJS': itemText - }) - }) - const data = await response.json() - console.log(data) - location.reload() +// marks a todo item as uncompleted +// must be async because fetching data from the server +async function markUnComplete() { + // select the text of the current item + const itemText = this.parentNode.childNodes[1].innerText; + // since async, must await the fetch from the server + // there must be a route on the server that matches 'markUnComplete' + try { + const response = await fetch('markUnComplete', { + // 'put' is the action to update the item in the database + method: 'put', + // lets the API know to expect JSON data + headers: { 'Content-Type': 'application/json' }, + // converts the javascript to JSON + body: JSON.stringify({ + itemFromJS: itemText, + }), + }); + // wait for API response and save into `data` variable + const data = await response.json(); + // print the response to the console + console.log(data); + // refresh the page to cause client to sent new GET req to show that the item has been marked as uncompleted + location.reload(); - }catch(err){ - console.log(err) - } -} \ No newline at end of file + // if something goes wrong, print the error to the console + } catch (err) { + console.log(err); + } +} From ae051f6d3c90d71f2bb397481f60ac8469d3bdd0 Mon Sep 17 00:00:00 2001 From: Danielle Date: Wed, 29 Jan 2025 16:07:39 -0800 Subject: [PATCH 06/11] Prettier formatting, fix typos, comment each line/block to explain what it does --- server.js | 197 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 123 insertions(+), 74 deletions(-) diff --git a/server.js b/server.js index 58b53e2f..62607841 100644 --- a/server.js +++ b/server.js @@ -1,93 +1,142 @@ -const express = require('express') -const app = express() -const MongoClient = require('mongodb').MongoClient -const PORT = 2121 -require('dotenv').config() - - +// import express +const express = require('express'); +// import mongodb +const MongoClient = require('mongodb').MongoClient; +// set up dotenv +require('dotenv').config(); +// use express +const app = express(); +// set default port +const PORT = 2121; +// initialize db variables let db, - dbConnectionStr = process.env.DB_STRING, - dbName = 'todo' + dbConnectionStr = process.env.DB_STRING, + dbName = 'todo'; -MongoClient.connect(dbConnectionStr, { useUnifiedTopology: true }) - .then(client => { - console.log(`Connected to ${dbName} Database`) - db = client.db(dbName) - }) - -app.set('view engine', 'ejs') -app.use(express.static('public')) -app.use(express.urlencoded({ extended: true })) -app.use(express.json()) +// connect to MongoDB database +MongoClient.connect(dbConnectionStr, { + useUnifiedTopology: true, +}).then((client) => { + console.log(`Connected to ${dbName} Database`); + db = client.db(dbName); +}); +// let express know to expect ejs as view engine +app.set('view engine', 'ejs'); +// tell express to use static files inside the public folder +app.use(express.static('public')); +// replace bodyparser middleware, this is how express can parse JSON +app.use(express.urlencoded({ extended: true })); +app.use(express.json()); -app.get('/',async (request, response)=>{ - const todoItems = await db.collection('todos').find().toArray() - const itemsLeft = await db.collection('todos').countDocuments({completed: false}) - response.render('index.ejs', { items: todoItems, left: itemsLeft }) - // db.collection('todos').find().toArray() - // .then(data => { - // db.collection('todos').countDocuments({completed: false}) - // .then(itemsLeft => { - // response.render('index.ejs', { items: data, left: itemsLeft }) - // }) - // }) - // .catch(error => console.error(error)) -}) +// set up route for home page -> GET/Read +// is async because need to await req to mongodb for todo items +app.get('/', async (request, response) => { + // search db for all todo items, put into an array + const todoItems = await db.collection('todos').find().toArray(); + // count how many todos are not done + const itemsLeft = await db + .collection('todos') + .countDocuments({ completed: false }); + // render the DOM using the index.ejs template, supplying the todoItems as items and itemsLeft as left + response.render('index.ejs', { items: todoItems, left: itemsLeft }); +}); +// route to add a new todo item -> POST/Create app.post('/addTodo', (request, response) => { - db.collection('todos').insertOne({thing: request.body.todoItem, completed: false}) - .then(result => { - console.log('Todo Added') - response.redirect('/') + // access the todos db collection + db.collection('todos') + // insert one item (as thing) using the form to set the body, hard coding whether it is completed as false + .insertOne({ thing: request.body.todoItem, completed: false }) + // after the item is inserted (async) + .then((result) => { + // print to the console that the todo was added + console.log('Todo Added'); + // tell the client to redirect back to the home page (essentially a refresh) to cause a new GET request + response.redirect('/'); }) - .catch(error => console.error(error)) -}) + // if something goes wrong, print the error to the console + .catch((error) => console.error(error)); +}); +// route to mark a todo as complete -> PUT/Update app.put('/markComplete', (request, response) => { - db.collection('todos').updateOne({thing: request.body.itemFromJS},{ + // access the todos db collection + db.collection('todos') + // update the first todo item found based on the todo text grabbed from the DOM on the client-side + .updateOne( + { thing: request.body.itemFromJS }, + { + // set its completed attribute to true $set: { - completed: true - } - },{ - sort: {_id: -1}, - upsert: false - }) - .then(result => { - console.log('Marked Complete') - response.json('Marked Complete') + completed: true, + }, + }, + { + // sort the items in descending order + sort: { _id: -1 }, + // if no item found, do not add a new one + upsert: false, + } + ) + .then((result) => { + // print to the console that the item is marked complete + console.log('Marked Complete'); + // respond to client that item marked complete + response.json('Marked Complete'); }) - .catch(error => console.error(error)) - -}) + // if something goes wrong, print error to console + .catch((error) => console.error(error)); +}); +// route to mark a todo as uncompleted -> PUT/Update app.put('/markUnComplete', (request, response) => { - db.collection('todos').updateOne({thing: request.body.itemFromJS},{ + // access the todos db collection + db.collection('todos') + // update the first todo item found based on the todo text grabbed from the DOM on the client-side + .updateOne( + { thing: request.body.itemFromJS }, + { + // set its completed attribute to false $set: { - completed: false - } - },{ - sort: {_id: -1}, - upsert: false - }) - .then(result => { - console.log('Marked Complete') - response.json('Marked Complete') + completed: false, + }, + }, + { + // sort the items in descending order + sort: { _id: -1 }, + // if no item found, do not add a new one + upsert: false, + } + ) + .then((result) => { + // print to the console that the item is marked uncompleted + console.log('Marked Uncompleted'); + // respond to client that item marked uncompleted + response.json('Marked Uncompleted'); }) - .catch(error => console.error(error)) - -}) + // if something goes wrong, print error to console + .catch((error) => console.error(error)); +}); +// route to remove a todo from the list -> DELETE/Dee-ley-tey app.delete('/deleteItem', (request, response) => { - db.collection('todos').deleteOne({thing: request.body.itemFromJS}) - .then(result => { - console.log('Todo Deleted') - response.json('Todo Deleted') + // access the todos db collection + db.collection('todos') + // remove the first todo item found based on the todo text grabbed from the DOM on the client-side + .deleteOne({ thing: request.body.itemFromJS }) + .then((result) => { + // print to the console that the item was deleted + console.log('Todo Deleted'); + // respond to client that item was deleted + response.json('Todo Deleted'); }) - .catch(error => console.error(error)) - -}) + // if something goes wrong, print error to console + .catch((error) => console.error(error)); +}); -app.listen(process.env.PORT || PORT, ()=>{ - console.log(`Server running on port ${PORT}`) -}) \ No newline at end of file +// tell the server where to listen +app.listen(process.env.PORT || PORT, () => { + // print listening statement to console + console.log(`Server running on port ${PORT}`); +}); From bdafe367c87548bb8aaeb6381cae8bb3167aa321 Mon Sep 17 00:00:00 2001 From: Danielle Date: Wed, 5 Feb 2025 19:54:48 -0800 Subject: [PATCH 07/11] add styles --- public/css/normalize.css | 349 +++++++++++++++++++++++++++++++++++++++ public/css/style.css | 279 ++++++++++++++++++++++++++++++- 2 files changed, 623 insertions(+), 5 deletions(-) create mode 100644 public/css/normalize.css diff --git a/public/css/normalize.css b/public/css/normalize.css new file mode 100644 index 00000000..c4699894 --- /dev/null +++ b/public/css/normalize.css @@ -0,0 +1,349 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + + html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers. + */ + +body { + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Remove the gray background on active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove the border on images inside links in IE 10. + */ + +img { + border-style: none; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + +/** + * Add the correct display in IE 10+. + */ + +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} diff --git a/public/css/style.css b/public/css/style.css index a29ed28c..efebf45f 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1,10 +1,279 @@ -/* make all h1 HTML content red */ -h1 { - color: red; +:root { + /* colors */ + --paper-background-color: #f6f6f6; + --completed-color: #808080; + --primary-dark-color: #0d1117; + --secondary-dark-color: #282a36; + --primary-light-color: #f8f8f2; + --secondary-light-color: #b5bcc4; + --accent-color: #73cfee; + --action-color: #dd6387; + + /* shadows */ + --accent-box-glow: 0 0 0.5em var(--accent-color); + --accent-text-outline: -2px -2px 0 var(--accent-color), + 2px -2px 0 var(--accent-color), -2px 2px 0 var(--accent-color), + 2px 2px 0 var(--accent-color); + --action-text-glow: 0.1em 0.1em 0.2em rgba(221, 99, 135, 0.5); + --light-text-glow: 0 0 0.2em var(--primary-light-color); + --accent-box-shadow: 0 -0.2em 0.2em rgba(115, 207, 238, 0.5); + + /* transitions */ + --transition: all 0.3s ease-in-out; +} + +/* overall document settings */ +body { + background-color: var(--primary-dark-color); + color: var(--secondary-dark-color); + font-family: system-ui, -apple-system, BlinkMacSystemFont, + 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', + 'Helvetica Neue', sans-serif; + line-height: 1.5; + + /* main grid */ + display: grid; + grid: auto 1fr auto / 10vw 1fr 10vw; + grid-template-areas: + 'header header header' + 'main main main' + 'footer footer footer'; + min-height: 100vh; +} + +a, +a *, +button, +button * { + cursor: pointer; +} + +/* header */ +header { + grid-area: header; + text-align: center; + font-size: clamp(1.75rem, 4vw + 1rem, 3rem); + text-shadow: var(--accent-text-outline); + color: var(--primary-light-color); +} + +header:hover { + text-shadow: var(--light-text-glow); + transition: var(--transition); +} + +.title { + margin: 0; +} + +/* main content */ +main { + grid-area: main; +} + +.todoContainer { + border: 1px solid var(--action-color); + border-radius: 1em; + margin: 1em auto; + max-width: 500px; + padding: 0.5em 2em; +} + +.paperContainer { + position: relative; + width: 90%; + max-width: 800px; + min-width: 400px; + height: 480px; + margin: 0 auto; + background: var(--paper-background-color); + border-radius: 10px; + box-shadow: var(--accent-box-glow); + overflow: auto; +} + +.paperContainer:before { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 60px; + background: radial-gradient( + var(--secondary-light-color) 8px, + transparent 2px + ) + repeat-y; + background-size: 30px 30px; + border-right: 3px solid var(--action-color); + box-sizing: border-box; +} + +.paperContent { + position: absolute; + top: 30px; + right: 0; + bottom: 30px; + left: 60px; + background: linear-gradient( + transparent, + transparent 28px, + var(--accent-color) 28px + ); + background-size: 100% 30px; + overflow-y: auto; +} + +.todosLeft { + margin: 0 0 0 0.25em; +} + +.todoItems { + margin-top: -3px; +} + +.paperContent .item { + display: flex; + align-items: center; + height: 30px; + line-height: 30px; + font-size: 1.3em; + font-weight: bold; + font-family: 'Handlee', cursive; + z-index: 1; +} + +.item .fa-trash { + color: var(--secondary-dark-color); + padding-left: 10px; +} + +.item span:hover, +.fa-trash:hover { + color: var(--action-color); + transition: var(--transition); +} + +.item:hover { + cursor: pointer; } -/* make any HTML element with a class of completed grey and with a line through it */ .completed { - color: gray; + color: var(--completed-color); text-decoration: line-through; } + +.addTodoContainer { + max-width: max-content; + margin: 0 auto; +} + +.addTodoContainer button:hover { + box-shadow: var(--action-text-glow); +} + +.addTodo { + color: var(--action-color); + margin-bottom: 0; +} + +input[type='text'] { + background: var(--paper-background-color); + color: var(--primary-dark-color); + border: 1px solid var(--accent-color); + padding: 12px 16px; + font-size: 1rem; + border-radius: 8px; + outline: none; + transition: var(--transition); + width: 250px; + box-shadow: 0 4px 8px rgba(115, 207, 238, 0.3); +} + +input[type='text']::placeholder { + color: var(--secondary-light-color); + font-style: italic; +} + +input[type='text']:focus { + border-color: var(--action-color); + box-shadow: var(--action-text-glow); +} + +button { + background: var(--action-color); + color: var(--primary-light-color); + border: 1px solid var(--action-color); + padding: 12px 24px; + font-size: 1rem; + font-weight: bold; + border-radius: 8px; + cursor: pointer; + transition: var(--transition); +} + +button:hover { + box-shadow: var(--action-text-glow); + background: var(--primary-light-color); + color: var(--action-color); +} + +button:active { + transform: scale(0.95); + box-shadow: inset 0 4px 8px rgba(221, 99, 135, 0.5); +} + +/* footer layout */ +footer { + grid-area: footer; + background-color: var(--secondary-dark-color); + color: var(--primary-light-color); + box-shadow: var(--accent-box-shadow); + display: flex; + align-items: center; + justify-content: center; + gap: 15px; + padding: 10px; + margin-top: 15px; + font-size: 18px; + font-weight: 500; +} + +footer .fa-github, +footer .fa-linkedin { + color: var(--primary-light-color); + font-size: 24px; + transition: var(--transition); +} + +footer .fa-github:hover, +footer .fa-linkedin:hover { + transform: rotateY(360deg) scale(1.4); + color: var(--action-color); +} + +footer .fa-heart { + color: var(--action-color); + font-size: 24px; + transition: var(--transition); +} +footer .fa-heart:hover { + transform: scale(1.3); +} + +/* media queries */ +@media (max-width: 500px) { + #footer-span { + display: none; + } +} + +@media (max-width: 300px) { + header { + font-size: 1em; + } + + footer p { + display: none; + } +} From 8bca41fc7087e0ec33fa3e94dfb5d0a8143300ac Mon Sep 17 00:00:00 2001 From: Danielle Date: Wed, 5 Feb 2025 19:55:11 -0800 Subject: [PATCH 08/11] extra text update when all done --- views/index.ejs | 109 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 81 insertions(+), 28 deletions(-) diff --git a/views/index.ejs b/views/index.ejs index 535066b8..783d6d22 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -10,40 +10,93 @@ name="viewport" content="width=device-width, initial-scale=1.0" /> - ToDo List - + + + + ToDo List + + + + + + + -

Todo List:

-
    - - <% for(let i=0; i < items.length; i++) {%> -
  • - <% if(items[i].completed === true) {%> - <%= items[i].thing %> - <% }else{ %> - <%= items[i].thing %> - <% } %> - -
  • - <% } %> -
+
+

Todo List

+
+
+
+
+

+ <% if ( left < 1 ) { %> + All Done! + <% } else { %> + <%= left %> Todos Left + <% } %> +

-

Left to do: <%= left %>

+
    + + <% for(let i=0; i < items.length; i++) {%> +
  • + <% if(items[i].completed === true) {%> + <%= items[i].thing %> + <% }else{ %> + <%= items[i].thing %> + <% } %> + +
  • + <% } %> +
+
+
-

Add A Todo:

- -
- - -
- - - +
+

Add Todo:

+ +
+ + + +
+
+
+
+

+ © + + | + + Crafted with by DrAcula27 | +

+ + + +

|

+ + + +
From f2c29101e0a95f9cd500566ad76847b04999fb9a Mon Sep 17 00:00:00 2001 From: Danielle Date: Wed, 5 Feb 2025 19:55:20 -0800 Subject: [PATCH 09/11] add updates and todos sections --- README.md | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4e16864e..767cbe7c 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,16 @@ Now you can work on the code. The assignment is to comment every line. Start with server.js, then move to index.ejs, main.js, and style.css if you want. ## Running the App + > Note: This technically isn't part of the assignment. ### Orginal README said: run `npm install` + - Run `npm install` from the terminal while in your repository's folder. - This command goes through the `package.json` file and installs all dependencies listed there so the app can work. -### Original README said: add DB_STRING to .env file +### Original README said: add DB_STRING to .env file + - Create a file called `.env`, and type `DB_STRING=your-MongoDB-connection-string`. - **Note**: If you need help, follow along with this [article](https://zellwk.com/blog/crud-express-mongodb/#setting-up-mongodb-atlas) to get your connection string. - Add your connection string to your `.env` file. It should look something like: @@ -24,8 +27,22 @@ Now you can work on the code. The assignment is to comment every line. Start wit - Continue with your _full connection string_. This is sensitive information, which is why it's being stored in your `.env`, and your `.gitignore` file lists `.env` so that it is not shared to your public github repository. 1. Start the node server by running this command in a terminal within your repository folder: `node server.js` - - **Note**: If running properly, the terminal will state: - - `Server running on port 2121` - - `Connected to todo Database` + +- **Note**: If running properly, the terminal will state: + - `Server running on port 2121` + - `Connected to todo Database` + 1. Open the application by going to `http://127.0.0.1:2121/` or `http://localhost:2121/` - - The server is running on port 2121 on your local machine, which has the address of 127.0.0.1 (or localhost) when referring to itself. + +- The server is running on port 2121 on your local machine, which has the address of 127.0.0.1 (or localhost) when referring to itself. + +## Updates + +1. added styling +1. change text at top dynamically if all tasks get completed + +### Todos + +- [] add dark mode +- [] update app to use list item ids instead of DOM text to make changes +- [] improve list responsiveness on tiny screens From d6c98a0cfc476e0bc23f431ee89802a30c8d36dc Mon Sep 17 00:00:00 2001 From: Danielle Date: Thu, 6 Feb 2025 19:14:38 -0800 Subject: [PATCH 10/11] add favicon --- public/favicon.ico | Bin 0 -> 15086 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/favicon.ico diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..53134e8ebc6d12736ed4612bc5a1c33b2d7270fd GIT binary patch literal 15086 zcmeHOOKclO7#){Y-IbS+K( ze`fyo%+5QEMOYVGzn%d`*(WzLwvjOwjh6GP7|Wq-6(H4bzmPEl1nWQp7B-AT<>4=k zKp27le*{vRt()A+nz~&?I-b(4$5x8hhHigWwwr8s6UyHS@*%~oTi~-{a(ld$$>7$m zl$OyMD@Ig%#<;abwpqoEn0=GVLwRZ+mA2J+gWJ=pEr{DkfqJ?9rpiNkL$`hkUB^D~F?QG@61PsR(^t{kgFOT!2>F9PYf-Gg<(2az+F=ZS*NeXHZ+oR3231})Pjfl!KIk{+G+3Bjea-A1F$D9FylpRQ=dP%#ZPz>tq=Me>XmL(US42iE%I6 z@hinv=&J?N_~YTjtRFA-c~_zhKSo;|#&>+A842=8*8BLrmbIS5_;XP8!O#8Q5JAYbV)@nDcAt3J$*MC<=`Hb=*Fa)T{?p zJCd>d`dZ}CoT~Dw`IK&7U5l(nXw+{KvHH|Q-X2=#RF(Ujg$h5g))=hfX;%Czavl-- z2-03x)`>X3T`JcKH={57Bi9j5_XIM(jMLgsrEhkAEMB->)=9Xj=XN6A)23#{0})Kd+!}8@vH3X>MVooqq44Aa3f|trTRd9D~K0ycogmQ8ul7ZNmxI= zihGD9=gvX;)jHx=eNbsSJ&(5A<@$j8qE5Hh%Rb|UQ@zIEsa~(Tuq=!~7=bVXwMGEv zDEMI~dk#34AOgk;{i6)ek=SG(Gzf^3m{MMas4w(8eDeeX^eiNt`uTnb&hBdw^^1I3~;=n_r4lGVObj5|Dlh!_OUO<0{8qJ|@}i#L?iVxN zUNnuA$?~h@;H{NhPdM7-SQXuJY{QQ2)>Wd8U_zf> zE!WqJ8+z__S)Lb;G1`552YkkhrqyNC9^_kcIO}^R)M^{f7x`EdTp;=p&ZeGgr{A(} zVMB^%?+LPj#tlQuZK^k!LnorDT%ofHYBM*ew3J7DHdfKrGY)ahyC8F&2pb zxv2GjEZ|z{C-}vh5@TLXZ|vna@&D!9Yq3UO?wd>UA7i|8eYF(6=1Rns z_O6&OJl|45-(f)~L#!va$@yAw*m$H?T9r=rPp(5h2z-|(Xo;V2AxAD3WnR$xPIT${ qhLF{%&^hzOx+j7JBlD#*P{zgq>7eRIl>Z5w%69?Zi!#a!zJCG0BlTMV literal 0 HcmV?d00001 From 13bf764bd415a516f89b9f097177afdf52053402 Mon Sep 17 00:00:00 2001 From: Danielle Date: Thu, 6 Feb 2025 19:15:12 -0800 Subject: [PATCH 11/11] add dark/light mode toggle --- README.md | 2 +- public/css/style.css | 105 +++++++++++++++++++++++++++++-------------- public/js/main.js | 27 +++++++++++ views/index.ejs | 15 ++++--- 4 files changed, 110 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 767cbe7c..6897d92b 100644 --- a/README.md +++ b/README.md @@ -40,9 +40,9 @@ Now you can work on the code. The assignment is to comment every line. Start wit 1. added styling 1. change text at top dynamically if all tasks get completed +1. added dark/light mode toggle ### Todos -- [] add dark mode - [] update app to use list item ids instead of DOM text to make changes - [] improve list responsiveness on tiny screens diff --git a/public/css/style.css b/public/css/style.css index efebf45f..5c4bf7b3 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1,11 +1,11 @@ :root { /* colors */ - --paper-background-color: #f6f6f6; - --completed-color: #808080; + --paper-background-color: #f7f7e6; + --completed-color: #bdbdbd; --primary-dark-color: #0d1117; --secondary-dark-color: #282a36; - --primary-light-color: #f8f8f2; - --secondary-light-color: #b5bcc4; + --primary-light-color: #f8f8f8; + --secondary-light-color: #8f8f8f; --accent-color: #73cfee; --action-color: #dd6387; @@ -17,6 +17,7 @@ --action-text-glow: 0.1em 0.1em 0.2em rgba(221, 99, 135, 0.5); --light-text-glow: 0 0 0.2em var(--primary-light-color); --accent-box-shadow: 0 -0.2em 0.2em rgba(115, 207, 238, 0.5); + --light-box-shadow: 0 -0.2em 0.2em rgba(143, 143, 143, 0.5); /* transitions */ --transition: all 0.3s ease-in-out; @@ -24,8 +25,8 @@ /* overall document settings */ body { - background-color: var(--primary-dark-color); - color: var(--secondary-dark-color); + background-color: var(--primary-light-color); + color: var(--secondary-light-color); font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; @@ -53,13 +54,6 @@ header { grid-area: header; text-align: center; font-size: clamp(1.75rem, 4vw + 1rem, 3rem); - text-shadow: var(--accent-text-outline); - color: var(--primary-light-color); -} - -header:hover { - text-shadow: var(--light-text-glow); - transition: var(--transition); } .title { @@ -71,14 +65,6 @@ main { grid-area: main; } -.todoContainer { - border: 1px solid var(--action-color); - border-radius: 1em; - margin: 1em auto; - max-width: 500px; - padding: 0.5em 2em; -} - .paperContainer { position: relative; width: 90%; @@ -87,8 +73,8 @@ main { height: 480px; margin: 0 auto; background: var(--paper-background-color); + border: 1px solid var(--secondary-light-color); border-radius: 10px; - box-shadow: var(--accent-box-glow); overflow: auto; } @@ -144,7 +130,6 @@ main { } .item .fa-trash { - color: var(--secondary-dark-color); padding-left: 10px; } @@ -168,12 +153,7 @@ main { margin: 0 auto; } -.addTodoContainer button:hover { - box-shadow: var(--action-text-glow); -} - .addTodo { - color: var(--action-color); margin-bottom: 0; } @@ -200,7 +180,7 @@ input[type='text']:focus { box-shadow: var(--action-text-glow); } -button { +#addTaskBtn { background: var(--action-color); color: var(--primary-light-color); border: 1px solid var(--action-color); @@ -212,13 +192,13 @@ button { transition: var(--transition); } -button:hover { +#addTaskBtn:hover { box-shadow: var(--action-text-glow); background: var(--primary-light-color); color: var(--action-color); } -button:active { +#addTaskBtn:active { transform: scale(0.95); box-shadow: inset 0 4px 8px rgba(221, 99, 135, 0.5); } @@ -226,9 +206,9 @@ button:active { /* footer layout */ footer { grid-area: footer; - background-color: var(--secondary-dark-color); + background-color: var(--secondary-light-color); color: var(--primary-light-color); - box-shadow: var(--accent-box-shadow); + box-shadow: var(--light-box-shadow); display: flex; align-items: center; justify-content: center; @@ -257,10 +237,69 @@ footer .fa-heart { font-size: 24px; transition: var(--transition); } + footer .fa-heart:hover { transform: scale(1.3); } +/* Light mode */ +.light { + background-color: var(--primary-light-color); + color: var(--secondary-light-color); +} + +/* Dark theme overrides */ +body.dark { + background-color: var(--primary-dark-color); + color: var(--primary-light-color); +} + +.dark .paperContainer { + background-color: var(--secondary-dark-color); +} + +.dark .fa-trash { + color: var(--primary-light-color); +} + +.dark input[type='text'] { + background: var(--secondary-dark-color); + color: var(--primary-light-color); + border: 1px solid var(--secondary-light-color); + box-shadow: 0 4px 8px rgba(115, 207, 238, 0.3); +} + +.dark input[type='text']::placeholder { + color: var(--primary-light-color); +} + +.dark footer { + background-color: var(--secondary-dark-color); + box-shadow: var(--accent-box-shadow); +} + +/* Update button text based on theme */ +button.dark-mode-toggle { + position: absolute; + top: 1em; + right: 1em; + font-size: 1.5em; + background: none; + border: none; + border-radius: 50%; + padding: 0.3em; + cursor: pointer; + transition: var(--transition); +} + +button.dark-mode-toggle:hover { + box-shadow: var(--action-text-glow); +} + +button.dark-mode-toggle:active { + transform: scale(0.95); +} + /* media queries */ @media (max-width: 500px) { #footer-span { diff --git a/public/js/main.js b/public/js/main.js index 133b4f86..12afd567 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -114,3 +114,30 @@ async function markUnComplete() { console.log(err); } } + +// Check for saved theme preference in localStorage +const savedTheme = localStorage.getItem('theme'); +if (savedTheme) { + document.body.classList.add(savedTheme); +} + +// Toggle dark mode +const darkModeToggle = document.getElementById('darkModeToggle'); +darkModeToggle.addEventListener('click', () => { + const currentTheme = document.body.classList.contains('dark') + ? 'dark' + : 'light'; + + // Toggle theme + if (currentTheme === 'dark') { + document.body.classList.remove('dark'); + document.body.classList.add('light'); + darkModeToggle.textContent = '🌙'; // Change button text for light mode + localStorage.setItem('theme', 'light'); + } else { + document.body.classList.remove('light'); + document.body.classList.add('dark'); + darkModeToggle.textContent = '🌞'; // Change button text for dark mode + localStorage.setItem('theme', 'dark'); + } +}); diff --git a/views/index.ejs b/views/index.ejs index 783d6d22..b11df0bc 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -23,7 +23,11 @@ src="https://kit.fontawesome.com/940af2685d.js" crossorigin="anonymous" > - + @@ -36,15 +40,16 @@

Todo List

+

<% if ( left < 1 ) { %> - All Done! + All Done! <% } else { %> - <%= left %> Todos Left + <%= left %> Todos Left <% } %> -

+
    @@ -72,7 +77,7 @@ name="todoItem" /> - +