├── .gitignore ├── src ├── dots.png ├── enter.png ├── refresh.png ├── index.js ├── index.html ├── tasksList.js ├── style.css └── app.js ├── .babelrc ├── index.html ├── .hintrc ├── .stylelintrc.json ├── .eslintrc.json ├── server.js ├── webpack.config.js ├── README.md ├── package.json ├── dist ├── index.html └── main.js ├── tasksList.js ├── .github └── workflows │ └── linters.yml ├── feature.test.js ├── allFeatures.test.js └── app.js /.gitignore: -------------------------------------------------------------------------------- 1 | test.md 2 | node_modules 3 | -------------------------------------------------------------------------------- /src/dots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sediqullahbadakhsh/todo-list/HEAD/src/dots.png -------------------------------------------------------------------------------- /src/enter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sediqullahbadakhsh/todo-list/HEAD/src/enter.png -------------------------------------------------------------------------------- /src/refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sediqullahbadakhsh/todo-list/HEAD/src/refresh.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "plugins": ["@babel/plugin-transform-modules-commonjs"] 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './style.css'; 2 | 3 | import App from './app.js'; 4 | 5 | const theApp = new App(); 6 | theApp.displayTaskCards(); 7 | theApp.AddListeners(); 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.hintrc: -------------------------------------------------------------------------------- 1 | { 2 | "connector": { 3 | "name": "local", 4 | "options": { 5 | "pattern": ["**", "!.git/**", "!node_modules/**"] 6 | } 7 | }, 8 | "extends": ["development"], 9 | "formatters": ["stylish"], 10 | "hints": [ 11 | "button-type", 12 | "disown-opener", 13 | "html-checker", 14 | "meta-charset-utf-8", 15 | "meta-viewport", 16 | "no-inline-styles:error" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard"], 3 | "plugins": ["stylelint-scss", "stylelint-csstree-validator"], 4 | "rules": { 5 | "at-rule-no-unknown": null, 6 | "scss/at-rule-no-unknown": true, 7 | "csstree/validator": true 8 | }, 9 | "ignoreFiles": [ 10 | "build/**", 11 | "dist/**", 12 | "**/reset*.css", 13 | "**/bootstrap*.css", 14 | "**/*.js", 15 | "**/*.jsx" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "jest": true 6 | }, 7 | "parser": "babel-eslint", 8 | "parserOptions": { 9 | "ecmaVersion": 2018, 10 | "sourceType": "module" 11 | }, 12 | "extends": ["airbnb-base"], 13 | "rules": { 14 | "no-shadow": "off", 15 | "no-param-reassign": "off", 16 | "eol-last": "off", 17 | "import/extensions": [ 18 | 1, 19 | { 20 | "js": "always", 21 | "json": "always" 22 | } 23 | ] 24 | }, 25 | "ignorePatterns": ["dist/", "build/"] 26 | } 27 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | // const express = require('express'); 2 | // const webpack = require('webpack'); 3 | // const webpackDevMiddleware = require('webpack-dev-middleware'); 4 | 5 | // const app = express(); 6 | // const config = require('./webpack.config.js'); 7 | 8 | // const compiler = webpack(config); 9 | 10 | // // Tell express to use the webpack-dev-middleware and use the webpack.config.js 11 | // // configuration file as a base. 12 | // app.use( 13 | // webpackDevMiddleware(compiler, { 14 | // publicPath: config.output.publicPath, 15 | // }), 16 | // ); 17 | 18 | // // Serve the files on port 3000. 19 | // app.listen(3000, () => { 20 | // console.log('Example app listening on port 3000!\n'); 21 | // }); 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | mode: 'development', 6 | entry: { 7 | index: './src/index.js', 8 | }, 9 | devServer: { 10 | static: '.dist', 11 | }, 12 | plugins: [ 13 | new HtmlWebpackPlugin({ 14 | template: './src/index.html', 15 | }), 16 | ], 17 | output: { 18 | filename: 'main.js', 19 | path: path.resolve(__dirname, 'dist'), 20 | clean: true, 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.css$/i, 26 | use: ['style-loader', 'css-loader'], 27 | }, 28 | { 29 | test: /\.(png|svg|jpg|jpeg|gif)$/i, 30 | type: 'asset/resource', 31 | }, 32 | ], 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ToDo List 2 | 3 | > In this project, we can create our daily todo lists 4 | 5 | ## Built With 6 | 7 | - HTML 8 | - CSS 9 | - Javascript 10 | 11 | ## Live Demo 12 | 13 | - [Watch Project live](https://sediqullahbadakhsh.github.io/todo-list/dist/index.html) 14 | 15 | ## To get a local copy up and running follow these simple example steps. 16 | 17 | ### Setup 18 | 19 | Clone the repository using `git clone` and pasting the following link [git@github.com:sediqullahbadakhsh/todo-list.git](git@github.com:sediqullahbadakhsh/todo-list.git). 20 | 21 | ### Install 22 | 23 | Run the command `npm install` to install all project's dependencies. 24 | 25 | ### Deployment 26 | 27 | Run `npm run build` to get the dist folder with the HTML and JS files in it 28 | Run the command line `npm start`, the basic template will automatically open the index.html file in a browser. 29 | Distribution files are generated by webpack and served by webpack dev server from /dist folder. 30 | 31 | ## Author 32 | 33 | 👤 **Sediqullah Badakhsh** 34 | 35 | - GitHub: [@Badakhsh](https://github.com/sediqullahbadakhsh) 36 | 37 | ## 🤝 Contributing 38 | 39 | Contributions, issues, and feature requests are welcome! 40 | 41 | ## Show your support 42 | 43 | Give a ⭐️ if you like this project! 44 | 45 | ## Acknowledgments 46 | 47 | - A special thank for @fernandorpm for this amazing [README template](https://github.com/microverseinc/readme-template) 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo-list", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "scripts": { 7 | "test": "jest", 8 | "start": "webpack serve --open", 9 | "server": "node server.js", 10 | "build": "webpack" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/sediqullahbadakhsh/todo-list.git" 15 | }, 16 | "keywords": [], 17 | "author": "", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/sediqullahbadakhsh/todo-list/issues" 21 | }, 22 | "homepage": "https://github.com/sediqullahbadakhsh/todo-list#readme", 23 | "devDependencies": { 24 | "babel-eslint": "^10.1.0", 25 | "css-loader": "^6.6.0", 26 | "eslint": "^7.32.0", 27 | "eslint-config-airbnb-base": "^14.2.1", 28 | "eslint-plugin-import": "^2.25.4", 29 | "express": "^4.17.2", 30 | "html-webpack-plugin": "^5.5.0", 31 | "jest": "^27.5.1", 32 | "style-loader": "^3.3.1", 33 | "stylelint": "^13.13.1", 34 | "stylelint-config-standard": "^21.0.0", 35 | "stylelint-csstree-validator": "^1.9.0", 36 | "stylelint-scss": "^3.21.0", 37 | "webpack": "^5.69.0", 38 | "webpack-cli": "^4.9.2", 39 | "webpack-dev-middleware": "^5.3.1" 40 | }, 41 | "dependencies": { 42 | "@babel/core": "^7.17.5", 43 | "@babel/preset-env": "^7.16.11", 44 | "babel-jest": "^27.5.1", 45 | "lodash": "^4.17.21" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ToDo List 8 | 9 | 10 | 11 |
12 |
13 |
14 |

Today's To Do

15 | 18 |
19 |
20 | 25 | 28 |
29 | 43 |
44 | 45 |
46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | ToDo List 8 | 9 | 10 | 11 |
12 |
13 |
14 |

Today's To Do

15 | 18 |
19 |
20 | 25 | 28 |
29 | 43 |
44 | 45 |
46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /tasksList.js: -------------------------------------------------------------------------------- 1 | class TaskList { 2 | constructor() { 3 | this.taskListArray = this.loadDataFromStorage(); 4 | } 5 | 6 | loadDataFromStorage() { 7 | this.taskListArray = JSON.parse(window.localStorage.getItem('TODOLIST')); 8 | if (!this.taskListArray) this.taskListArray = []; 9 | return this.taskListArray; 10 | } 11 | 12 | saveDataToStorage() { 13 | window.localStorage.setItem('TODOLIST', JSON.stringify(this.taskListArray)); 14 | } 15 | 16 | addNewTask(description) { 17 | this.taskListArray.push({ 18 | index: this.taskListArray.length + 1, 19 | completed: false, 20 | description, 21 | }); 22 | } 23 | 24 | deleteTask(index) { 25 | this.taskListArray = this.taskListArray.filter((e) => e.index !== Number(index)); 26 | this.#reorderIndexes(); 27 | } 28 | 29 | updateTaskDescription(index, description) { 30 | this.taskListArray[index - 1].description = description; 31 | this.saveDataToStorage(); 32 | } 33 | 34 | updateTaskStatus(index, status) { 35 | this.taskListArray[index - 1].completed = status; 36 | this.saveDataToStorage(); 37 | } 38 | 39 | clearAllCompleted() { 40 | this.taskListArray = this.taskListArray.filter((e) => !e.completed); 41 | this.#reorderIndexes(); 42 | this.saveDataToStorage(); 43 | } 44 | 45 | getDescription(index) { 46 | return this.taskListArray[index - 1].description; 47 | } 48 | 49 | getStatus(index) { 50 | return this.taskListArray[index - 1].completed; 51 | } 52 | 53 | swapPositions(lID, rID) { 54 | let indexA = 0; 55 | let indexB = 0; 56 | for (let i = 0; i < this.taskListArray.length; i += 1) { 57 | if (this.taskListArray[i].index === Number(lID)) indexA = i; 58 | if (this.taskListArray[i].index === Number(rID)) indexB = i; 59 | } 60 | const tempo = this.taskListArray[indexA]; 61 | this.taskListArray[indexA] = this.taskListArray[indexB]; 62 | this.taskListArray[indexB] = tempo; 63 | this.#reorderIndexes(); 64 | this.saveDataToStorage(); 65 | } 66 | 67 | #reorderIndexes() { 68 | let index = 1; 69 | this.taskListArray.forEach((e) => { 70 | e.index = index; 71 | index += 1; 72 | }); 73 | } 74 | } 75 | 76 | export default TaskList; -------------------------------------------------------------------------------- /.github/workflows/linters.yml: -------------------------------------------------------------------------------- 1 | name: Linters 2 | 3 | on: pull_request 4 | 5 | env: 6 | FORCE_COLOR: 1 7 | 8 | jobs: 9 | lighthouse: 10 | name: Lighthouse 11 | runs-on: ubuntu-18.04 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-node@v1 15 | with: 16 | node-version: "12.x" 17 | - name: Setup Lighthouse 18 | run: npm install -g @lhci/cli@0.7.x 19 | - name: Lighthouse Report 20 | run: lhci autorun --upload.target=temporary-public-storage --collect.staticDistDir=. 21 | webhint: 22 | name: Webhint 23 | runs-on: ubuntu-18.04 24 | steps: 25 | - uses: actions/checkout@v2 26 | - uses: actions/setup-node@v1 27 | with: 28 | node-version: "12.x" 29 | - name: Setup Webhint 30 | run: | 31 | npm install --save-dev hint@6.x 32 | [ -f .hintrc ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/html-css-js/.hintrc 33 | - name: Webhint Report 34 | run: npx hint . 35 | stylelint: 36 | name: Stylelint 37 | runs-on: ubuntu-18.04 38 | steps: 39 | - uses: actions/checkout@v2 40 | - uses: actions/setup-node@v1 41 | with: 42 | node-version: "12.x" 43 | - name: Setup Stylelint 44 | run: | 45 | npm install --save-dev stylelint@13.x stylelint-scss@3.x stylelint-config-standard@21.x stylelint-csstree-validator@1.x 46 | [ -f .stylelintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/html-css-js/.stylelintrc.json 47 | - name: Stylelint Report 48 | run: npx stylelint "**/*.{css,scss}" 49 | eslint: 50 | name: ESLint 51 | runs-on: ubuntu-18.04 52 | steps: 53 | - uses: actions/checkout@v2 54 | - uses: actions/setup-node@v1 55 | with: 56 | node-version: "12.x" 57 | - name: Setup ESLint 58 | run: | 59 | npm install --save-dev eslint@7.x eslint-config-airbnb-base@14.x eslint-plugin-import@2.x babel-eslint@10.x 60 | [ -f .eslintrc.json ] || wget https://raw.githubusercontent.com/microverseinc/linters-config/master/html-css-js/.eslintrc.json 61 | - name: ESLint Report 62 | run: npx eslint . 63 | -------------------------------------------------------------------------------- /src/tasksList.js: -------------------------------------------------------------------------------- 1 | class TaskList { 2 | constructor() { 3 | this.taskListArray = this.loadDataFromStorage(); 4 | } 5 | 6 | loadDataFromStorage() { 7 | this.taskListArray = JSON.parse(window.localStorage.getItem('TODOLIST')); 8 | if (!this.taskListArray) this.taskListArray = []; 9 | return this.taskListArray; 10 | } 11 | 12 | saveDataToStorage() { 13 | window.localStorage.setItem('TODOLIST', JSON.stringify(this.taskListArray)); 14 | } 15 | 16 | addNewTask(description) { 17 | this.taskListArray.push({ 18 | index: this.taskListArray.length + 1, 19 | completed: false, 20 | description, 21 | }); 22 | } 23 | 24 | deleteTask(index) { 25 | this.taskListArray = this.taskListArray.filter( 26 | (e) => e.index !== Number(index), 27 | ); 28 | this.#reorderIndexes(); 29 | } 30 | 31 | updateTaskDescription(index, description) { 32 | this.taskListArray[index - 1].description = description; 33 | this.saveDataToStorage(); 34 | } 35 | 36 | updateTaskStatus(index, status) { 37 | this.taskListArray[index - 1].completed = status; 38 | this.saveDataToStorage(); 39 | } 40 | 41 | clearAllCompleted() { 42 | this.taskListArray = this.taskListArray.filter((e) => !e.completed); 43 | this.#reorderIndexes(); 44 | this.saveDataToStorage(); 45 | } 46 | 47 | getDescription(index) { 48 | return this.taskListArray[index - 1].description; 49 | } 50 | 51 | getStatus(index) { 52 | return this.taskListArray[index - 1].completed; 53 | } 54 | 55 | swapPositions(lID, rID) { 56 | let indexA = 0; 57 | let indexB = 0; 58 | for (let i = 0; i < this.taskListArray.length; i += 1) { 59 | if (this.taskListArray[i].index === Number(lID)) indexA = i; 60 | if (this.taskListArray[i].index === Number(rID)) indexB = i; 61 | } 62 | const tempo = this.taskListArray[indexA]; 63 | this.taskListArray[indexA] = this.taskListArray[indexB]; 64 | this.taskListArray[indexB] = tempo; 65 | this.#reorderIndexes(); 66 | this.saveDataToStorage(); 67 | } 68 | 69 | #reorderIndexes() { 70 | let index = 1; 71 | this.taskListArray.forEach((e) => { 72 | e.index = index; 73 | index += 1; 74 | }); 75 | } 76 | } 77 | 78 | export default TaskList; 79 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | header { 2 | height: 50px; 3 | background-color: black; 4 | width: 100%; 5 | position: absolute; 6 | top: 0%; 7 | } 8 | 9 | /* CONTAINER STARTS HERE */ 10 | body { 11 | background-color: rgb(206, 206, 206); 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: center; 16 | padding: 90px 0; 17 | font-size: 14px; 18 | font-weight: normal; 19 | } 20 | 21 | .main-container { 22 | width: 70%; 23 | background-color: white; 24 | box-shadow: 5px 5px 5px 5px gray; 25 | } 26 | 27 | button { 28 | border: none; 29 | background-color: white; 30 | } 31 | 32 | img { 33 | width: 20px; 34 | height: auto; 35 | } 36 | 37 | .dots { 38 | width: auto; 39 | height: 30px; 40 | padding-right: 10px; 41 | } 42 | 43 | .row-header { 44 | display: flex; 45 | flex-direction: row; 46 | justify-content: space-between; 47 | padding: 0 10px; 48 | margin: 0; 49 | border-bottom: solid 1px rgb(151, 151, 151); 50 | height: 40px; 51 | } 52 | 53 | p { 54 | font-size: 14px; 55 | font-weight: normal; 56 | } 57 | 58 | .row-input { 59 | display: flex; 60 | flex-direction: row; 61 | justify-content: space-between; 62 | padding: 0 10px; 63 | margin: 0; 64 | border-bottom: solid 1px rgb(151, 151, 151); 65 | height: 40px; 66 | } 67 | 68 | input { 69 | border: none; 70 | } 71 | 72 | .todo-input { 73 | width: 100%; 74 | } 75 | 76 | .task-container { 77 | display: flex; 78 | flex-direction: column; 79 | margin: 0; 80 | } 81 | 82 | .row-task { 83 | display: flex; 84 | flex-direction: row; 85 | justify-content: space-between; 86 | align-items: center; 87 | border-bottom: solid 1px rgb(151, 151, 151); 88 | height: 40px; 89 | } 90 | 91 | .task { 92 | gap: 5px; 93 | padding-left: 10px; 94 | } 95 | 96 | .buttons { 97 | display: flex; 98 | flex-direction: row; 99 | gap: 3px; 100 | } 101 | 102 | .clear-completed { 103 | color: rgb(107, 107, 107); 104 | background-color: rgb(231, 231, 231); 105 | height: 40px; 106 | text-align: center; 107 | width: 100%; 108 | } 109 | 110 | /* CONTAINER ENDS HERE */ 111 | 112 | footer { 113 | position: absolute; 114 | bottom: 0%; 115 | width: 100%; 116 | height: 50px; 117 | background-color: black; 118 | } 119 | -------------------------------------------------------------------------------- /feature.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | import TaskList from './src/tasksList.js'; 5 | import App from './src/app.js'; 6 | 7 | describe('localstorage', () => { 8 | test('Add', () => { 9 | const taskList = new TaskList(); 10 | taskList.addNewTask('item 1'); 11 | taskList.addNewTask('item 2'); 12 | expect(taskList.taskListArray.length).toBe(2); 13 | }); 14 | test('Delete', () => { 15 | const taskList = new TaskList(); 16 | taskList.addNewTask('item 1'); 17 | taskList.addNewTask('item 2'); 18 | taskList.deleteTask(1); 19 | expect(taskList.taskListArray.length).toBe(1); 20 | }); 21 | }); 22 | 23 | describe('DOM manipulation', () => { 24 | test('Add to list', () => { 25 | document.body.innerHTML = ` 26 | 38 |
39 |
40 | `; 41 | const app = new App(); 42 | app.taskList.addNewTask('item 1'); 43 | app.taskList.addNewTask('item 2'); 44 | app.taskList.addNewTask('item 3'); 45 | app.taskList.addNewTask('item 4'); 46 | app.displayTaskCards(); 47 | const tasks = document.body.querySelectorAll('.row-task'); 48 | expect(tasks.length).toBe(4); 49 | }); 50 | 51 | test('remove from list', () => { 52 | document.body.innerHTML = ` 53 | 65 |
66 |
67 | `; 68 | window.localStorage.clear(); 69 | const app = new App(); 70 | app.taskList.addNewTask('item 1'); 71 | app.taskList.addNewTask('item 2'); 72 | app.taskList.addNewTask('item 3'); 73 | app.taskList.addNewTask('item 4'); 74 | app.taskList.deleteTask(2); 75 | app.taskList.deleteTask(3); 76 | app.displayTaskCards(); 77 | const tasks = document.body.querySelectorAll('.row-task'); 78 | expect(tasks.length).toBe(2); 79 | }); 80 | }); -------------------------------------------------------------------------------- /allFeatures.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | import TaskList from './src/tasksList.js'; 5 | import App from './src/app.js'; 6 | // local Storage 7 | describe('update Status in local storage', () => { 8 | test('update task description', () => { 9 | const taskList = new TaskList(); 10 | taskList.addNewTask('New Task'); 11 | taskList.updateTaskDescription(1, 'edited task'); 12 | expect(taskList.getDescription(1)).toBe('edited task'); 13 | }); 14 | 15 | test('update task completed status', () => { 16 | const taskList = new TaskList(); 17 | taskList.addNewTask('New Task'); 18 | taskList.updateTaskStatus(1, true); 19 | expect(taskList.getStatus(1)).toBeTruthy(); 20 | }); 21 | 22 | test('Clear all completed from Local Storage', () => { 23 | window.localStorage.clear(); 24 | const taskList = new TaskList(); 25 | taskList.addNewTask('New Task1'); 26 | taskList.addNewTask('New Task2'); 27 | taskList.updateTaskStatus(1, true); 28 | taskList.clearAllCompleted(); 29 | const size = taskList.taskListArray.length; 30 | expect(size).toBe(1); 31 | }); 32 | }); 33 | 34 | describe('update status in the DOM', () => { 35 | test('updates task description in the DOM', () => { 36 | window.localStorage.clear(); 37 | document.body.innerHTML = ` 38 | 50 |
51 |
52 | `; 53 | const app = new App(); 54 | app.taskList.addNewTask('item 1'); 55 | app.taskList.addNewTask('item 2'); 56 | app.taskList.addNewTask('item 3'); 57 | app.taskList.addNewTask('item 4'); 58 | app.displayTaskCards(); 59 | app.taskList.updateTaskDescription(2, 'edit item 2'); 60 | app.displayTaskCards(); 61 | const tasks = document.body.querySelectorAll('.row-task'); 62 | const description = tasks[1].querySelector('.task-description').value; 63 | expect(description).toBe('edit item 2'); 64 | }); 65 | 66 | test('update task completed (Checkbox is checked)', () => { 67 | document.body.innerHTML = ` 68 | 80 |
81 |
82 | `; 83 | window.localStorage.clear(); 84 | const app = new App(); 85 | app.taskList.addNewTask('Item 1'); 86 | app.taskList.addNewTask('Item 2'); 87 | app.taskList.addNewTask('Item 3'); 88 | app.taskList.addNewTask('Item 4'); 89 | app.displayTaskCards(); 90 | app.taskList.updateTaskStatus(1, true); 91 | app.displayTaskCards(); 92 | const tasks = document.body.querySelectorAll('.row-task'); 93 | const status = tasks[0].querySelector('.check').checked; 94 | expect(status).toBeTruthy(); 95 | }); 96 | 97 | test('Clear all completed from the DOM', () => { 98 | document.body.innerHTML = ` 99 | 111 |
112 |
113 | `; 114 | window.localStorage.clear(); 115 | const app = new App(); 116 | app.taskList.addNewTask('Item 1'); 117 | app.taskList.addNewTask('Item 2'); 118 | app.taskList.addNewTask('Item 3'); 119 | app.taskList.addNewTask('Item 4'); 120 | app.displayTaskCards(); 121 | app.taskList.updateTaskStatus(1, true); 122 | app.taskList.updateTaskStatus(3, true); 123 | app.displayTaskCards(); 124 | app.taskList.clearAllCompleted(); 125 | app.displayTaskCards(); 126 | const tasksCardCount = document.body.querySelectorAll('.row-task').length; 127 | expect(tasksCardCount).toBe(2); 128 | }); 129 | }); -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import TaskList from './tasksList.js'; 2 | 3 | class App { 4 | constructor() { 5 | this.taskList = new TaskList(); 6 | } 7 | 8 | displayTaskCards() { 9 | this.taskList.saveDataToStorage(); 10 | const taskContainer = document.querySelector('.task-container'); 11 | const taskTemplate = document.getElementById('task-template'); 12 | taskContainer.innerHTML = ''; 13 | this.taskList.taskListArray.forEach((task) => { 14 | const taskCard = taskTemplate.content.cloneNode(true).children[0]; 15 | const checkBox = taskCard.querySelector('.check'); 16 | const descriptionInput = taskCard.querySelector('.task-description'); 17 | const taskButton = taskCard.querySelector('.row-task button'); 18 | const deleteButton = taskCard.querySelector('.delete-btn'); 19 | checkBox.checked = task.completed ? 'checked' : ''; 20 | checkBox.addEventListener('change', (e) => { 21 | const grandParent = e.target.parentNode.parentNode; 22 | if (e.target.checked) { 23 | grandParent.querySelector('.task-description').style.textDecoration = 'line-through'; 24 | } else { 25 | grandParent.querySelector('.task-description').style.textDecoration = 'none'; 26 | } 27 | this.taskList.updateTaskStatus(grandParent.querySelector('.delete-btn').dataset.id, e.target.checked); 28 | }); 29 | descriptionInput.value = task.description; 30 | if (task.completed) { 31 | descriptionInput.style.textDecoration = 'line-through'; 32 | } else { 33 | descriptionInput.style.textDecoration = 'none'; 34 | } 35 | taskButton.setAttribute('data-id', task.index); 36 | deleteButton.setAttribute('data-id', task.index); 37 | descriptionInput.addEventListener('input', (e) => { 38 | const grandParent = e.target.parentNode.parentNode; 39 | grandParent.style.backgroundColor = 'lightgoldenrodyellow'; 40 | grandParent.querySelector('.delete-btn').style.display = 'block'; 41 | grandParent.querySelector('.move-btn').style.display = 'none'; 42 | }); 43 | 44 | descriptionInput.addEventListener('blur', (e) => { 45 | const grandParent = e.target.parentNode.parentNode; 46 | grandParent.style.backgroundColor = 'inherit'; 47 | const index = e.target.parentNode.parentNode.querySelector('.delete-btn').dataset.id; 48 | this.taskList.updateTaskDescription(index, e.target.value); 49 | setTimeout(() => { 50 | grandParent.querySelector('.delete-btn').style.display = 'none'; 51 | grandParent.querySelector('.move-btn').style.display = 'block'; 52 | }, 200); 53 | }); 54 | deleteButton.addEventListener('click', (e) => { 55 | this.taskList.deleteTask(e.target.dataset.id); 56 | this.displayTaskCards(); 57 | }); 58 | taskContainer.appendChild(taskCard); 59 | }); 60 | } 61 | 62 | AddListeners() { 63 | document.querySelector('.row-input input').addEventListener('keypress', (e) => { 64 | if (e.code === 'Enter') { 65 | if (e.target.value === '') return; 66 | this.taskList.addNewTask(e.target.value); 67 | this.displayTaskCards(); 68 | e.target.value = ''; 69 | e.target.focus(); 70 | } 71 | }); 72 | document.querySelector('.clear-completed').addEventListener('click', () => { 73 | this.taskList.clearAllCompleted(); 74 | this.displayTaskCards(); 75 | }); 76 | document.addEventListener('dragstart', (e) => { 77 | // Event handlers for card's drag operations 78 | if (e.target.matches('.row-task')) { 79 | e.target.style.opacity = '0.4'; 80 | this.dragSourceElement = e.target; 81 | this.dragSourceID = e.target.querySelector('.move-btn').dataset.id; 82 | } 83 | }); 84 | document.addEventListener('dragend', (e) => { 85 | if (e.target.matches('.row-task')) { 86 | e.target.style.opacity = '1'; 87 | e.target.classList.remove('over'); 88 | } 89 | }); 90 | 91 | document.addEventListener('dragenter', (e) => { 92 | if (e.target.matches('.row-task')) { 93 | e.target.classList.add('over'); 94 | } 95 | }); 96 | 97 | document.addEventListener('dragleave', (e) => { 98 | if (e.target.matches('.row-task')) { 99 | e.target.classList.remove('over'); 100 | } 101 | }); 102 | document.addEventListener('dragover', (e) => { 103 | e.preventDefault(); 104 | }); 105 | 106 | document.addEventListener('drop', (e) => { 107 | e.preventDefault(); 108 | if (e.target.matches('.row-task')) { 109 | e.stopPropagation(); 110 | if (this.dragSourceElement !== e.target) { 111 | this.dragTargetID = e.target.querySelector('.move-btn').dataset.id; 112 | this.taskList.swapPositions(this.dragTargetID, this.dragSourceID); 113 | e.target.classList.remove('over'); 114 | this.displayTaskCards(); 115 | } 116 | } 117 | }); 118 | } 119 | } 120 | 121 | export default App; -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | import TaskList from './tasksList.js'; 2 | 3 | class App { 4 | constructor() { 5 | this.taskList = new TaskList(); 6 | } 7 | 8 | displayTaskCards() { 9 | this.taskList.saveDataToStorage(); 10 | const taskContainer = document.querySelector('.task-container'); 11 | const taskTemplate = document.getElementById('task-template'); 12 | taskContainer.innerHTML = ''; 13 | this.taskList.taskListArray.forEach((task) => { 14 | const taskCard = taskTemplate.content.cloneNode(true).children[0]; 15 | const checkBox = taskCard.querySelector('.check'); 16 | const descriptionInput = taskCard.querySelector('.task-description'); 17 | const taskButton = taskCard.querySelector('.row-task button'); 18 | const deleteButton = taskCard.querySelector('.delete-btn'); 19 | checkBox.checked = task.completed ? 'checked' : ''; 20 | checkBox.addEventListener('change', (e) => { 21 | const grandParent = e.target.parentNode.parentNode; 22 | if (e.target.checked) { 23 | grandParent.querySelector('.task-description').style.textDecoration = 'line-through'; 24 | } else { 25 | grandParent.querySelector('.task-description').style.textDecoration = 'none'; 26 | } 27 | this.taskList.updateTaskStatus( 28 | grandParent.querySelector('.delete-btn').dataset.id, 29 | e.target.checked, 30 | ); 31 | }); 32 | descriptionInput.value = task.description; 33 | if (task.completed) { 34 | descriptionInput.style.textDecoration = 'line-through'; 35 | } else { 36 | descriptionInput.style.textDecoration = 'none'; 37 | } 38 | taskButton.setAttribute('data-id', task.index); 39 | deleteButton.setAttribute('data-id', task.index); 40 | descriptionInput.addEventListener('input', (e) => { 41 | const grandParent = e.target.parentNode.parentNode; 42 | grandParent.style.backgroundColor = 'lightgoldenrodyellow'; 43 | grandParent.querySelector('.delete-btn').style.display = 'block'; 44 | grandParent.querySelector('.move-btn').style.display = 'none'; 45 | }); 46 | 47 | descriptionInput.addEventListener('blur', (e) => { 48 | const grandParent = e.target.parentNode.parentNode; 49 | grandParent.style.backgroundColor = 'inherit'; 50 | const index = e.target.parentNode.parentNode.querySelector('.delete-btn').dataset 51 | .id; 52 | this.taskList.updateTaskDescription(index, e.target.value); 53 | setTimeout(() => { 54 | grandParent.querySelector('.delete-btn').style.display = 'none'; 55 | grandParent.querySelector('.move-btn').style.display = 'block'; 56 | }, 200); 57 | }); 58 | deleteButton.addEventListener('click', (e) => { 59 | this.taskList.deleteTask(e.target.dataset.id); 60 | this.displayTaskCards(); 61 | }); 62 | taskContainer.appendChild(taskCard); 63 | }); 64 | } 65 | 66 | AddListeners() { 67 | document 68 | .querySelector('.row-input input') 69 | .addEventListener('keypress', (e) => { 70 | if (e.code === 'Enter') { 71 | if (e.target.value === '') return; 72 | this.taskList.addNewTask(e.target.value); 73 | this.displayTaskCards(); 74 | e.target.value = ''; 75 | e.target.focus(); 76 | } 77 | }); 78 | document.querySelector('.clear-completed').addEventListener('click', () => { 79 | this.taskList.clearAllCompleted(); 80 | this.displayTaskCards(); 81 | }); 82 | document.addEventListener('dragstart', (e) => { 83 | // Event handlers for card's drag operations 84 | if (e.target.matches('.row-task')) { 85 | e.target.style.opacity = '0.4'; 86 | this.dragSourceElement = e.target; 87 | this.dragSourceID = e.target.querySelector('.move-btn').dataset.id; 88 | } 89 | }); 90 | document.addEventListener('dragend', (e) => { 91 | if (e.target.matches('.row-task')) { 92 | e.target.style.opacity = '1'; 93 | e.target.classList.remove('over'); 94 | } 95 | }); 96 | 97 | document.addEventListener('dragenter', (e) => { 98 | if (e.target.matches('.row-task')) { 99 | e.target.classList.add('over'); 100 | } 101 | }); 102 | 103 | document.addEventListener('dragleave', (e) => { 104 | if (e.target.matches('.row-task')) { 105 | e.target.classList.remove('over'); 106 | } 107 | }); 108 | document.addEventListener('dragover', (e) => { 109 | e.preventDefault(); 110 | }); 111 | 112 | document.addEventListener('drop', (e) => { 113 | e.preventDefault(); 114 | if (e.target.matches('.row-task')) { 115 | e.stopPropagation(); 116 | if (this.dragSourceElement !== e.target) { 117 | this.dragTargetID = e.target.querySelector('.move-btn').dataset.id; 118 | this.taskList.swapPositions(this.dragTargetID, this.dragSourceID); 119 | e.target.classList.remove('over'); 120 | this.displayTaskCards(); 121 | } 122 | } 123 | }); 124 | } 125 | } 126 | 127 | export default App; 128 | -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development"). 3 | * This devtool is neither made for production nor for readable output files. 4 | * It uses "eval()" calls to create a separate source file in the browser devtools. 5 | * If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/) 6 | * or disable the default devtool with "devtool: false". 7 | * If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/). 8 | */ 9 | /******/ (() => { // webpackBootstrap 10 | /******/ "use strict"; 11 | /******/ var __webpack_modules__ = ({ 12 | 13 | /***/ "./node_modules/css-loader/dist/cjs.js!./src/style.css": 14 | /*!*************************************************************!*\ 15 | !*** ./node_modules/css-loader/dist/cjs.js!./src/style.css ***! 16 | \*************************************************************/ 17 | /***/ ((module, __webpack_exports__, __webpack_require__) => { 18 | 19 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../node_modules/css-loader/dist/runtime/noSourceMaps.js */ \"./node_modules/css-loader/dist/runtime/noSourceMaps.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../node_modules/css-loader/dist/runtime/api.js */ \"./node_modules/css-loader/dist/runtime/api.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1__);\n// Imports\n\n\nvar ___CSS_LOADER_EXPORT___ = _node_modules_css_loader_dist_runtime_api_js__WEBPACK_IMPORTED_MODULE_1___default()((_node_modules_css_loader_dist_runtime_noSourceMaps_js__WEBPACK_IMPORTED_MODULE_0___default()));\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"header {\\r\\n height: 50px;\\r\\n background-color: black;\\r\\n width: 100%;\\r\\n position: absolute;\\r\\n top: 0%;\\r\\n}\\r\\n\\r\\n/* CONTAINER STARTS HERE */\\r\\nbody {\\r\\n background-color: rgb(206, 206, 206);\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n align-items: center;\\r\\n justify-content: center;\\r\\n padding: 90px 0;\\r\\n font-size: 14px;\\r\\n font-weight: normal;\\r\\n}\\r\\n\\r\\n.main-container {\\r\\n width: 70%;\\r\\n background-color: white;\\r\\n box-shadow: 5px 5px 5px 5px gray;\\r\\n}\\r\\n\\r\\nbutton {\\r\\n border: none;\\r\\n background-color: white;\\r\\n}\\r\\n\\r\\nimg {\\r\\n width: 20px;\\r\\n height: auto;\\r\\n}\\r\\n\\r\\n.dots {\\r\\n width: auto;\\r\\n height: 30px;\\r\\n padding-right: 10px;\\r\\n}\\r\\n\\r\\n.row-header {\\r\\n display: flex;\\r\\n flex-direction: row;\\r\\n justify-content: space-between;\\r\\n padding: 0 10px;\\r\\n margin: 0;\\r\\n border-bottom: solid 1px rgb(151, 151, 151);\\r\\n height: 40px;\\r\\n}\\r\\n\\r\\np {\\r\\n font-size: 14px;\\r\\n font-weight: normal;\\r\\n}\\r\\n\\r\\n.row-input {\\r\\n display: flex;\\r\\n flex-direction: row;\\r\\n justify-content: space-between;\\r\\n padding: 0 10px;\\r\\n margin: 0;\\r\\n border-bottom: solid 1px rgb(151, 151, 151);\\r\\n height: 40px;\\r\\n}\\r\\n\\r\\ninput {\\r\\n border: none;\\r\\n}\\r\\n\\r\\n.todo-input {\\r\\n width: 100%;\\r\\n}\\r\\n\\r\\n.task-container {\\r\\n display: flex;\\r\\n flex-direction: column;\\r\\n margin: 0;\\r\\n}\\r\\n\\r\\n.row-task {\\r\\n display: flex;\\r\\n flex-direction: row;\\r\\n justify-content: space-between;\\r\\n align-items: center;\\r\\n border-bottom: solid 1px rgb(151, 151, 151);\\r\\n height: 40px;\\r\\n}\\r\\n\\r\\n.task {\\r\\n gap: 5px;\\r\\n padding-left: 10px;\\r\\n}\\r\\n\\r\\n.buttons {\\r\\n display: flex;\\r\\n flex-direction: row;\\r\\n gap: 3px;\\r\\n}\\r\\n\\r\\n.clear-completed {\\r\\n color: rgb(107, 107, 107);\\r\\n background-color: rgb(231, 231, 231);\\r\\n height: 40px;\\r\\n text-align: center;\\r\\n width: 100%;\\r\\n}\\r\\n\\r\\n/* CONTAINER ENDS HERE */\\r\\n\\r\\nfooter {\\r\\n position: absolute;\\r\\n bottom: 0%;\\r\\n width: 100%;\\r\\n height: 50px;\\r\\n background-color: black;\\r\\n}\\r\\n\", \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://todo-list/./src/style.css?./node_modules/css-loader/dist/cjs.js"); 20 | 21 | /***/ }), 22 | 23 | /***/ "./node_modules/css-loader/dist/runtime/api.js": 24 | /*!*****************************************************!*\ 25 | !*** ./node_modules/css-loader/dist/runtime/api.js ***! 26 | \*****************************************************/ 27 | /***/ ((module) => { 28 | 29 | eval("\n\n/*\n MIT License http://www.opensource.org/licenses/mit-license.php\n Author Tobias Koppers @sokra\n*/\nmodule.exports = function (cssWithMappingToString) {\n var list = []; // return the list of modules as css string\n\n list.toString = function toString() {\n return this.map(function (item) {\n var content = \"\";\n var needLayer = typeof item[5] !== \"undefined\";\n\n if (item[4]) {\n content += \"@supports (\".concat(item[4], \") {\");\n }\n\n if (item[2]) {\n content += \"@media \".concat(item[2], \" {\");\n }\n\n if (needLayer) {\n content += \"@layer\".concat(item[5].length > 0 ? \" \".concat(item[5]) : \"\", \" {\");\n }\n\n content += cssWithMappingToString(item);\n\n if (needLayer) {\n content += \"}\";\n }\n\n if (item[2]) {\n content += \"}\";\n }\n\n if (item[4]) {\n content += \"}\";\n }\n\n return content;\n }).join(\"\");\n }; // import a list of modules into the list\n\n\n list.i = function i(modules, media, dedupe, supports, layer) {\n if (typeof modules === \"string\") {\n modules = [[null, modules, undefined]];\n }\n\n var alreadyImportedModules = {};\n\n if (dedupe) {\n for (var k = 0; k < this.length; k++) {\n var id = this[k][0];\n\n if (id != null) {\n alreadyImportedModules[id] = true;\n }\n }\n }\n\n for (var _k = 0; _k < modules.length; _k++) {\n var item = [].concat(modules[_k]);\n\n if (dedupe && alreadyImportedModules[item[0]]) {\n continue;\n }\n\n if (typeof layer !== \"undefined\") {\n if (typeof item[5] === \"undefined\") {\n item[5] = layer;\n } else {\n item[1] = \"@layer\".concat(item[5].length > 0 ? \" \".concat(item[5]) : \"\", \" {\").concat(item[1], \"}\");\n item[5] = layer;\n }\n }\n\n if (media) {\n if (!item[2]) {\n item[2] = media;\n } else {\n item[1] = \"@media \".concat(item[2], \" {\").concat(item[1], \"}\");\n item[2] = media;\n }\n }\n\n if (supports) {\n if (!item[4]) {\n item[4] = \"\".concat(supports);\n } else {\n item[1] = \"@supports (\".concat(item[4], \") {\").concat(item[1], \"}\");\n item[4] = supports;\n }\n }\n\n list.push(item);\n }\n };\n\n return list;\n};\n\n//# sourceURL=webpack://todo-list/./node_modules/css-loader/dist/runtime/api.js?"); 30 | 31 | /***/ }), 32 | 33 | /***/ "./node_modules/css-loader/dist/runtime/noSourceMaps.js": 34 | /*!**************************************************************!*\ 35 | !*** ./node_modules/css-loader/dist/runtime/noSourceMaps.js ***! 36 | \**************************************************************/ 37 | /***/ ((module) => { 38 | 39 | eval("\n\nmodule.exports = function (i) {\n return i[1];\n};\n\n//# sourceURL=webpack://todo-list/./node_modules/css-loader/dist/runtime/noSourceMaps.js?"); 40 | 41 | /***/ }), 42 | 43 | /***/ "./src/style.css": 44 | /*!***********************!*\ 45 | !*** ./src/style.css ***! 46 | \***********************/ 47 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 48 | 49 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! !../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js */ \"./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! !../node_modules/style-loader/dist/runtime/styleDomAPI.js */ \"./node_modules/style-loader/dist/runtime/styleDomAPI.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! !../node_modules/style-loader/dist/runtime/insertBySelector.js */ \"./node_modules/style-loader/dist/runtime/insertBySelector.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! !../node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js */ \"./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! !../node_modules/style-loader/dist/runtime/insertStyleElement.js */ \"./node_modules/style-loader/dist/runtime/insertStyleElement.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! !../node_modules/style-loader/dist/runtime/styleTagTransform.js */ \"./node_modules/style-loader/dist/runtime/styleTagTransform.js\");\n/* harmony import */ var _node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5__);\n/* harmony import */ var _node_modules_css_loader_dist_cjs_js_style_css__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! !!../node_modules/css-loader/dist/cjs.js!./style.css */ \"./node_modules/css-loader/dist/cjs.js!./src/style.css\");\n\n \n \n \n \n \n \n \n \n \n\nvar options = {};\n\noptions.styleTagTransform = (_node_modules_style_loader_dist_runtime_styleTagTransform_js__WEBPACK_IMPORTED_MODULE_5___default());\noptions.setAttributes = (_node_modules_style_loader_dist_runtime_setAttributesWithoutAttributes_js__WEBPACK_IMPORTED_MODULE_3___default());\n\n options.insert = _node_modules_style_loader_dist_runtime_insertBySelector_js__WEBPACK_IMPORTED_MODULE_2___default().bind(null, \"head\");\n \noptions.domAPI = (_node_modules_style_loader_dist_runtime_styleDomAPI_js__WEBPACK_IMPORTED_MODULE_1___default());\noptions.insertStyleElement = (_node_modules_style_loader_dist_runtime_insertStyleElement_js__WEBPACK_IMPORTED_MODULE_4___default());\n\nvar update = _node_modules_style_loader_dist_runtime_injectStylesIntoStyleTag_js__WEBPACK_IMPORTED_MODULE_0___default()(_node_modules_css_loader_dist_cjs_js_style_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"], options);\n\n\n\n\n /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_node_modules_css_loader_dist_cjs_js_style_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"] && _node_modules_css_loader_dist_cjs_js_style_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals ? _node_modules_css_loader_dist_cjs_js_style_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals : undefined);\n\n\n//# sourceURL=webpack://todo-list/./src/style.css?"); 50 | 51 | /***/ }), 52 | 53 | /***/ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js": 54 | /*!****************************************************************************!*\ 55 | !*** ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js ***! 56 | \****************************************************************************/ 57 | /***/ ((module) => { 58 | 59 | eval("\n\nvar stylesInDOM = [];\n\nfunction getIndexByIdentifier(identifier) {\n var result = -1;\n\n for (var i = 0; i < stylesInDOM.length; i++) {\n if (stylesInDOM[i].identifier === identifier) {\n result = i;\n break;\n }\n }\n\n return result;\n}\n\nfunction modulesToDom(list, options) {\n var idCountMap = {};\n var identifiers = [];\n\n for (var i = 0; i < list.length; i++) {\n var item = list[i];\n var id = options.base ? item[0] + options.base : item[0];\n var count = idCountMap[id] || 0;\n var identifier = \"\".concat(id, \" \").concat(count);\n idCountMap[id] = count + 1;\n var indexByIdentifier = getIndexByIdentifier(identifier);\n var obj = {\n css: item[1],\n media: item[2],\n sourceMap: item[3],\n supports: item[4],\n layer: item[5]\n };\n\n if (indexByIdentifier !== -1) {\n stylesInDOM[indexByIdentifier].references++;\n stylesInDOM[indexByIdentifier].updater(obj);\n } else {\n var updater = addElementStyle(obj, options);\n options.byIndex = i;\n stylesInDOM.splice(i, 0, {\n identifier: identifier,\n updater: updater,\n references: 1\n });\n }\n\n identifiers.push(identifier);\n }\n\n return identifiers;\n}\n\nfunction addElementStyle(obj, options) {\n var api = options.domAPI(options);\n api.update(obj);\n\n var updater = function updater(newObj) {\n if (newObj) {\n if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap && newObj.supports === obj.supports && newObj.layer === obj.layer) {\n return;\n }\n\n api.update(obj = newObj);\n } else {\n api.remove();\n }\n };\n\n return updater;\n}\n\nmodule.exports = function (list, options) {\n options = options || {};\n list = list || [];\n var lastIdentifiers = modulesToDom(list, options);\n return function update(newList) {\n newList = newList || [];\n\n for (var i = 0; i < lastIdentifiers.length; i++) {\n var identifier = lastIdentifiers[i];\n var index = getIndexByIdentifier(identifier);\n stylesInDOM[index].references--;\n }\n\n var newLastIdentifiers = modulesToDom(newList, options);\n\n for (var _i = 0; _i < lastIdentifiers.length; _i++) {\n var _identifier = lastIdentifiers[_i];\n\n var _index = getIndexByIdentifier(_identifier);\n\n if (stylesInDOM[_index].references === 0) {\n stylesInDOM[_index].updater();\n\n stylesInDOM.splice(_index, 1);\n }\n }\n\n lastIdentifiers = newLastIdentifiers;\n };\n};\n\n//# sourceURL=webpack://todo-list/./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js?"); 60 | 61 | /***/ }), 62 | 63 | /***/ "./node_modules/style-loader/dist/runtime/insertBySelector.js": 64 | /*!********************************************************************!*\ 65 | !*** ./node_modules/style-loader/dist/runtime/insertBySelector.js ***! 66 | \********************************************************************/ 67 | /***/ ((module) => { 68 | 69 | eval("\n\nvar memo = {};\n/* istanbul ignore next */\n\nfunction getTarget(target) {\n if (typeof memo[target] === \"undefined\") {\n var styleTarget = document.querySelector(target); // Special case to return head of iframe instead of iframe itself\n\n if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) {\n try {\n // This will throw an exception if access to iframe is blocked\n // due to cross-origin restrictions\n styleTarget = styleTarget.contentDocument.head;\n } catch (e) {\n // istanbul ignore next\n styleTarget = null;\n }\n }\n\n memo[target] = styleTarget;\n }\n\n return memo[target];\n}\n/* istanbul ignore next */\n\n\nfunction insertBySelector(insert, style) {\n var target = getTarget(insert);\n\n if (!target) {\n throw new Error(\"Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.\");\n }\n\n target.appendChild(style);\n}\n\nmodule.exports = insertBySelector;\n\n//# sourceURL=webpack://todo-list/./node_modules/style-loader/dist/runtime/insertBySelector.js?"); 70 | 71 | /***/ }), 72 | 73 | /***/ "./node_modules/style-loader/dist/runtime/insertStyleElement.js": 74 | /*!**********************************************************************!*\ 75 | !*** ./node_modules/style-loader/dist/runtime/insertStyleElement.js ***! 76 | \**********************************************************************/ 77 | /***/ ((module) => { 78 | 79 | eval("\n\n/* istanbul ignore next */\nfunction insertStyleElement(options) {\n var element = document.createElement(\"style\");\n options.setAttributes(element, options.attributes);\n options.insert(element, options.options);\n return element;\n}\n\nmodule.exports = insertStyleElement;\n\n//# sourceURL=webpack://todo-list/./node_modules/style-loader/dist/runtime/insertStyleElement.js?"); 80 | 81 | /***/ }), 82 | 83 | /***/ "./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js": 84 | /*!**********************************************************************************!*\ 85 | !*** ./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js ***! 86 | \**********************************************************************************/ 87 | /***/ ((module, __unused_webpack_exports, __webpack_require__) => { 88 | 89 | eval("\n\n/* istanbul ignore next */\nfunction setAttributesWithoutAttributes(styleElement) {\n var nonce = true ? __webpack_require__.nc : 0;\n\n if (nonce) {\n styleElement.setAttribute(\"nonce\", nonce);\n }\n}\n\nmodule.exports = setAttributesWithoutAttributes;\n\n//# sourceURL=webpack://todo-list/./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js?"); 90 | 91 | /***/ }), 92 | 93 | /***/ "./node_modules/style-loader/dist/runtime/styleDomAPI.js": 94 | /*!***************************************************************!*\ 95 | !*** ./node_modules/style-loader/dist/runtime/styleDomAPI.js ***! 96 | \***************************************************************/ 97 | /***/ ((module) => { 98 | 99 | eval("\n\n/* istanbul ignore next */\nfunction apply(styleElement, options, obj) {\n var css = \"\";\n\n if (obj.supports) {\n css += \"@supports (\".concat(obj.supports, \") {\");\n }\n\n if (obj.media) {\n css += \"@media \".concat(obj.media, \" {\");\n }\n\n var needLayer = typeof obj.layer !== \"undefined\";\n\n if (needLayer) {\n css += \"@layer\".concat(obj.layer.length > 0 ? \" \".concat(obj.layer) : \"\", \" {\");\n }\n\n css += obj.css;\n\n if (needLayer) {\n css += \"}\";\n }\n\n if (obj.media) {\n css += \"}\";\n }\n\n if (obj.supports) {\n css += \"}\";\n }\n\n var sourceMap = obj.sourceMap;\n\n if (sourceMap && typeof btoa !== \"undefined\") {\n css += \"\\n/*# sourceMappingURL=data:application/json;base64,\".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), \" */\");\n } // For old IE\n\n /* istanbul ignore if */\n\n\n options.styleTagTransform(css, styleElement, options.options);\n}\n\nfunction removeStyleElement(styleElement) {\n // istanbul ignore if\n if (styleElement.parentNode === null) {\n return false;\n }\n\n styleElement.parentNode.removeChild(styleElement);\n}\n/* istanbul ignore next */\n\n\nfunction domAPI(options) {\n var styleElement = options.insertStyleElement(options);\n return {\n update: function update(obj) {\n apply(styleElement, options, obj);\n },\n remove: function remove() {\n removeStyleElement(styleElement);\n }\n };\n}\n\nmodule.exports = domAPI;\n\n//# sourceURL=webpack://todo-list/./node_modules/style-loader/dist/runtime/styleDomAPI.js?"); 100 | 101 | /***/ }), 102 | 103 | /***/ "./node_modules/style-loader/dist/runtime/styleTagTransform.js": 104 | /*!*********************************************************************!*\ 105 | !*** ./node_modules/style-loader/dist/runtime/styleTagTransform.js ***! 106 | \*********************************************************************/ 107 | /***/ ((module) => { 108 | 109 | eval("\n\n/* istanbul ignore next */\nfunction styleTagTransform(css, styleElement) {\n if (styleElement.styleSheet) {\n styleElement.styleSheet.cssText = css;\n } else {\n while (styleElement.firstChild) {\n styleElement.removeChild(styleElement.firstChild);\n }\n\n styleElement.appendChild(document.createTextNode(css));\n }\n}\n\nmodule.exports = styleTagTransform;\n\n//# sourceURL=webpack://todo-list/./node_modules/style-loader/dist/runtime/styleTagTransform.js?"); 110 | 111 | /***/ }), 112 | 113 | /***/ "./src/app.js": 114 | /*!********************!*\ 115 | !*** ./src/app.js ***! 116 | \********************/ 117 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 118 | 119 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _tasksList_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./tasksList.js */ \"./src/tasksList.js\");\n\n\nclass App {\n constructor() {\n this.taskList = new _tasksList_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"]();\n }\n\n displayTaskCards() {\n this.taskList.saveDataToStorage();\n const taskContainer = document.querySelector('.task-container');\n const taskTemplate = document.getElementById('task-template');\n taskContainer.innerHTML = '';\n this.taskList.taskListArray.forEach((task) => {\n const taskCard = taskTemplate.content.cloneNode(true).children[0];\n const checkBox = taskCard.querySelector('.check');\n const descriptionInput = taskCard.querySelector('.task-description');\n const taskButton = taskCard.querySelector('.row-task button');\n const deleteButton = taskCard.querySelector('.delete-btn');\n checkBox.checked = task.completed ? 'checked' : '';\n checkBox.addEventListener('change', (e) => {\n const grandParent = e.target.parentNode.parentNode;\n if (e.target.checked) {\n grandParent.querySelector('.task-description').style.textDecoration = 'line-through';\n } else {\n grandParent.querySelector('.task-description').style.textDecoration = 'none';\n }\n this.taskList.updateTaskStatus(\n grandParent.querySelector('.delete-btn').dataset.id,\n e.target.checked,\n );\n });\n descriptionInput.value = task.description;\n if (task.completed) {\n descriptionInput.style.textDecoration = 'line-through';\n } else {\n descriptionInput.style.textDecoration = 'none';\n }\n taskButton.setAttribute('data-id', task.index);\n deleteButton.setAttribute('data-id', task.index);\n descriptionInput.addEventListener('input', (e) => {\n const grandParent = e.target.parentNode.parentNode;\n grandParent.style.backgroundColor = 'lightgoldenrodyellow';\n grandParent.querySelector('.delete-btn').style.display = 'block';\n grandParent.querySelector('.move-btn').style.display = 'none';\n });\n\n descriptionInput.addEventListener('blur', (e) => {\n const grandParent = e.target.parentNode.parentNode;\n grandParent.style.backgroundColor = 'inherit';\n const index = e.target.parentNode.parentNode.querySelector('.delete-btn').dataset\n .id;\n this.taskList.updateTaskDescription(index, e.target.value);\n setTimeout(() => {\n grandParent.querySelector('.delete-btn').style.display = 'none';\n grandParent.querySelector('.move-btn').style.display = 'block';\n }, 200);\n });\n deleteButton.addEventListener('click', (e) => {\n this.taskList.deleteTask(e.target.dataset.id);\n this.displayTaskCards();\n });\n taskContainer.appendChild(taskCard);\n });\n }\n\n AddListeners() {\n document\n .querySelector('.row-input input')\n .addEventListener('keypress', (e) => {\n if (e.code === 'Enter') {\n if (e.target.value === '') return;\n this.taskList.addNewTask(e.target.value);\n this.displayTaskCards();\n e.target.value = '';\n e.target.focus();\n }\n });\n document.querySelector('.clear-completed').addEventListener('click', () => {\n this.taskList.clearAllCompleted();\n this.displayTaskCards();\n });\n document.addEventListener('dragstart', (e) => {\n // Event handlers for card's drag operations\n if (e.target.matches('.row-task')) {\n e.target.style.opacity = '0.4';\n this.dragSourceElement = e.target;\n this.dragSourceID = e.target.querySelector('.move-btn').dataset.id;\n }\n });\n document.addEventListener('dragend', (e) => {\n if (e.target.matches('.row-task')) {\n e.target.style.opacity = '1';\n e.target.classList.remove('over');\n }\n });\n\n document.addEventListener('dragenter', (e) => {\n if (e.target.matches('.row-task')) {\n e.target.classList.add('over');\n }\n });\n\n document.addEventListener('dragleave', (e) => {\n if (e.target.matches('.row-task')) {\n e.target.classList.remove('over');\n }\n });\n document.addEventListener('dragover', (e) => {\n e.preventDefault();\n });\n\n document.addEventListener('drop', (e) => {\n e.preventDefault();\n if (e.target.matches('.row-task')) {\n e.stopPropagation();\n if (this.dragSourceElement !== e.target) {\n this.dragTargetID = e.target.querySelector('.move-btn').dataset.id;\n this.taskList.swapPositions(this.dragTargetID, this.dragSourceID);\n e.target.classList.remove('over');\n this.displayTaskCards();\n }\n }\n });\n }\n}\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (App);\n\n\n//# sourceURL=webpack://todo-list/./src/app.js?"); 120 | 121 | /***/ }), 122 | 123 | /***/ "./src/index.js": 124 | /*!**********************!*\ 125 | !*** ./src/index.js ***! 126 | \**********************/ 127 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 128 | 129 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _style_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./style.css */ \"./src/style.css\");\n/* harmony import */ var _app_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./app.js */ \"./src/app.js\");\n\n\n\n\nconst theApp = new _app_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"]();\ntheApp.displayTaskCards();\ntheApp.AddListeners();\n\n\n//# sourceURL=webpack://todo-list/./src/index.js?"); 130 | 131 | /***/ }), 132 | 133 | /***/ "./src/tasksList.js": 134 | /*!**************************!*\ 135 | !*** ./src/tasksList.js ***! 136 | \**************************/ 137 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 138 | 139 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\nclass TaskList {\n constructor() {\n this.taskListArray = this.loadDataFromStorage();\n }\n\n loadDataFromStorage() {\n this.taskListArray = JSON.parse(window.localStorage.getItem('TODOLIST'));\n if (!this.taskListArray) this.taskListArray = [];\n return this.taskListArray;\n }\n\n saveDataToStorage() {\n window.localStorage.setItem('TODOLIST', JSON.stringify(this.taskListArray));\n }\n\n addNewTask(description) {\n this.taskListArray.push({\n index: this.taskListArray.length + 1,\n completed: false,\n description,\n });\n }\n\n deleteTask(index) {\n this.taskListArray = this.taskListArray.filter(\n (e) => e.index !== Number(index),\n );\n this.#reorderIndexes();\n }\n\n updateTaskDescription(index, description) {\n this.taskListArray[index - 1].description = description;\n this.saveDataToStorage();\n }\n\n updateTaskStatus(index, status) {\n this.taskListArray[index - 1].completed = status;\n this.saveDataToStorage();\n }\n\n clearAllCompleted() {\n this.taskListArray = this.taskListArray.filter((e) => !e.completed);\n this.#reorderIndexes();\n this.saveDataToStorage();\n }\n\n getDescription(index) {\n return this.taskListArray[index - 1].description;\n }\n\n getStatus(index) {\n return this.taskListArray[index - 1].completed;\n }\n\n swapPositions(lID, rID) {\n let indexA = 0;\n let indexB = 0;\n for (let i = 0; i < this.taskListArray.length; i += 1) {\n if (this.taskListArray[i].index === Number(lID)) indexA = i;\n if (this.taskListArray[i].index === Number(rID)) indexB = i;\n }\n const tempo = this.taskListArray[indexA];\n this.taskListArray[indexA] = this.taskListArray[indexB];\n this.taskListArray[indexB] = tempo;\n this.#reorderIndexes();\n this.saveDataToStorage();\n }\n\n #reorderIndexes() {\n let index = 1;\n this.taskListArray.forEach((e) => {\n e.index = index;\n index += 1;\n });\n }\n}\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (TaskList);\n\n\n//# sourceURL=webpack://todo-list/./src/tasksList.js?"); 140 | 141 | /***/ }) 142 | 143 | /******/ }); 144 | /************************************************************************/ 145 | /******/ // The module cache 146 | /******/ var __webpack_module_cache__ = {}; 147 | /******/ 148 | /******/ // The require function 149 | /******/ function __webpack_require__(moduleId) { 150 | /******/ // Check if module is in cache 151 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 152 | /******/ if (cachedModule !== undefined) { 153 | /******/ return cachedModule.exports; 154 | /******/ } 155 | /******/ // Create a new module (and put it into the cache) 156 | /******/ var module = __webpack_module_cache__[moduleId] = { 157 | /******/ id: moduleId, 158 | /******/ // no module.loaded needed 159 | /******/ exports: {} 160 | /******/ }; 161 | /******/ 162 | /******/ // Execute the module function 163 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 164 | /******/ 165 | /******/ // Return the exports of the module 166 | /******/ return module.exports; 167 | /******/ } 168 | /******/ 169 | /************************************************************************/ 170 | /******/ /* webpack/runtime/compat get default export */ 171 | /******/ (() => { 172 | /******/ // getDefaultExport function for compatibility with non-harmony modules 173 | /******/ __webpack_require__.n = (module) => { 174 | /******/ var getter = module && module.__esModule ? 175 | /******/ () => (module['default']) : 176 | /******/ () => (module); 177 | /******/ __webpack_require__.d(getter, { a: getter }); 178 | /******/ return getter; 179 | /******/ }; 180 | /******/ })(); 181 | /******/ 182 | /******/ /* webpack/runtime/define property getters */ 183 | /******/ (() => { 184 | /******/ // define getter functions for harmony exports 185 | /******/ __webpack_require__.d = (exports, definition) => { 186 | /******/ for(var key in definition) { 187 | /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { 188 | /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 189 | /******/ } 190 | /******/ } 191 | /******/ }; 192 | /******/ })(); 193 | /******/ 194 | /******/ /* webpack/runtime/hasOwnProperty shorthand */ 195 | /******/ (() => { 196 | /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) 197 | /******/ })(); 198 | /******/ 199 | /******/ /* webpack/runtime/make namespace object */ 200 | /******/ (() => { 201 | /******/ // define __esModule on exports 202 | /******/ __webpack_require__.r = (exports) => { 203 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 204 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 205 | /******/ } 206 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 207 | /******/ }; 208 | /******/ })(); 209 | /******/ 210 | /************************************************************************/ 211 | /******/ 212 | /******/ // startup 213 | /******/ // Load entry module and return exports 214 | /******/ // This entry module can't be inlined because the eval devtool is used. 215 | /******/ var __webpack_exports__ = __webpack_require__("./src/index.js"); 216 | /******/ 217 | /******/ })() 218 | ; --------------------------------------------------------------------------------