Merge branch 'main' into add-app-to-readme

This commit is contained in:
Arthur Lu 2022-11-18 01:33:51 -08:00 committed by GitHub
commit 46cee253f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 2108 additions and 895 deletions

23
.github/workflows/css-linting.yml vendored Normal file
View 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
View 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

View File

@ -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
View File

@ -0,0 +1,3 @@
{
"attr-value-not-empty": false
}

3
.stylelintrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": "stylelint-config-standard"
}

View File

@ -31,6 +31,9 @@ So far the features listed below have been completed to some degree:
- Linting (JS) - Linting (JS)
- Implemented: ction triggers on any PR, uses eslint to perform style enforcement on all JS components - Implemented: ction triggers on any PR, uses eslint to perform style enforcement on all JS components
- ToDo: trigger workflow only on certain PRs which relate to JS code - ToDo: trigger workflow only on certain PRs which relate to JS code
- Linting (HTML)
- Implemented: action triggers on any PR, uses HTMLhint to perform style enforcement on all HTML components
- Linting (CSS)
- Implemented: action triggers on any PR, uses Stylelint to perform style enforcement on all CSS components
## Planned Features and Timeline ## Planned Features and Timeline

Binary file not shown.

View 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

View File

@ -55,6 +55,14 @@ Overall we feel that sprint 1 was a success with many lessons learned. Our enthu
* Scheduled meetings with more notice and keep meetings at a more central location so that more members can attend * Scheduled meetings with more notice and keep meetings at a more central location so that more members can attend
* Get the unit testing modules up to date * Get the unit testing modules up to date
* To-do: create a style guide * To-do: create a style guide
* Heed the styles and documentation (to avoid linter issues)
## Early Issues
* restructure local storage to store individual (key, review) pairs rather than storing data under one key (current schema)
* implement a file upload system (think canvas file upload)
* add a cuisine attribute for tagging and filtering
* Create UI buttons and low fidelity css
* Unit test all the above
## End Time ## End Time
- 11/14/2022 at 5:00PM - 11/14/2022 at 5:00PM

View File

@ -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

View File

@ -0,0 +1,33 @@
# Meeting Minutes (11/16/2022)
## Team 29: Hackers1995
## Meeting Topic: Weekly TA Catchup with Gagan
We are meeting with Gagan to discuss Checkpoint 1 and Sprint 2 resolutions.
## Attendance
1. Rhea Bhutada
2. George Dubinin
3. Gagan Gopalaiah
4. Kara Hoagland
## Meeting Details
- When: 11/16/2022 at 3:30PM
- Where: Zoom
## Agenda:
## Discussion Points by Gagan
- Updated Gagan on Sprint 1
- looked at Girhub actions
- looked at the published page so far
- discussed retrospective
- Upcoming Assignments
- we have to come up with a video on the status of our app
- ramp up the styling part, so u can brag about the design of the app
- this video is supposed to encourage healthy competition
- Other Concerns
- JSDocs - not primary concern right now
- GitHub Pages vs. Netlify
- Gagan sees Netlify as more professional and not to difficult to implement
## End Time
- 11/09/2022 at 3:45PM

View File

@ -0,0 +1,47 @@
# Meeting Minutes (11/07/2022)
## Team 29: Hackers1995
## Meeting Topic: First Sprint
Meeting notes for the first sprint
## Attendance
1. Rhea Bhutada
2. George Dubinin
3. Gavyn Ezell
4. Henry Feng
5. Kara Hoagland
6. Marc Reta
7. Sanjit Joseph
8. Daniel Hernandez
9. Arthur Lu
10. Isaac Otero
## Meeting Details
- When: 11/17/2022 at 11:30PM
- Where: Design & Innovation Building
## Agenda:
- ### Old/Unresolved Business
- N/A
- ### New Business
- Second sprint commences!
- Focus on design progress for the project showoff
- Cuisine vs Tag identifiers for reviews (both?)
- localStorage will hold:
- list of active IDs which is updated for very create operation. An ID uniquely identifies a review
- value, "nextId" denoting the index of the next available slot for an Id
- entries for every single review (javascript object)
- a list for every tag that denotes which Ids belong to reviews containing this tag
End2end tests will rely on specific html element names which include the following:
- "create-btn" (located on homepage and used to create a new review)
- "submit-btn" (located on form and used to post review)
- "update-btn" (located on a specific review page)
- "delete-btn" (located on a specific review page)
- "tag-add-btn" (located on the review create form)
- ### Next Meeting's Business
## Decisions Made
-
## End Time
- 11/17/2022 at 1:00PM

View File

@ -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
View 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
View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View 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

View 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

View 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

View 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

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

View 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);

View 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 = "";
}
});
}
});
}

View 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);
}

View 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));
}

View 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);
});

View 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
View 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
View 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
View 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"]
}
]

View File

View 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;
}

View 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
View 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;
}

View File

@ -0,0 +1,19 @@
# Use Stylelint for CSS linting framework
- Status: accept
- Deciders: Arthur Lu, Marc Reta
- Date: 11 / 14 / 22
## Decision Drivers
- Need linting to work with multiple style standards
- Need linting to be fast and informative
## Considered Options
- Stylelint
- Prettier
## Decision Outcome
Chosen Option: Stylelint for its easy installation and unopinionated.

View File

@ -0,0 +1,19 @@
# Use HTMLhint for HTML linting framework
- Status: accept
- Deciders: Arthur Lu, Marc Reta
- Date: 11 / 14 / 22
## Decision Drivers
- Need linting to work with multiple style standards
- Need linting to be fast and informative
## Considered Options
- HTMLhint
- HTML-validate
## Decision Outcome
Chosen Option: HTMLhint for its low configuration complexity.

View File

@ -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
}

View File

@ -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;
}

View File

@ -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);
});
});

View File

@ -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();
}