├── .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 |
19 |
29 |
30 |
42 |
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 |
19 |
29 |
30 |
42 |
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 |
27 |
37 |
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 |
54 |
64 |
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 |
39 |
49 |
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 |
69 |
79 |
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 |
100 |
110 |
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 | ;
--------------------------------------------------------------------------------