23
									
								
								.github/workflows/css-linting.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,23 @@ | |||||||
|  | name: CSS Linting | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   pull_request: | ||||||
|  |     branches: | ||||||
|  |     - main | ||||||
|  |  | ||||||
|  |   # Allows you to run this workflow manually from the Actions tab | ||||||
|  |   workflow_dispatch: | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   # Single deploy job since we're just deploying | ||||||
|  |   test: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - name: Install apt updates | ||||||
|  |         run: sudo apt -y update; sudo apt -y upgrade; | ||||||
|  |       - name: Checkout | ||||||
|  |         uses: actions/checkout@v3 | ||||||
|  |       - name: Install dependencies | ||||||
|  |         run: sudo npm install | ||||||
|  |       - name: Run tests | ||||||
|  |         run: sudo npm run lintCSS | ||||||
							
								
								
									
										23
									
								
								.github/workflows/html-linting.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,23 @@ | |||||||
|  | name: HTML Linting | ||||||
|  |  | ||||||
|  | on: | ||||||
|  |   pull_request: | ||||||
|  |     branches: | ||||||
|  |     - main | ||||||
|  |  | ||||||
|  |   # Allows you to run this workflow manually from the Actions tab | ||||||
|  |   workflow_dispatch: | ||||||
|  |  | ||||||
|  | jobs: | ||||||
|  |   # Single deploy job since we're just deploying | ||||||
|  |   test: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - name: Install apt updates | ||||||
|  |         run: sudo apt -y update; sudo apt -y upgrade; | ||||||
|  |       - name: Checkout | ||||||
|  |         uses: actions/checkout@v3 | ||||||
|  |       - name: Install dependencies | ||||||
|  |         run: sudo npm install | ||||||
|  |       - name: Run tests | ||||||
|  |         run: sudo npm run lintHTML | ||||||
							
								
								
									
										4
									
								
								.github/workflows/js-unittest.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -16,7 +16,9 @@ jobs: | |||||||
|       - name: Install apt updates |       - name: Install apt updates | ||||||
|         run: sudo apt -y update; sudo apt -y upgrade; |         run: sudo apt -y update; sudo apt -y upgrade; | ||||||
|       - name: Install prerequisites |       - name: Install prerequisites | ||||||
|         run: sudo apt install -y nodejs npm; |         uses: actions/setup-node@v3 | ||||||
|  |         with: | ||||||
|  |           node-version: 18 | ||||||
|       - name: Checkout |       - name: Checkout | ||||||
|         uses: actions/checkout@v3 |         uses: actions/checkout@v3 | ||||||
|       - name: Install dependencies |       - name: Install dependencies | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								.htmlhintrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | { | ||||||
|  |   "attr-value-not-empty": false | ||||||
|  | } | ||||||
							
								
								
									
										3
									
								
								.stylelintrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | { | ||||||
|  | 	"extends": "stylelint-config-standard" | ||||||
|  | } | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| # Sprint 1 Review Meeting Minutes (11/13/2022) | # Sprint 1 Review Meeting Minutes (11/13/2022) | ||||||
| ## Team 29: Hackers1995 | ## Team 29: Hackers1995 | ||||||
| ## Meeting Topic: Sprint 1 Review | ## Meeting Topic: Sprint 1 Review | ||||||
|  | We are meeting with Gagan to discuss progress made on Sprint 1 and testing strategies that we need to keep in mind as we continue developing. | ||||||
|  |  | ||||||
| ## Attendance | ## Attendance | ||||||
| 1. Rhea Bhutada | 1. Rhea Bhutada | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								admin/meetings/11822-sprint1day2.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,39 @@ | |||||||
|  | # Meeting Minutes (11/08/2022) | ||||||
|  | ## Team 29: Hackers1995 | ||||||
|  | ## Meeting Topic: In-Person First Sprint Day 2 | ||||||
|  | Meeting notes for the first sprint | ||||||
|  |  | ||||||
|  | ## Attendance | ||||||
|  | 1. Rhea Bhutada | ||||||
|  | 2. George Dubinin | ||||||
|  | 3. Gavyn Ezell | ||||||
|  | 4. Kara Hoagland | ||||||
|  | 5. Sanjit Joseph | ||||||
|  | 6. Daniel Hernandez | ||||||
|  |  | ||||||
|  | ## Meeting Details | ||||||
|  | - When: 11/08/2022 at 2:00PM | ||||||
|  | - Where: Mike's Red Tacos | ||||||
|  |  | ||||||
|  | ## Agenda: | ||||||
|  | - ### Old/Unresolved Business | ||||||
|  |   - N/A | ||||||
|  | - ### New Business | ||||||
|  |   - Isaac now knows what Wolftown is | ||||||
|  |   - Pair programming setup with VSCode | ||||||
|  | - ### Next Meeting's Business | ||||||
|  |  | ||||||
|  | ## App Progress | ||||||
|  | - The landing page is closer | ||||||
|  | - Review card css file entered | ||||||
|  | - Review Card javascript logic implemented (thanks Gavin) | ||||||
|  | -  | ||||||
|  |  | ||||||
|  | ## Decisions Made | ||||||
|  | - Linting details decided (TABS NOT SPACES) | ||||||
|  |  | ||||||
|  | ## End Time | ||||||
|  | - 11/07/2022 at 8:00PM | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @@ -1,13 +1,20 @@ | |||||||
| { | { | ||||||
|   "name": "food-journal", |   "name": "food-journal", | ||||||
|   "version": "1.0.0", |   "version": "1.0.0", | ||||||
|  |   "type": "module", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "test": "mocha --recursive './{,!(node_modules)/**}/*.test.js'", |     "test": "mocha --recursive --require mock-local-storage './{,!(node_modules)/**}/*.test.js'", | ||||||
|     "lint": "eslint '**/*.js'", |     "lint": "eslint '**/*.js'", | ||||||
|     "fix-style": "eslint --fix **/*.js" |     "fix-style": "eslint --fix **/*.js", | ||||||
|  |     "lintHTML": "htmlhint '**/*.html'", | ||||||
|  |     "lintCSS": "stylelint '**/*.css'" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "eslint": "^8.27.0", |     "eslint": "^8.27.0", | ||||||
|     "mocha": "10" |     "htmlhint": "1.1.4", | ||||||
|  |     "mocha": "10", | ||||||
|  |     "mock-local-storage": "^1.1.23", | ||||||
|  |     "stylelint": "14.14.1", | ||||||
|  |     "stylelint-config-standard": "^29.0.0" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										77
									
								
								source/CreatePage.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,77 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  |  | ||||||
|  | <head> | ||||||
|  |   <meta charset="UTF-8" /> | ||||||
|  |   <meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||||||
|  |   <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||||
|  |   <title>Food Journal</title> | ||||||
|  |  | ||||||
|  |   <!-- Recipe Card Custom Element --> | ||||||
|  |   <script src="assets/scripts/ReviewCard.js" type="module"></script> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   <!-- Main Stylesheets & Scripts --> | ||||||
|  |   <!-- Temporarily commented out reset.css due to furthur discussion needed on the values of the default config--> | ||||||
|  |   <!-- <link rel="stylesheet" href="/static/reset.css" /> --> | ||||||
|  |   <link rel="stylesheet" href="./static/CreatePage.css" /> | ||||||
|  |   <script src="assets/scripts/main.js" type="module"></script> | ||||||
|  |  | ||||||
|  | </head> | ||||||
|  |  | ||||||
|  | <body> | ||||||
|  |   <form id="new-food-entry"> | ||||||
|  |     <fieldset> | ||||||
|  |       <legend>Pic:</legend> | ||||||
|  |       <label for="mealImage"> | ||||||
|  |         Source: | ||||||
|  |         <input type="text" id="mealImg" name="mealImg"> | ||||||
|  |       </label> | ||||||
|  |       <label for="image-alt"> | ||||||
|  |         Alt Text: | ||||||
|  |         <input type="text" id="imgAlt" name="imgAlt"> | ||||||
|  |       </label> | ||||||
|  |     </fieldset> | ||||||
|  |     <fieldset> | ||||||
|  |  | ||||||
|  |       <legend> Meal: </legend> | ||||||
|  |       <label for="Meal: ">Meal: | ||||||
|  |         <input type="text" id="mealName" name="mealName" required> | ||||||
|  |       </label> | ||||||
|  |       <label for="comments">Comments: | ||||||
|  |         <br> | ||||||
|  |         <textarea name="comments" id="comments"></textarea> | ||||||
|  |       </label> | ||||||
|  |     </fieldset> | ||||||
|  |  | ||||||
|  |     <fieldset class="rating"> | ||||||
|  |       <legend> Rating: </legend> | ||||||
|  |       <input type="radio" id="s5" name="rating" value="5"/> <label for="s5"> 5 stars </label> | ||||||
|  |       <input type="radio" id="s4" name="rating" value="4"/> <label for="s4"> 4 stars </label> | ||||||
|  |       <input type="radio" id="s3" name="rating" value="3"/> <label for="s3"> 3 stars </label> | ||||||
|  |       <input type="radio" id="s2" name="rating" value="2"/> <label for="s2"> 2 stars </label> | ||||||
|  |       <input type="radio" id="s1" name="rating" value="1"/> <label for="s1"> 1 star </label> | ||||||
|  |     </fieldset> | ||||||
|  |  | ||||||
|  |     <fieldset> | ||||||
|  |       <legend>Other Info:</legend> | ||||||
|  |       <label for="restaurant"> | ||||||
|  |         Restaurant: | ||||||
|  |         <input type="text" id="restaurant" name="restaurant" required> | ||||||
|  |       </label> | ||||||
|  |       <label for="tag-form"> | ||||||
|  |         Tags: | ||||||
|  |         <input type="text" id="tag-form" name="tag-form"> | ||||||
|  |         <div class='tag-container' id="tag-container-form"> | ||||||
|  |  | ||||||
|  |         </div> | ||||||
|  |         <button type="button" id="tagAdd">Add Tag</button> | ||||||
|  |       </label> | ||||||
|  |  | ||||||
|  |     </fieldset> | ||||||
|  |     <button type="submit" value="Submit">Add Review</button> | ||||||
|  |     <button type="button" class="danger">Clear Review Journal</button> | ||||||
|  |   </form> | ||||||
|  | </body> | ||||||
|  |  | ||||||
|  | </html> | ||||||
							
								
								
									
										77
									
								
								source/ReviewDetails.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,77 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | <head> | ||||||
|  |   <meta charset="UTF-8" /> | ||||||
|  |   <meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||||||
|  |   <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||||
|  |   <title>Food Journal</title> | ||||||
|  |  | ||||||
|  |   <!-- Recipe Card Custom Element --> | ||||||
|  |   <script src="assets/scripts/ReviewCard.js" type="module"></script> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   <!-- Main Stylesheets & Scripts -->  | ||||||
|  |   <!-- Temporarily commented out reset.css due to furthur discussion needed on the values of the default config--> | ||||||
|  |   <!-- <link rel="stylesheet" href="/static/reset.css" /> --> | ||||||
|  |   <link rel="stylesheet" href="./static/ReviewCard.css" /> | ||||||
|  |   <script src="assets/scripts/ReviewDetails.js" type="module"></script> | ||||||
|  |  | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |   <main> | ||||||
|  |     <button type="button" id="update">Update</button> | ||||||
|  |     <button type="button" id="delete" class="danger">Delete</button> | ||||||
|  |   </main> | ||||||
|  |     <!----> <form id="update-food-entry" class="hidden"> | ||||||
|  |       <fieldset> | ||||||
|  |         <legend>Pic:</legend> | ||||||
|  |         <label for="mealImage"> | ||||||
|  |           Source: | ||||||
|  |           <input type="text" id="mealImg" name="mealImg"> | ||||||
|  |         </label> | ||||||
|  |         <label for="image-alt"> | ||||||
|  |           Alt Text: | ||||||
|  |           <input type="text" id="imgAlt" name="imgAlt"> | ||||||
|  |         </label> | ||||||
|  |       </fieldset> | ||||||
|  |       <fieldset> | ||||||
|  |    | ||||||
|  |         <legend> Meal: </legend> | ||||||
|  |         <label for="Meal: ">Meal: | ||||||
|  |           <input type="text" id="mealName" name="mealName" required> | ||||||
|  |         </label> | ||||||
|  |         <label for="comments">Comments:  | ||||||
|  |           <br> | ||||||
|  |           <textarea name="comments" id="comments"></textarea> | ||||||
|  |         </label> | ||||||
|  |       </fieldset> | ||||||
|  |        | ||||||
|  |       <fieldset class="rating"> | ||||||
|  |         <legend> Rating: </legend> | ||||||
|  |         <input type="radio" id="s5" name="rating" value="5"/> <label for="s5"> 5 stars </label> | ||||||
|  |         <input type="radio" id="s4" name="rating" value="4"/> <label for="s4"> 4 stars </label> | ||||||
|  |         <input type="radio" id="s3" name="rating" value="3"/> <label for="s3"> 3 stars </label> | ||||||
|  |         <input type="radio" id="s2" name="rating" value="2"/> <label for="s2"> 2 stars </label> | ||||||
|  |         <input type="radio" id="s1" name="rating" value="1"/> <label for="s1"> 1 star </label> | ||||||
|  |       </fieldset> | ||||||
|  |  | ||||||
|  |       <fieldset> | ||||||
|  |         <legend>Other Info:</legend> | ||||||
|  |         <label for="restaurant"> | ||||||
|  |           Restaurant: | ||||||
|  |           <input type="text" id="restaurant" name="restaurant" required> | ||||||
|  |         </label> | ||||||
|  |         <label for="tag-form"> | ||||||
|  |           Tags: | ||||||
|  |           <input type="text" id="tag-form" name="tag-form"> | ||||||
|  |           <div class='tag-container' id="tag-container-form"> | ||||||
|  |    | ||||||
|  |           </div> | ||||||
|  |           <button type="button" id="tagAdd">Add Tag</button> | ||||||
|  |         </label> | ||||||
|  |    | ||||||
|  |       </fieldset> | ||||||
|  |       <button type="submit" value="Submit">Add Review</button> | ||||||
|  |       </form> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										
											BIN
										
									
								
								source/assets/images/1_spooky-ghost-cookies.jpeg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 6.6 KiB | 
							
								
								
									
										
											BIN
										
									
								
								source/assets/images/2_frightfully-easy-ghost-cookies.jpeg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 4.9 KiB | 
							
								
								
									
										
											BIN
										
									
								
								source/assets/images/3_ingredient-ghost-halloween-cookies.jpeg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 8.6 KiB | 
							
								
								
									
										1
									
								
								source/assets/images/icons/0-star.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 157.02 27.59"><defs><style>.cls-1{fill:#dadce0;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M142.51,0l-3.42,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H145.94"/><path class="cls-1" d="M110.51,0l-3.42,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H113.94"/><path class="cls-1" d="M78.51,0,75.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H81.94"/><path class="cls-1" d="M46.51,0,43.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H49.94"/><path class="cls-1" d="M14.51,0,11.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H17.94"/></g></g></svg> | ||||||
| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										1
									
								
								source/assets/images/icons/1-star.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 157.02 27.59"><defs><style>.cls-1{fill:#febc17;}.cls-2{fill:#dadce0;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M14.51,0,11.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H17.94"/><path class="cls-2" d="M142.51,0l-3.42,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H145.94"/><path class="cls-2" d="M110.51,0l-3.42,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H113.94"/><path class="cls-2" d="M78.51,0,75.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H81.94"/><path class="cls-2" d="M46.51,0,43.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H49.94"/></g></g></svg> | ||||||
| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										1
									
								
								source/assets/images/icons/2-star.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 157.02 27.59"><defs><style>.cls-1{fill:#febc17;}.cls-2{fill:#dadce0;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M14.51,0,11.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H17.94"/><path class="cls-1" d="M46.51,0,43.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H49.94"/><path class="cls-2" d="M142.51,0l-3.42,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H145.94"/><path class="cls-2" d="M110.51,0l-3.42,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H113.94"/><path class="cls-2" d="M78.51,0,75.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H81.94"/></g></g></svg> | ||||||
| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										1
									
								
								source/assets/images/icons/3-star.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 157.02 27.59"><defs><style>.cls-1{fill:#febc17;}.cls-2{fill:#dadce0;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M14.51,0,11.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H17.94"/><path class="cls-1" d="M46.51,0,43.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H49.94"/><path class="cls-1" d="M78.51,0,75.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H81.94"/><path class="cls-2" d="M142.51,0l-3.42,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H145.94"/><path class="cls-2" d="M110.51,0l-3.42,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H113.94"/></g></g></svg> | ||||||
| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										1
									
								
								source/assets/images/icons/4-star.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 157.02 27.59"><defs><style>.cls-1{fill:#febc17;}.cls-2{fill:#dadce0;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M14.51,0,11.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H17.94"/><path class="cls-1" d="M46.51,0,43.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H49.94"/><path class="cls-1" d="M78.51,0,75.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H81.94"/><path class="cls-1" d="M110.51,0l-3.42,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H113.94"/><path class="cls-2" d="M142.51,0l-3.42,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H145.94"/></g></g></svg> | ||||||
| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										1
									
								
								source/assets/images/icons/5-star.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 157.02 27.59"><defs><style>.cls-1{fill:#febc17;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M14.51,0,11.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H17.94"/><path class="cls-1" d="M46.51,0,43.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H49.94"/><path class="cls-1" d="M78.51,0,75.09,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H81.94"/><path class="cls-1" d="M110.51,0l-3.42,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H113.94"/><path class="cls-1" d="M142.51,0l-3.42,10.53c-3.66,0-7.61,0-11.09,0l9,6.51c-1.12,3.47-2.35,7.24-3.42,10.54,2.87-2.1,5.92-4.3,9-6.51l9,6.51c-1.12-3.41-2.26-6.95-3.42-10.54l9-6.51H145.94"/></g></g></svg> | ||||||
| After Width: | Height: | Size: 1.1 KiB | 
							
								
								
									
										
											BIN
										
									
								
								source/assets/images/icons/default_plate.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 311 KiB | 
							
								
								
									
										
											BIN
										
									
								
								source/assets/images/icons/plate_with_chopsticks.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 129 KiB | 
							
								
								
									
										
											BIN
										
									
								
								source/assets/images/icons/plate_with_cutlery.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 253 KiB | 
							
								
								
									
										270
									
								
								source/assets/scripts/ReviewCard.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,270 @@ | |||||||
|  | // ReviewCard.js | ||||||
|  |  | ||||||
|  | class ReviewCard extends HTMLElement { | ||||||
|  | 	// Called once when document.createElement('review-card') is called, or | ||||||
|  | 	// the element is written into the DOM directly as <review-card> | ||||||
|  | 	constructor() { | ||||||
|  | 		super();  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		let shadowEl = this.attachShadow({mode:"open"}); | ||||||
|  |  | ||||||
|  | 		let articleEl = document.createElement("article"); | ||||||
|  |  | ||||||
|  | 		let styleEl = document.createElement("style"); | ||||||
|  | 		styleEl.textContent = ` | ||||||
|  |       * { | ||||||
|  |         font-family: sans-serif; | ||||||
|  |         margin: 0; | ||||||
|  |         padding: 0; | ||||||
|  |       } | ||||||
|  |      | ||||||
|  |       a { | ||||||
|  |         text-decoration: none; | ||||||
|  |       } | ||||||
|  |      | ||||||
|  |       a:hover { | ||||||
|  |         text-decoration: underline; | ||||||
|  |       } | ||||||
|  |      | ||||||
|  |       article { | ||||||
|  |         align-items: center; | ||||||
|  |         border: 1px solid rgb(223, 225, 229); | ||||||
|  |         border-radius: 8px; | ||||||
|  |         display: grid; | ||||||
|  |         grid-template-rows: 118px 56px 14px 18px 15px 36px; | ||||||
|  |         height: auto; | ||||||
|  |         row-gap: 5px; | ||||||
|  |         padding: 0 16px 16px 16px; | ||||||
|  |         width: 178px; | ||||||
|  |       } | ||||||
|  |      | ||||||
|  |       div.rating { | ||||||
|  |         align-items: center; | ||||||
|  |         column-gap: 5px; | ||||||
|  |         display: flex; | ||||||
|  |       } | ||||||
|  |      | ||||||
|  |       div.rating>img { | ||||||
|  |         height: auto; | ||||||
|  |         display: inline-block; | ||||||
|  |         object-fit: scale-down; | ||||||
|  |         width: 78px; | ||||||
|  |       } | ||||||
|  |      | ||||||
|  |       article>img { | ||||||
|  |         border-top-left-radius: 8px; | ||||||
|  |         border-top-right-radius: 8px; | ||||||
|  |         height: 118px; | ||||||
|  |         object-fit: cover; | ||||||
|  |         margin-left: -16px; | ||||||
|  |         width: calc(100% + 32px); | ||||||
|  |       } | ||||||
|  |      | ||||||
|  |       label.restaurant-name { | ||||||
|  |         color: black !important; | ||||||
|  |       } | ||||||
|  |      | ||||||
|  |       label.meal-name { | ||||||
|  |         display: -webkit-box; | ||||||
|  |         font-size: 16px; | ||||||
|  |         height: 36px; | ||||||
|  |         line-height: 18px; | ||||||
|  |         overflow: hidden; | ||||||
|  |         -webkit-line-clamp: 2; | ||||||
|  |         -webkit-box-orient: vertical; | ||||||
|  |       } | ||||||
|  |      | ||||||
|  |       label:not(.meal-name), | ||||||
|  |       span, | ||||||
|  |       time { | ||||||
|  |         color: #70757A; | ||||||
|  |         font-size: 12px; | ||||||
|  |       } | ||||||
|  |     `; | ||||||
|  | 		articleEl.append(styleEl); | ||||||
|  | 		shadowEl.append(articleEl); | ||||||
|  | 		this.shadowEl = shadowEl; | ||||||
|  | 		//attach event listener to each recipe-card | ||||||
|  | 		this.addEventListener("click", (event) => { | ||||||
|  | 			console.log(event.target); | ||||||
|  | 			console.log(event.target.data); | ||||||
|  | 			//Option 1: sending current data to second html page using localStorage (could also just store index) | ||||||
|  | 			sessionStorage.setItem("current", JSON.stringify(event.target.data)); | ||||||
|  | 			window.location.assign("./ReviewDetails.html"); | ||||||
|  | 			/* | ||||||
|  |       //Option 2: sending current data to second html page using string query w/ url (currently not storing value) | ||||||
|  |       let reviewFields = window.location.search.slice(1).split("&"); | ||||||
|  |       for(let i = 0; i < reviewFields.length; i++) { | ||||||
|  |         let kv = reviewFields[i].split("="); | ||||||
|  |         let key = kv[0];  | ||||||
|  |         let value = kv[1]; | ||||||
|  |         console.log(key); | ||||||
|  |         console.log(value); | ||||||
|  |         // What you want to do with name and value... | ||||||
|  |       }*/ | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  |    * Called when the .data property is set on this element. | ||||||
|  |    * | ||||||
|  |    * For Example: | ||||||
|  |    * let reviewCard = document.createElement('review-card');  | ||||||
|  |    * reviewCard.data = { foo: 'bar' }  | ||||||
|  |    * | ||||||
|  |    * @param {Object} data - The data to pass into the <review-card>, must be of the | ||||||
|  |    *                        following format: | ||||||
|  |    *                        { | ||||||
|  |    *                          "mealImg": "string", | ||||||
|  |    *                          "imgAlt": "string", | ||||||
|  |    *                          "mealName": "string", | ||||||
|  |    *                          "comments": "string", | ||||||
|  |    *                          "rating": number, | ||||||
|  |    *                          "restaurant": "string", | ||||||
|  |    *                          "tags": string array | ||||||
|  |    *                        } | ||||||
|  |    */ | ||||||
|  | 	set data(data) { | ||||||
|  | 		// If nothing was passed in, return | ||||||
|  | 		if (!data) return; | ||||||
|  |  | ||||||
|  | 		// Select the <article> we added to the Shadow DOM in the constructor | ||||||
|  | 		let articleEl = this.shadowEl.querySelector("article"); | ||||||
|  |      | ||||||
|  | 		// setting the article elements for the review card | ||||||
|  |  | ||||||
|  | 		//image setup | ||||||
|  | 		let mealImg = document.createElement("img"); | ||||||
|  | 		mealImg.setAttribute("id", "a-mealImg"); | ||||||
|  | 		mealImg.setAttribute("src",data["mealImg"]); | ||||||
|  | 		mealImg.setAttribute("alt",data["imgAlt"]); | ||||||
|  |  | ||||||
|  | 		//meal name setup | ||||||
|  | 		let mealLabel = document.createElement("label"); | ||||||
|  | 		mealLabel.setAttribute("id", "a-mealName"); | ||||||
|  | 		mealLabel.setAttribute("class","meal-name"); | ||||||
|  | 		mealLabel.innerHTML = data["mealName"]; | ||||||
|  |  | ||||||
|  | 		//restaurant name setup | ||||||
|  | 		/* | ||||||
|  |     //review page link | ||||||
|  |     //giving it functionality to save the review card's info to session storage for loading the review page | ||||||
|  |     let reviewLink = document.createElement('a'); | ||||||
|  |     reviewLink.setAttribute('href','./review.html') | ||||||
|  |     reviewLink.innerHTML = 'review page' | ||||||
|  |     reviewLink.addEventListener('click', () => { | ||||||
|  |       sessionStorage.clear(); | ||||||
|  |       let currReview = { | ||||||
|  |         "imgSrc": data['imgSrc'], | ||||||
|  |         "imgAlt": data['imgAlt'], | ||||||
|  |         "mealName": data['mealName'], | ||||||
|  |         "restaurant": data['restaurant'], | ||||||
|  |         "comments": data['comments'], | ||||||
|  |         "rating": data['rating'], | ||||||
|  |         "tags": data['tags']                 | ||||||
|  |       } | ||||||
|  |       sessionStorage.setItem('currReview', JSON.stringify(currReview)); | ||||||
|  |     }); | ||||||
|  | */ | ||||||
|  | 		let restaurantLabel = document.createElement("label"); | ||||||
|  | 		restaurantLabel.setAttribute("id", "a-restaurant"); | ||||||
|  | 		restaurantLabel.setAttribute("class","restaurant-name"); | ||||||
|  | 		restaurantLabel.innerHTML = data["restaurant"]; | ||||||
|  |  | ||||||
|  | 		//comment section setup (display set to none) | ||||||
|  | 		let comments = document.createElement("p"); | ||||||
|  | 		comments.setAttribute("id", "a-comments"); | ||||||
|  | 		comments.style.display = "none"; | ||||||
|  | 		comments.innerText = data["comments"]; | ||||||
|  |  | ||||||
|  | 		//other info: rating | ||||||
|  | 		let ratingDiv = document.createElement("div"); | ||||||
|  | 		ratingDiv.setAttribute("class", "rating"); | ||||||
|  | 		let starsImg = document.createElement("img"); | ||||||
|  | 		starsImg.setAttribute("id", "a-rating"); | ||||||
|  | 		starsImg.setAttribute("src", "./assets/images/icons/"+data["rating"]+"-star.svg"); | ||||||
|  | 		starsImg.setAttribute("alt", data["rating"] +" stars"); | ||||||
|  | 		starsImg.setAttribute("num", data["rating"]); | ||||||
|  | 		ratingDiv.append(starsImg); | ||||||
|  |  | ||||||
|  | 		//added tags | ||||||
|  | 		let tagContainer = document.createElement("div"); | ||||||
|  | 		tagContainer.setAttribute("class", "tag-container"); | ||||||
|  | 		tagContainer.setAttribute("id", "a-tags"); | ||||||
|  | 		tagContainer.setAttribute("list", data["tags"]); | ||||||
|  | 		if(data["tags"]){ | ||||||
|  | 			for (let i = 0; i < data["tags"].length; i++) { | ||||||
|  | 				let newTag = document.createElement("label"); | ||||||
|  | 				newTag.setAttribute("class","tag"); | ||||||
|  | 				newTag.innerHTML = data["tags"][i] + "   "; | ||||||
|  | 				tagContainer.append(newTag); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		articleEl.append(mealImg); | ||||||
|  | 		articleEl.append(mealLabel); | ||||||
|  | 		//articleEl.append(reviewLink) | ||||||
|  | 		articleEl.append(restaurantLabel); | ||||||
|  | 		articleEl.append(ratingDiv); | ||||||
|  | 		articleEl.append(tagContainer); | ||||||
|  | 		articleEl.append(comments); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  |    * Called when getting the .data property of this element. | ||||||
|  |    * | ||||||
|  |    * For Example: | ||||||
|  |    * let reviewCard = document.createElement('review-card');  | ||||||
|  |    * reviewCard.data = { foo: 'bar' }  | ||||||
|  |    * | ||||||
|  |    * @return {Object} data - The data from the <review-card>, of the | ||||||
|  |    *                        following format: | ||||||
|  |    *                        { | ||||||
|  |    *                          "mealImg": "string", | ||||||
|  |    *                          "imgAlt": "string", | ||||||
|  |    *                          "mealName": "string", | ||||||
|  |    *                          "comments": "string", | ||||||
|  |    *                          "rating": number, | ||||||
|  |    *                          "restaurant": "string", | ||||||
|  |    *                          "tags": string array | ||||||
|  |    *                        } | ||||||
|  |    */ | ||||||
|  | 	get data() { | ||||||
|  |  | ||||||
|  | 		let dataContainer = {}; | ||||||
|  |      | ||||||
|  | 		// getting the article elements for the review card | ||||||
|  |  | ||||||
|  | 		//get image | ||||||
|  | 		let mealImg = this.shadowEl.getElementById("a-mealImg"); | ||||||
|  | 		dataContainer["mealImg"] = mealImg.getAttribute("src"); | ||||||
|  | 		dataContainer["imgAlt"] = mealImg.getAttribute("alt"); | ||||||
|  |  | ||||||
|  | 		//get meal name | ||||||
|  | 		let mealLabel = this.shadowEl.getElementById("a-mealName"); | ||||||
|  | 		dataContainer["mealName"] = mealLabel.innerHTML; | ||||||
|  |  | ||||||
|  | 		//get comment section | ||||||
|  | 		let comments = this.shadowEl.getElementById("a-comments"); | ||||||
|  | 		console.log(comments); | ||||||
|  | 		dataContainer["comments"] = comments.innerText; | ||||||
|  |  | ||||||
|  | 		//get other info: rating | ||||||
|  | 		let starsImg = this.shadowEl.getElementById("a-rating"); | ||||||
|  | 		dataContainer["rating"] = starsImg.getAttribute("num"); | ||||||
|  |  | ||||||
|  | 		//get restaurant name | ||||||
|  | 		let restaurantLabel = this.shadowEl.getElementById("a-restaurant"); | ||||||
|  | 		dataContainer["restaurant"] = restaurantLabel.innerHTML; | ||||||
|  |  | ||||||
|  | 		//get tags | ||||||
|  | 		let tagContainer = this.shadowEl.getElementById("a-tags"); | ||||||
|  | 		dataContainer["tags"] = tagContainer.getAttribute("list").split(","); | ||||||
|  |  | ||||||
|  | 		return dataContainer; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | customElements.define("review-card", ReviewCard); | ||||||
							
								
								
									
										124
									
								
								source/assets/scripts/ReviewDetails.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,124 @@ | |||||||
|  | //reviewDetails.js | ||||||
|  | import {getReviewsFromStorage, saveReviewsToStorage} from "./localStorage.js"; | ||||||
|  |  | ||||||
|  | // Run the init() function when the page has loaded | ||||||
|  | window.addEventListener("DOMContentLoaded", init); | ||||||
|  |  | ||||||
|  | function init(){ | ||||||
|  | 	setupDelete(); | ||||||
|  | 	setupUpdate(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function setupDelete(){ | ||||||
|  | 	let deleteBtn = document.getElementById("delete"); | ||||||
|  | 	let reviews = getReviewsFromStorage(); | ||||||
|  | 	let current = JSON.parse(sessionStorage.getItem("current")); | ||||||
|  | 	deleteBtn.addEventListener("click", function(){ | ||||||
|  | 		if(window.confirm("Are you sure you want to delete this entry?")){ | ||||||
|  | 			//delete function | ||||||
|  | 			if(current){ | ||||||
|  | 				console.log(current); | ||||||
|  | 				for(let i = 0; i < reviews.length; i++){ | ||||||
|  | 					console.log(reviews[i]); | ||||||
|  | 					if(reviews[i]["mealName"] == current["mealName"] && reviews[i]["restaurant"] == current["restaurant"]){ | ||||||
|  | 						console.log("match found"); | ||||||
|  | 						reviews.splice(i,1); | ||||||
|  | 						saveReviewsToStorage(reviews); | ||||||
|  | 						sessionStorage.removeItem("current"); | ||||||
|  | 						window.location.assign("./index.html"); | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function setupUpdate(){ | ||||||
|  | 	let updateBtn = document.getElementById("update"); | ||||||
|  | 	let reviews = getReviewsFromStorage(); | ||||||
|  | 	let current = JSON.parse(sessionStorage.getItem("current")); | ||||||
|  | 	let form = document.getElementById("update-food-entry"); | ||||||
|  | 	updateBtn.addEventListener("click", function(){ | ||||||
|  | 		//update function | ||||||
|  | 		if(current){ | ||||||
|  | 			console.log(current); | ||||||
|  | 			form.style.display = "block"; | ||||||
|  | 			let tagContainer = document.getElementById("tag-container-form"); | ||||||
|  | 			console.log(document.querySelectorAll("#update-food-entry input")); | ||||||
|  |  | ||||||
|  | 			//Set value of each input element to current's values | ||||||
|  | 			document.getElementById("mealImg").defaultValue = current["mealImg"]; | ||||||
|  | 			document.getElementById("imgAlt").defaultValue = current["imgAlt"]; | ||||||
|  | 			document.getElementById("mealName").defaultValue = current["mealName"]; | ||||||
|  | 			document.getElementById("comments").textContent = current["comments"]; | ||||||
|  | 			document.getElementById("rating-" + `${current["rating"]}`).checked = true; | ||||||
|  | 			document.getElementById("restaurant").defaultValue = current["restaurant"]; | ||||||
|  |  | ||||||
|  | 			if(current["tags"]){ | ||||||
|  | 				for (let i = 0; i < current["tags"].length; i++) { | ||||||
|  | 					let newTag = document.createElement("label"); | ||||||
|  | 					newTag.setAttribute("class","tag"); | ||||||
|  | 					newTag.innerHTML = current["tags"][i] + "   "; | ||||||
|  | 					newTag.addEventListener("click",()=> { | ||||||
|  | 						tagContainer.removeChild(newTag); | ||||||
|  | 					}); | ||||||
|  | 					tagContainer.append(newTag); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			//Take formdata values as newData when submit | ||||||
|  | 			form.addEventListener("submit", function(){ | ||||||
|  | 				/* | ||||||
|  |                 *  User submits the form for their review. | ||||||
|  |                 *  We create reviewCard and put in storage | ||||||
|  |                 */ | ||||||
|  | 				let formData = new FormData(form); | ||||||
|  | 				let newData = {}; | ||||||
|  | 				for (let [key, value] of formData) { | ||||||
|  | 					console.log(`${key}`); | ||||||
|  | 					console.log(`${value}`); | ||||||
|  | 					if (`${key}` !== "tag-form") { | ||||||
|  | 						newData[`${key}`] = `${value}`; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 				newData["tags"] = []; | ||||||
|  |              | ||||||
|  | 				let tags = document.querySelectorAll(".tag"); | ||||||
|  | 				for(let i = 0; i < tags.length; i ++) { | ||||||
|  | 					newData["tags"].push(tags[i].innerHTML); | ||||||
|  | 					tagContainer.removeChild(tags[i]); | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				for(let i = 0; i < reviews.length; i++){ | ||||||
|  | 					console.log(reviews[i]); | ||||||
|  | 					if(reviews[i]["mealName"] == current["mealName"] && reviews[i]["restaurant"] == current["restaurant"]){ | ||||||
|  | 						console.log("match found"); | ||||||
|  | 						reviews.splice(i,1,newData); | ||||||
|  | 						saveReviewsToStorage(reviews); | ||||||
|  | 						sessionStorage.setItem("current", JSON.stringify(newData)); | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				form.style.display = "none"; | ||||||
|  |  | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			let tagAddBtn = document.getElementById("tagAdd"); | ||||||
|  | 			tagAddBtn.addEventListener("click", ()=> { | ||||||
|  | 				let tagField = document.getElementById("tag-form"); | ||||||
|  | 				if (tagField.value.length > 0) { | ||||||
|  | 					let tagLabel = document.createElement("label"); | ||||||
|  | 					tagLabel.innerHTML = tagField.value; | ||||||
|  | 					tagLabel.setAttribute("class","tag"); | ||||||
|  | 					tagLabel.addEventListener("click",()=> { | ||||||
|  | 						tagContainer.removeChild(tagLabel); | ||||||
|  | 					}); | ||||||
|  |                  | ||||||
|  | 					tagContainer.append(tagLabel); | ||||||
|  | 					tagField.value = ""; | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								source/assets/scripts/ReviewPage.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,13 @@ | |||||||
|  | // Run the init() function when the page has loaded | ||||||
|  | window.addEventListener("DOMContentLoaded", init); | ||||||
|  |  | ||||||
|  | function init() { | ||||||
|  | 	let result = sessionStorage.getItem("currReview"); | ||||||
|  |  | ||||||
|  | 	let main = document.querySelector("main"); | ||||||
|  |      | ||||||
|  | 	main.innerHTML = result; | ||||||
|  | 	let p = document.createElement("p"); | ||||||
|  | 	p.innerHTML = JSON.parse(result)["comments"]; | ||||||
|  | 	main.append(p); | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								source/assets/scripts/localStorage.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,19 @@ | |||||||
|  | /** | ||||||
|  |  * @returns {Array<Object>} An array of reviews found in localStorage | ||||||
|  |  */ | ||||||
|  | export function getReviewsFromStorage() { | ||||||
|  | 	let result = JSON.parse(localStorage.getItem("reviews")); | ||||||
|  | 	if (result) { | ||||||
|  | 		return result; | ||||||
|  | 	} | ||||||
|  | 	return new Array(0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Takes in an array of reviews, converts it to a string, and then | ||||||
|  |  * saves that string to 'reviews' in localStorage | ||||||
|  |  * @param {Array<Object>} reviews An array of reviews | ||||||
|  |  */ | ||||||
|  | export function saveReviewsToStorage(reviews) { | ||||||
|  | 	localStorage.setItem("reviews", JSON.stringify(reviews)); | ||||||
|  | } | ||||||
							
								
								
									
										48
									
								
								source/assets/scripts/localStorage.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,48 @@ | |||||||
|  | import {strict as assert} from "node:assert"; | ||||||
|  | import {describe, it, beforeEach} from "mocha"; | ||||||
|  | import {saveReviewsToStorage, getReviewsFromStorage} from "./localStorage.js"; | ||||||
|  |  | ||||||
|  | beforeEach(() => { | ||||||
|  | 	localStorage.clear(); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | describe("test app localStorage interaction", () => { | ||||||
|  | 	it("get after init", () => { | ||||||
|  | 		assert.deepEqual(getReviewsFromStorage(), []); | ||||||
|  | 	}); | ||||||
|  | 	it("store one then get", () => { | ||||||
|  | 		let reviews = [{ | ||||||
|  | 			"imgSrc": "sample src", | ||||||
|  | 			"imgAlt": "sample alt", | ||||||
|  | 			"mealName": "sample name", | ||||||
|  | 			"restaurant": "sample restaurant", | ||||||
|  | 			"rating": 5, | ||||||
|  | 			"tags": ["tag 1", "tag  2", "tag 3"] | ||||||
|  | 		}]; | ||||||
|  |  | ||||||
|  | 		saveReviewsToStorage(reviews); | ||||||
|  | 		assert.deepEqual(getReviewsFromStorage(), reviews); | ||||||
|  | 	}); | ||||||
|  | 	it("repeated store one more and get", () => { | ||||||
|  | 		let reviews = []; | ||||||
|  |  | ||||||
|  | 		assert.deepEqual(getReviewsFromStorage(), reviews); | ||||||
|  |  | ||||||
|  | 		for(let i = 0; i < 1000; i++){ | ||||||
|  | 			reviews = getReviewsFromStorage(); | ||||||
|  | 			 | ||||||
|  | 			reviews.push( | ||||||
|  | 				{ | ||||||
|  | 					"imgSrc": `sample src ${i}`, | ||||||
|  | 					"imgAlt": `sample alt ${i}`, | ||||||
|  | 					"mealName": `sample name ${i}`, | ||||||
|  | 					"restaurant": `sample restaurant ${i}`, | ||||||
|  | 					"rating": i, | ||||||
|  | 					"tags": [`tag ${3*i}`, `tag ${3*i + 1}`, `tag ${3*i + 2}`] | ||||||
|  | 				} | ||||||
|  | 			); | ||||||
|  | 			saveReviewsToStorage(reviews); | ||||||
|  | 			assert.deepEqual(getReviewsFromStorage(), reviews); | ||||||
|  | 		} | ||||||
|  | 	}).timeout(10000); | ||||||
|  | }); | ||||||
							
								
								
									
										119
									
								
								source/assets/scripts/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,119 @@ | |||||||
|  | // main.js | ||||||
|  | import {getReviewsFromStorage, saveReviewsToStorage} from "./localStorage.js"; | ||||||
|  |  | ||||||
|  | // Run the init() function when the page has loaded | ||||||
|  | window.addEventListener("DOMContentLoaded", init); | ||||||
|  |  | ||||||
|  | function init() { | ||||||
|  | 	// Get the reviews from localStorage | ||||||
|  | 	let reviews = getReviewsFromStorage(); | ||||||
|  | 	// Add each reviews to the <main> element | ||||||
|  | 	addReviewsToDocument(reviews); | ||||||
|  | 	// Add the event listeners to the form elements | ||||||
|  | 	initFormHandler(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * @param {Array<Object>} reviews An array of reviews | ||||||
|  |  */ | ||||||
|  | function addReviewsToDocument(reviews) { | ||||||
|  | 	let mainEl = document.querySelector("main"); | ||||||
|  | 	reviews.forEach(review => { | ||||||
|  | 		let newReview = document.createElement("review-card"); | ||||||
|  | 		newReview.data = review; | ||||||
|  | 		//TODO: want to append it to whatever the box is in layout  | ||||||
|  | 		mainEl.append(newReview); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Adds the necessary event handlers to <form> and the clear storage | ||||||
|  |  * <button>. | ||||||
|  |  */ | ||||||
|  | function initFormHandler() { | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	//btn to create form (could be its own function?) | ||||||
|  | 	let createBtn = document.getElementById("create"); | ||||||
|  | 	createBtn.addEventListener("click", function(){ | ||||||
|  | 		window.location.assign("./CreatePage.html"); | ||||||
|  | 	});*/ | ||||||
|  |  | ||||||
|  | 	//accessing form components | ||||||
|  | 	let tagContainer = document.getElementById("tag-container-form"); | ||||||
|  | 	let form = document.querySelector("form"); | ||||||
|  |    | ||||||
|  | 	form.addEventListener("submit", function(){ | ||||||
|  | 	/* | ||||||
|  |     *  User submits the form for their review. | ||||||
|  |     *  We create reviewCard and put in storage | ||||||
|  |     */ | ||||||
|  | 		let formData = new FormData(form); | ||||||
|  | 		let reviewObject = {}; | ||||||
|  | 		for (let [key, value] of formData) { | ||||||
|  | 			console.log(`${key}`); | ||||||
|  | 			console.log(`${value}`); | ||||||
|  | 			if (`${key}` !== "tag-form") { | ||||||
|  | 				reviewObject[`${key}`] = `${value}`; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		reviewObject["tags"] = []; | ||||||
|  |  | ||||||
|  | 		let tags = document.querySelectorAll(".tag"); | ||||||
|  | 		for(let i = 0; i < tags.length; i ++) { | ||||||
|  | 			reviewObject["tags"].push(tags[i].innerHTML); | ||||||
|  | 			tagContainer.removeChild(tags[i]); | ||||||
|  | 		} | ||||||
|  |      | ||||||
|  |  | ||||||
|  | 		let newReview = document.createElement("review-card"); | ||||||
|  | 		newReview.data = reviewObject; | ||||||
|  |  | ||||||
|  | 		//TODO: want to append it to whatever the box is in layout  | ||||||
|  | 		let mainEl = document.querySelector("main"); | ||||||
|  | 		mainEl.append(newReview); | ||||||
|  |  | ||||||
|  | 		let storedReviews = getReviewsFromStorage(); | ||||||
|  | 		storedReviews.push(reviewObject); | ||||||
|  | 		saveReviewsToStorage(storedReviews); | ||||||
|  | 		document.getElementById("new-food-entry").reset(); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	// DEV-MODE: for testing purposes  | ||||||
|  | 	let clearBtn = document.querySelector(".danger"); | ||||||
|  | 	clearBtn.addEventListener("click", function() { | ||||||
|  | 		localStorage.clear(); | ||||||
|  | 		let mainEl = document.querySelector("main"); | ||||||
|  | 		while (mainEl.firstChild) { | ||||||
|  | 			mainEl.removeChild(mainEl.firstChild); | ||||||
|  | 		} | ||||||
|  | 		let deleteTags = document.querySelectorAll(".tag"); | ||||||
|  | 		for(let i = 0; i < deleteTags.length; i ++) { | ||||||
|  | 			tagContainer.removeChild(deleteTags[i]); | ||||||
|  | 		} | ||||||
|  |      | ||||||
|  | 		//clears reviews AS WELL as resets form | ||||||
|  | 		document.getElementById("new-food-entry").reset(); | ||||||
|  |       | ||||||
|  |       | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	let tagAddBtn = document.getElementById("tagAdd"); | ||||||
|  | 	tagAddBtn.addEventListener("click", ()=> { | ||||||
|  | 		let tagField = document.getElementById("tag-form"); | ||||||
|  | 		if (tagField.value.length > 0) { | ||||||
|  | 			let tagLabel = document.createElement("label"); | ||||||
|  | 			tagLabel.innerHTML = tagField.value; | ||||||
|  | 			tagLabel.setAttribute("class","tag"); | ||||||
|  | 			tagLabel.addEventListener("click",()=> { | ||||||
|  | 				tagContainer.removeChild(tagLabel); | ||||||
|  | 			}); | ||||||
|  |        | ||||||
|  | 			tagContainer.append(tagLabel); | ||||||
|  | 			tagField.value = ""; | ||||||
|  |  | ||||||
|  | 		} | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										79
									
								
								source/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,79 @@ | |||||||
|  |  | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | <head> | ||||||
|  |   <meta charset="UTF-8" /> | ||||||
|  |   <meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||||||
|  |   <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||||
|  |   <title>Food Journal</title> | ||||||
|  |  | ||||||
|  |   <!-- Recipe Card Custom Element --> | ||||||
|  |   <script src="assets/scripts/ReviewCard.js" type="module"></script> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   <!-- Main Stylesheets & Scripts -->  | ||||||
|  |   <!-- Temporarily commented out reset.css due to furthur discussion needed on the values of the default config--> | ||||||
|  |   <!-- <link rel="stylesheet" href="/static/reset.css" /> --> | ||||||
|  |   <link rel="stylesheet" href="./static/ReviewCard.css" /> | ||||||
|  |   <script src="assets/scripts/main.js" type="module"></script> | ||||||
|  |  | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |   <main> | ||||||
|  |     <!-- Add Food Entries Here --> | ||||||
|  |   </main> | ||||||
|  |   <!--<button type="button" id="create">CREATE</button>--> | ||||||
|  |   <!----> <form id="new-food-entry"> | ||||||
|  |     <fieldset> | ||||||
|  |       <legend>Pic:</legend> | ||||||
|  |       <label for="mealImage"> | ||||||
|  |         Source: | ||||||
|  |         <input type="text" id="mealImg" name="mealImg"> | ||||||
|  |       </label> | ||||||
|  |       <label for="image-alt"> | ||||||
|  |         Alt Text: | ||||||
|  |         <input type="text" id="imgAlt" name="imgAlt"> | ||||||
|  |       </label> | ||||||
|  |     </fieldset> | ||||||
|  |     <fieldset> | ||||||
|  |  | ||||||
|  |       <legend> Meal: </legend> | ||||||
|  |       <label for="Meal: ">Meal: | ||||||
|  |         <input type="text" id="mealName" name="mealName" required> | ||||||
|  |       </label> | ||||||
|  |       <label for="comments">Comments:  | ||||||
|  |         <br> | ||||||
|  |         <textarea name="comments" id="comments"></textarea> | ||||||
|  |       </label> | ||||||
|  |     </fieldset> | ||||||
|  |      | ||||||
|  |     <fieldset class="rating"> | ||||||
|  |       <legend> Rating: </legend> | ||||||
|  |       <input type="radio" id="s5" name="rating" value="5"/> <label for="s5"> 5 stars </label> | ||||||
|  |       <input type="radio" id="s4" name="rating" value="4"/> <label for="s4"> 4 stars </label> | ||||||
|  |       <input type="radio" id="s3" name="rating" value="3"/> <label for="s3"> 3 stars </label> | ||||||
|  |       <input type="radio" id="s2" name="rating" value="2"/> <label for="s2"> 2 stars </label> | ||||||
|  |       <input type="radio" id="s1" name="rating" value="1"/> <label for="s1"> 1 star </label> | ||||||
|  |     </fieldset> | ||||||
|  |  | ||||||
|  |     <fieldset> | ||||||
|  |       <legend>Other Info:</legend> | ||||||
|  |       <label for="restaurant"> | ||||||
|  |         Restaurant: | ||||||
|  |         <input type="text" id="restaurant" name="restaurant" required> | ||||||
|  |       </label> | ||||||
|  |       <label for="tag-form"> | ||||||
|  |         Tags: | ||||||
|  |         <input type="text" id="tag-form" name="tag-form"> | ||||||
|  |         <div class='tag-container' id="tag-container-form"> | ||||||
|  |  | ||||||
|  |         </div> | ||||||
|  |         <button type="button" id="tagAdd">Add Tag</button> | ||||||
|  |       </label> | ||||||
|  |  | ||||||
|  |     </fieldset> | ||||||
|  |     <button type="submit" value="Submit">Add Review</button> | ||||||
|  |     <button type="button" class="danger">Clear Review Journal</button> | ||||||
|  |   </form> <!----> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										16
									
								
								source/review.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,16 @@ | |||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | <head> | ||||||
|  |     <meta charset="UTF-8" /> | ||||||
|  |     <meta http-equiv="X-UA-Compatible" content="IE=edge" /> | ||||||
|  |     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||||
|  |     <title>Food Journal</title> | ||||||
|  |      | ||||||
|  |     <script src="assets/scripts/reviewpage.js" type="module"></script> | ||||||
|  | </head> | ||||||
|  | <body> | ||||||
|  |     <h1>Current Review:</h1> | ||||||
|  |     <main> | ||||||
|  |     </main> | ||||||
|  | </body> | ||||||
|  | </html> | ||||||
							
								
								
									
										19
									
								
								source/reviews.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,19 @@ | |||||||
|  | [ | ||||||
|  |    { | ||||||
|  |       "imgSrc": "https://cdn.vox-cdn.com/thumbor/Cj5J-5WqSCjlC2tWCOXEB536CJY=/0x0:1810x1182/1200x800/filters:focal(761x447:1049x735)/cdn.vox-cdn.com/uploads/chorus_image/image/69422966/Tacos_Lined_Up.0.png", | ||||||
|  |       "imgAlt": "tacos pic", | ||||||
|  |       "mealName": "Birria Tacos", | ||||||
|  |       "restaurant": "Mike's Red Tacos", | ||||||
|  |       "rating": 5, | ||||||
|  |       "tags": ["delicious", "#worthit","omg"] | ||||||
|  |    }, | ||||||
|  |    { | ||||||
|  |       "imgSrc": "https://www.redwormcomposting.com/images/worm-burrito.JPG", | ||||||
|  |       "imgAlt": "wolftown pic", | ||||||
|  |       "mealName": "Carnitas Burrito", | ||||||
|  |       "restaurant": "Wolftown UCSD", | ||||||
|  |       "rating": 0, | ||||||
|  |       "tags": ["gross", "why","no"] | ||||||
|  |  | ||||||
|  |    } | ||||||
|  | ] | ||||||
							
								
								
									
										83
									
								
								source/static/CreatePage.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,83 @@ | |||||||
|  | /* CreatePage.css */ | ||||||
|  |  | ||||||
|  | * { | ||||||
|  |   font-family: sans-serif; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body { | ||||||
|  |   height: 100%; | ||||||
|  |   width: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fieldset { | ||||||
|  |   border: 2px solid rgb(214 214 214); | ||||||
|  |   box-sizing: border-box; | ||||||
|  |   display: block; | ||||||
|  |   width: max-content; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | form button { | ||||||
|  |   display: block; | ||||||
|  |   margin-top: 5px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | label[for="ingredients"] p { | ||||||
|  |   margin: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | label[for="numRatings"] { | ||||||
|  |   margin: 10px 0 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | label[for^="rating"] { | ||||||
|  |   padding-right: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | label:not([for^="rating"]) { | ||||||
|  |   display: block; | ||||||
|  |   margin-bottom: 5px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | main { | ||||||
|  |   column-gap: 10px; | ||||||
|  |   display: flex; | ||||||
|  |   flex-wrap: wrap; | ||||||
|  |   height: auto; | ||||||
|  |   max-width: 660px; | ||||||
|  |   row-gap: 10px; | ||||||
|  |   width: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tag-container { | ||||||
|  |   display: flex; | ||||||
|  |   flex-flow: row wrap; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tag { | ||||||
|  |   background-color: grey; | ||||||
|  |   border-radius: 7px; | ||||||
|  |   color: white; | ||||||
|  |   padding-right: 7px; | ||||||
|  |   padding-left: 7px; | ||||||
|  |   margin: 3px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tag::before { | ||||||
|  |   display: inline-block; | ||||||
|  |   content: "x"; | ||||||
|  |   height: 15px; | ||||||
|  |   width: 15px; | ||||||
|  |   margin-right: 4px; | ||||||
|  |   text-align: center; | ||||||
|  |   color: white; | ||||||
|  |   cursor: pointer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tag:hover::before { | ||||||
|  |   color: red; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .danger { | ||||||
|  |   background-color: rgb(254 171 171); | ||||||
|  |   border-color: red; | ||||||
|  | } | ||||||
							
								
								
									
										83
									
								
								source/static/ReviewCard.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,83 @@ | |||||||
|  | /* main.css */ | ||||||
|  |  | ||||||
|  | * { | ||||||
|  |   font-family: sans-serif; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body { | ||||||
|  |   height: 100%; | ||||||
|  |   width: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fieldset { | ||||||
|  |   border: 2px solid rgb(214 214 214); | ||||||
|  |   box-sizing: border-box; | ||||||
|  |   display: block; | ||||||
|  |   width: max-content; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | form button { | ||||||
|  |   display: block; | ||||||
|  |   margin-top: 5px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | label[for="ingredients"] p { | ||||||
|  |   margin: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | label[for="numRatings"] { | ||||||
|  |   margin: 10px 0 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | label[for^="rating"] { | ||||||
|  |   padding-right: 10px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | label:not([for^="rating"]) { | ||||||
|  |   display: block; | ||||||
|  |   margin-bottom: 5px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | main { | ||||||
|  |   column-gap: 10px; | ||||||
|  |   display: flex; | ||||||
|  |   flex-wrap: wrap; | ||||||
|  |   height: auto; | ||||||
|  |   max-width: 660px; | ||||||
|  |   row-gap: 10px; | ||||||
|  |   width: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tag-container { | ||||||
|  |   display: flex; | ||||||
|  |   flex-flow: row wrap; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tag { | ||||||
|  |   background-color: grey; | ||||||
|  |   border-radius: 7px; | ||||||
|  |   color: white; | ||||||
|  |   padding-right: 7px; | ||||||
|  |   padding-left: 7px; | ||||||
|  |   margin: 3px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tag::before { | ||||||
|  |   display: inline-block; | ||||||
|  |   content: "x"; | ||||||
|  |   height: 15px; | ||||||
|  |   width: 15px; | ||||||
|  |   margin-right: 4px; | ||||||
|  |   text-align: center; | ||||||
|  |   color: white; | ||||||
|  |   cursor: pointer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .tag:hover::before { | ||||||
|  |   color: red; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .danger { | ||||||
|  |   background-color: rgb(254 171 171); | ||||||
|  |   border-color: red; | ||||||
|  | } | ||||||
							
								
								
									
										158
									
								
								source/static/reset.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,158 @@ | |||||||
|  | /* This is a generic CSS file that sets preliminary rules for content that should be the same across pages */ | ||||||
|  |  | ||||||
|  | html, | ||||||
|  | body, | ||||||
|  | div, | ||||||
|  | span, | ||||||
|  | object, | ||||||
|  | iframe, | ||||||
|  | h1, | ||||||
|  | h2, | ||||||
|  | h3, | ||||||
|  | h4, | ||||||
|  | h5, | ||||||
|  | h6, | ||||||
|  | p, | ||||||
|  | blockquote, | ||||||
|  | pre, | ||||||
|  | abbr, | ||||||
|  | address, | ||||||
|  | cite, | ||||||
|  | code, | ||||||
|  | del, | ||||||
|  | dfn, | ||||||
|  | em, | ||||||
|  | img, | ||||||
|  | ins, | ||||||
|  | kbd, | ||||||
|  | q, | ||||||
|  | samp, | ||||||
|  | small, | ||||||
|  | strong, | ||||||
|  | sub, | ||||||
|  | sup, | ||||||
|  | var, | ||||||
|  | b, | ||||||
|  | i, | ||||||
|  | dl, | ||||||
|  | dt, | ||||||
|  | dd, | ||||||
|  | ol, | ||||||
|  | ul, | ||||||
|  | li, | ||||||
|  | fieldset, | ||||||
|  | form, | ||||||
|  | label, | ||||||
|  | legend, | ||||||
|  | table, | ||||||
|  | caption, | ||||||
|  | tbody, | ||||||
|  | tfoot, | ||||||
|  | thead, | ||||||
|  | tr, | ||||||
|  | th, | ||||||
|  | td, | ||||||
|  | article, | ||||||
|  | aside, | ||||||
|  | canvas, | ||||||
|  | details, | ||||||
|  | figcaption, | ||||||
|  | figure, | ||||||
|  | footer, | ||||||
|  | header, | ||||||
|  | hgroup, | ||||||
|  | menu, | ||||||
|  | nav, | ||||||
|  | section, | ||||||
|  | summary, | ||||||
|  | time, | ||||||
|  | mark, | ||||||
|  | audio, | ||||||
|  | video { | ||||||
|  |   margin: 0; | ||||||
|  |   padding: 0; | ||||||
|  |   border: 0; | ||||||
|  |   outline: 0; | ||||||
|  |   font-size: 100%; | ||||||
|  |   vertical-align: baseline; | ||||||
|  |   background: transparent; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body { | ||||||
|  |   line-height: 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | article, | ||||||
|  | aside, | ||||||
|  | details, | ||||||
|  | figcaption, | ||||||
|  | figure, | ||||||
|  | footer, | ||||||
|  | header, | ||||||
|  | hgroup, | ||||||
|  | menu, | ||||||
|  | nav, | ||||||
|  | section { | ||||||
|  |   display: block; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | nav ul { | ||||||
|  |   list-style: none; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | blockquote, | ||||||
|  | q { | ||||||
|  |   quotes: none; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | blockquote::before, | ||||||
|  | blockquote::after, | ||||||
|  | q::before, | ||||||
|  | q::after { | ||||||
|  |   content: ""; | ||||||
|  |   content: none; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | a { | ||||||
|  |   margin: 0; | ||||||
|  |   padding: 0; | ||||||
|  |   font-size: 100%; | ||||||
|  |   vertical-align: baseline; | ||||||
|  |   background: transparent; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | table { | ||||||
|  |   border-collapse: collapse; | ||||||
|  |   border-spacing: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | input, | ||||||
|  | select { | ||||||
|  |   vertical-align: middle; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | img, | ||||||
|  | fieldset, | ||||||
|  | object { | ||||||
|  |   border: none; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | *, | ||||||
|  | *::after, | ||||||
|  | *::before { | ||||||
|  |   box-sizing: border-box; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | button, | ||||||
|  | label { | ||||||
|  |   cursor: pointer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | html, | ||||||
|  | body { | ||||||
|  |   height: 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | form { | ||||||
|  |   border: solid; | ||||||
|  | } | ||||||
| @@ -1,82 +0,0 @@ | |||||||
| /* This is a generic CSS file that sets preliminary rules for content that should be the same across pages */ |  | ||||||
|  |  | ||||||
| html, body, div, span, object, iframe, |  | ||||||
| h1, h2, h3, h4, h5, h6, p, blockquote, pre, |  | ||||||
| abbr, address, cite, code, |  | ||||||
| del, dfn, em, img, ins, kbd, q, samp, |  | ||||||
| small, strong, sub, sup, var, |  | ||||||
| b, i, |  | ||||||
| dl, dt, dd, ol, ul, li, |  | ||||||
| fieldset, form, label, legend, |  | ||||||
| table, caption, tbody, tfoot, thead, tr, th, td, |  | ||||||
| article, aside, canvas, details, figcaption, figure, |  | ||||||
| footer, header, hgroup, menu, nav, section, summary, |  | ||||||
| time, mark, audio, video { |  | ||||||
|   margin: 0; |  | ||||||
|   padding: 0; |  | ||||||
|   border: 0; |  | ||||||
|   outline: 0; |  | ||||||
|   font-size: 100%; |  | ||||||
|   vertical-align: baseline; |  | ||||||
|   background: transparent; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| body { |  | ||||||
|   line-height: 1; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| article,aside,details,figcaption,figure, |  | ||||||
| footer,header,hgroup,menu,nav,section { |  | ||||||
|   display: block; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| nav ul { |  | ||||||
|   list-style: none; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| blockquote, q { |  | ||||||
|   quotes: none; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| blockquote:before, blockquote:after, |  | ||||||
| q:before, q:after { |  | ||||||
|   content: ''; |  | ||||||
|   content: none; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| a { |  | ||||||
|   margin: 0; |  | ||||||
|   padding: 0; |  | ||||||
|   font-size: 100%; |  | ||||||
|   vertical-align: baseline; |  | ||||||
|   background: transparent; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| table { |  | ||||||
|   border-collapse: collapse; |  | ||||||
|   border-spacing: 0; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| input, select { |  | ||||||
|   vertical-align :middle; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| img, fieldset, object { |  | ||||||
|   border:none; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| *, *:after, *:before { |  | ||||||
|   box-sizing:border-box; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| button, label { |  | ||||||
|   cursor:pointer; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| html, body { |  | ||||||
|   height:100%; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| form { |  | ||||||
|   border |  | ||||||
| } |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| module.exports = {environment}; |  | ||||||
|  |  | ||||||
| function environment () { |  | ||||||
| 	const localStorageMock = (function () { |  | ||||||
| 		let store = {}; |  | ||||||
| 		return { |  | ||||||
| 			getItem(key) { |  | ||||||
| 				return store[key]; |  | ||||||
| 			}, |  | ||||||
| 			setItem(key, value) { |  | ||||||
| 				store[key] = value; |  | ||||||
| 			}, |  | ||||||
| 			clear() { |  | ||||||
| 				store = {}; |  | ||||||
| 			}, |  | ||||||
| 			removeItem(key) { |  | ||||||
| 				delete store[key]; |  | ||||||
| 			}, |  | ||||||
| 			getAll() { |  | ||||||
| 				return store; |  | ||||||
| 			}, |  | ||||||
| 		}; |  | ||||||
| 	})(); |  | ||||||
|  |  | ||||||
| 	let window = {}; |  | ||||||
| 	Object.defineProperty(window, "localStorage", { value: localStorageMock }); |  | ||||||
| 	return window; |  | ||||||
| } |  | ||||||
| @@ -1,81 +0,0 @@ | |||||||
| const {environment} = require("./testenv.js"); |  | ||||||
| var assert = require("assert"); |  | ||||||
| var {describe, it, beforeEach} = require("mocha"); |  | ||||||
| var {saveToLocal, getFromLocal, removeFromLocal, clearLocal} = require("./testenv_helpers"); |  | ||||||
|  |  | ||||||
| beforeEach(() => { |  | ||||||
| 	window = environment(); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| describe("test localStorage mock", () => { |  | ||||||
| 	it("test save and fetch", () => { |  | ||||||
| 		saveToLocal("testkey1", "testvalue1"); |  | ||||||
| 		saveToLocal("testkey2", "testvalue2"); |  | ||||||
| 		saveToLocal("testkey3", "testvalue3"); |  | ||||||
| 		saveToLocal("testkey4", "testvalue4"); |  | ||||||
|  |  | ||||||
| 		assert.equal(getFromLocal("testkey1"), "testvalue1"); |  | ||||||
| 		assert.equal(getFromLocal("testkey2"), "testvalue2"); |  | ||||||
| 		assert.equal(getFromLocal("testkey3"), "testvalue3"); |  | ||||||
| 		assert.equal(getFromLocal("testkey4"), "testvalue4"); |  | ||||||
|  |  | ||||||
| 		saveToLocal("testkey6", "testvalue5"); |  | ||||||
| 		assert.equal(getFromLocal("testkey6"), "testvalue5"); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	it("test window locality", () => { |  | ||||||
| 		assert.equal(getFromLocal("testkey1"), null); |  | ||||||
| 		assert.equal(getFromLocal("testkey2"), null); |  | ||||||
| 		assert.equal(getFromLocal("testkey3"), null); |  | ||||||
| 		assert.equal(getFromLocal("testkey4"), null); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	it("test delete and fetch", () => { |  | ||||||
| 		saveToLocal("testkey1", "testvalue1"); |  | ||||||
| 		saveToLocal("testkey2", "testvalue2"); |  | ||||||
| 		saveToLocal("testkey3", "testvalue3"); |  | ||||||
| 		saveToLocal("testkey4", "testvalue4"); |  | ||||||
|  |  | ||||||
| 		removeFromLocal("testkey3"); |  | ||||||
|  |  | ||||||
| 		assert.equal(getFromLocal("testkey1"), "testvalue1"); |  | ||||||
| 		assert.equal(getFromLocal("testkey2"), "testvalue2"); |  | ||||||
| 		assert.equal(getFromLocal("testkey3"), null); |  | ||||||
| 		assert.equal(getFromLocal("testkey4"), "testvalue4"); |  | ||||||
|  |  | ||||||
| 		removeFromLocal("testkey1"); |  | ||||||
|  |  | ||||||
| 		assert.equal(getFromLocal("testkey1"), null); |  | ||||||
| 		assert.equal(getFromLocal("testkey2"), "testvalue2"); |  | ||||||
| 		assert.equal(getFromLocal("testkey3"), null); |  | ||||||
| 		assert.equal(getFromLocal("testkey4"), "testvalue4"); |  | ||||||
|  |  | ||||||
| 		removeFromLocal("testkey4"); |  | ||||||
| 		 |  | ||||||
| 		assert.equal(getFromLocal("testkey1"), null); |  | ||||||
| 		assert.equal(getFromLocal("testkey2"), "testvalue2"); |  | ||||||
| 		assert.equal(getFromLocal("testkey3"), null); |  | ||||||
| 		assert.equal(getFromLocal("testkey4"), null); |  | ||||||
|  |  | ||||||
| 		removeFromLocal("testkey2"); |  | ||||||
| 		 |  | ||||||
| 		assert.equal(getFromLocal("testkey1"), null); |  | ||||||
| 		assert.equal(getFromLocal("testkey2"), null); |  | ||||||
| 		assert.equal(getFromLocal("testkey3"), null); |  | ||||||
| 		assert.equal(getFromLocal("testkey4"), null); |  | ||||||
| 	}); |  | ||||||
|  |  | ||||||
| 	it("test clear and fetch", () => { |  | ||||||
| 		saveToLocal("testkey1", "testvalue1"); |  | ||||||
| 		saveToLocal("testkey2", "testvalue2"); |  | ||||||
| 		saveToLocal("testkey3", "testvalue3"); |  | ||||||
| 		saveToLocal("testkey4", "testvalue4"); |  | ||||||
|  |  | ||||||
| 		clearLocal(); |  | ||||||
|  |  | ||||||
| 		assert.equal(getFromLocal("testkey1"), null); |  | ||||||
| 		assert.equal(getFromLocal("testkey2"), null); |  | ||||||
| 		assert.equal(getFromLocal("testkey3"), null); |  | ||||||
| 		assert.equal(getFromLocal("testkey4"), null); |  | ||||||
| 	}); |  | ||||||
| }); |  | ||||||
| @@ -1,17 +0,0 @@ | |||||||
| module.exports = {saveToLocal, getFromLocal, removeFromLocal, clearLocal}; |  | ||||||
|  |  | ||||||
| function saveToLocal (k, v) { |  | ||||||
| 	window.localStorage.setItem(k, v); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getFromLocal (k) { |  | ||||||
| 	return window.localStorage.getItem(k); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function removeFromLocal (k) { |  | ||||||
| 	window.localStorage.removeItem(k); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function clearLocal () { |  | ||||||
| 	window.localStorage.clear(); |  | ||||||
| }  |  | ||||||