├── .babelrc
├── .eslintrc
├── .github
└── workflows
│ └── linters.yml
├── .gitignore
├── .hintrc
├── .stylelintrc
├── README.md
├── dist
├── 6731ef40f4416b5c9069.ttf
├── a0d3b87095ec6aee15f3.png
├── index.html
└── main.js
├── package-lock.json
├── package.json
├── src
├── assets
│ ├── css
│ │ └── style.css
│ ├── fonts
│ │ └── cocogoose-pro-regular.ttf
│ └── img
│ │ └── todo-list-desktop-v-snapshot.png
├── components
│ ├── ClearAllButton.js
│ ├── CreateNewTaskItem.js
│ ├── HeadingTaskListItem.js
│ ├── TaskListItem.js
│ └── TaskListItem.test.js
├── index.html
├── index.js
└── modules
│ ├── DataStore.js
│ ├── Task.js
│ └── Task.test.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "test": {
4 | "plugins": [
5 | "@babel/plugin-transform-modules-commonjs"
6 | ]
7 | }
8 | }
9 | }
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
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": [
13 | "airbnb-base"
14 | ],
15 | "rules": {
16 | "no-shadow": "off",
17 | "no-param-reassign": "off",
18 | "eol-last": "off",
19 | "import/extensions": [
20 | 1,
21 | {
22 | "js": "always",
23 | "json": "always"
24 | }
25 | ]
26 | },
27 | "ignorePatterns": [
28 | "dist/",
29 | "build/"
30 | ]
31 | }
--------------------------------------------------------------------------------
/.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 .
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .vscode/
3 |
4 | coverage/
--------------------------------------------------------------------------------
/.hintrc:
--------------------------------------------------------------------------------
1 | {
2 | "connector": {
3 | "name": "local",
4 | "options": {
5 | "pattern": [
6 | "**",
7 | "!.git/**",
8 | "!node_modules/**"
9 | ]
10 | }
11 | },
12 | "extends": [
13 | "development"
14 | ],
15 | "formatters": [
16 | "stylish"
17 | ],
18 | "hints": [
19 | "button-type",
20 | "disown-opener",
21 | "html-checker",
22 | "meta-charset-utf-8",
23 | "meta-viewport",
24 | "no-inline-styles:error"
25 | ]
26 | }
--------------------------------------------------------------------------------
/.stylelintrc:
--------------------------------------------------------------------------------
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": ["build/**", "dist/**", "**/reset*.css", "**/bootstrap*.css", "**/*.js", "**/*.jsx"]
10 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | 
3 |
4 | # todo-list
5 | "To-do list" is a tool that helps to organize your day. It simply lists the things that you need to do and allows you to mark them as complete. This simple website allows you to that, and it is built using ES6 and Webpack!
6 |
7 | ### Desktop Version
8 |
9 | 
10 |
11 | In this project, you will get a simple HTML list of To Do tasks. It is built using webpack and served by a webpack dev server. The list is styled according to the specifications listed by the [minimalist](https://web.archive.org/web/20180320194056/http://www.getminimalist.com:80/) website.
12 |
13 | The CRUD (create, update, delete) methods for the todo tasks are implemented. All the elements of the user interface are fully functional and the application is completed.You will also be able to mark task completion by selecting the corresponding checkbox (or undo it by unchecking the checkbox). The updated tasks list will be stored in local storage.
14 |
15 | ## Built With
16 |
17 | - HTML, CSS and JavaScript (ES6)
18 |
19 | ## Live Demo
20 |
21 | - [Live Demo Link](https://teks-todo-list.netlify.app)
22 |
23 | ## Getting Started
24 | - Install Node.js ^12.13
25 | - clone the repository by running\
26 | `git clone https://github.com/gtekle/todo-list.git`
27 | - navigate to the folder\
28 | `cd todo-list`
29 | - Install packages\
30 | `npm install`
31 | - To run application using webpack-dev-server\
32 | `npm start`
33 |
34 | ## Author
35 |
36 | 👤 **Tekle Gebreyohannes**
37 |
38 | - GitHub: [@githubhandle](https://github.com/gtekle)
39 | - LinkedIn: [LinkedIn](www.linkedin.com/in/tekle-gebreyohannes-kidanemariam-7605752b)
40 |
41 | ## 🤝 Contributing
42 |
43 | Contributions, issues, and feature requests are welcome!
44 |
45 | Feel free to check the [issues page](../../issues/).
46 |
47 | ## Show your support
48 |
49 | Give a ⭐️ if you like this project!
50 |
51 | ## Acknowledgments
52 |
53 | - [Microverse Inc](https://www.microverse.org/)
54 | - [minimalist](https://web.archive.org/web/20180320194056/http://www.getminimalist.com:80/)
55 |
56 | ## 📝 License
57 |
58 | This project is [MIT](./MIT.md) licensed.
59 |
--------------------------------------------------------------------------------
/dist/6731ef40f4416b5c9069.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gtekle/todo-list/66daf5675b29b0ae9965acaef53895127ddec5e8/dist/6731ef40f4416b5c9069.ttf
--------------------------------------------------------------------------------
/dist/a0d3b87095ec6aee15f3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gtekle/todo-list/66daf5675b29b0ae9965acaef53895127ddec5e8/dist/a0d3b87095ec6aee15f3.png
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | To Do List
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | TO DO LIST
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/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/assets/css/style.css":
14 | /*!************************************************************************!*\
15 | !*** ./node_modules/css-loader/dist/cjs.js!./src/assets/css/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/* harmony import */ var _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../../node_modules/css-loader/dist/runtime/getUrl.js */ \"./node_modules/css-loader/dist/runtime/getUrl.js\");\n/* harmony import */ var _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(_node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2__);\n// Imports\n\n\n\nvar ___CSS_LOADER_URL_IMPORT_0___ = new URL(/* asset import */ __webpack_require__(/*! ../fonts/cocogoose-pro-regular.ttf */ \"./src/assets/fonts/cocogoose-pro-regular.ttf\"), __webpack_require__.b);\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()));\nvar ___CSS_LOADER_URL_REPLACEMENT_0___ = _node_modules_css_loader_dist_runtime_getUrl_js__WEBPACK_IMPORTED_MODULE_2___default()(___CSS_LOADER_URL_IMPORT_0___);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"*,\\n*::after,\\n*::before {\\n box-sizing: border-box;\\n}\\n\\n@font-face {\\n font-family: cocogoose;\\n src: url(\" + ___CSS_LOADER_URL_REPLACEMENT_0___ + \");\\n}\\n\\nbody {\\n background-color: #ddd;\\n margin: 0;\\n font-family: Arial, Helvetica, sans-serif;\\n}\\n\\n.btn {\\n cursor: pointer;\\n}\\n\\n#header,\\n#footer {\\n background-color: darkgray;\\n width: 100%;\\n height: 5px;\\n padding: 0;\\n margin: 0;\\n}\\n\\n#main {\\n max-width: 94%;\\n min-height: calc(100vh - 64px);\\n margin: 12px auto;\\n text-align: center;\\n}\\n\\n.main-section-title {\\n color: darkslategray;\\n font-family: cocogoose, sans-serif;\\n}\\n\\n.todo-list {\\n list-style: none;\\n text-align: center;\\n padding: 0;\\n background-color: #d3d3d3;\\n margin: 0;\\n box-shadow: 4px 4px 4px 2px rgba(0, 0, 0, 0.2);\\n}\\n\\n.todo-heading {\\n display: flex;\\n justify-content: space-between;\\n}\\n\\n.todo-heading h2 {\\n color: darkgray;\\n font-size: 20px;\\n line-height: 24px;\\n font-weight: 700;\\n}\\n\\n.todo-heading .btn-refresh {\\n border: none;\\n background-color: #fff;\\n text-align: right;\\n padding: 0;\\n margin: 0;\\n margin-right: 10px;\\n}\\n\\n.todo-heading i {\\n color: darkgray;\\n font-size: 22px;\\n font-weight: 500;\\n}\\n\\n.btn i:hover {\\n color: black;\\n}\\n\\n.btn i:active {\\n color: blueviolet;\\n}\\n\\n#form-add-task {\\n display: flex;\\n width: 100%;\\n text-align: justify;\\n background-color: #fff;\\n border-bottom: 1px solid gray;\\n}\\n\\n#form-add-task input {\\n width: calc(100% - 43px);\\n margin: 0;\\n font-size: 17px;\\n line-height: 32px;\\n padding: 3px 10px;\\n font-style: italic;\\n border: none;\\n outline: none;\\n}\\n\\n#form-add-task .btn-add-task {\\n display: flex;\\n flex-direction: column;\\n justify-content: center;\\n align-items: center;\\n color: grey;\\n border: none;\\n margin: 0;\\n font-weight: 700;\\n vertical-align: text-top;\\n font-size: 16px;\\n line-height: 24px;\\n background-color: #fff;\\n}\\n\\n.todo-list li {\\n display: flex;\\n border-bottom: 1px solid #bbb;\\n justify-content: space-between;\\n align-items: center;\\n background-color: #fff;\\n padding-left: 10px;\\n font-size: 16px;\\n font-weight: 500;\\n cursor: text;\\n}\\n\\ninput[name=\\\"isCompleted\\\"] {\\n cursor: pointer;\\n}\\n\\n.item-description {\\n display: flex;\\n align-items: center;\\n font-size: 18px;\\n width: 100%;\\n}\\n\\n.todo-list li .task-description {\\n color: rgba(0, 0, 0, 0.8);\\n width: 100%;\\n margin: 0;\\n padding: 10px;\\n font-size: 16px;\\n font-weight: 500;\\n text-align: left;\\n}\\n\\n.todo-list .fa-ellipsis-v {\\n color: grey;\\n font-size: 16px;\\n padding: 5px;\\n cursor: move;\\n z-index: 10;\\n}\\n\\n.todo-list .btn-v-ellipsis {\\n border: none;\\n background-color: transparent;\\n cursor: pointer;\\n z-index: 100;\\n}\\n\\n.todo-list .btn-trash {\\n border: none;\\n background-color: transparent;\\n cursor: pointer;\\n z-index: 110;\\n}\\n\\n.todo-list .fa-trash {\\n color: red;\\n font-size: 16px;\\n padding: 5px;\\n}\\n\\n.todo-list .fa-ellipsis-v:hover {\\n color: black;\\n}\\n\\n.todo-list .over {\\n background-color: #cababa;\\n}\\n\\n.todo-list li > * {\\n margin-right: 10px;\\n}\\n\\n.btn-clear-completed {\\n border: none;\\n padding: 6px;\\n margin: 10px;\\n font-size: 16px;\\n background-color: transparent;\\n font-weight: 700;\\n color: darkslategray;\\n}\\n\\n.d-off {\\n display: none;\\n}\\n\\n@media screen and (min-width: 768px) {\\n #main {\\n max-width: 40%;\\n min-height: calc(100vh - 88px);\\n margin: 24px auto;\\n }\\n}\\n\", \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://todo-list/./src/assets/css/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/getUrl.js":
34 | /*!********************************************************!*\
35 | !*** ./node_modules/css-loader/dist/runtime/getUrl.js ***!
36 | \********************************************************/
37 | /***/ ((module) => {
38 |
39 | eval("\n\nmodule.exports = function (url, options) {\n if (!options) {\n options = {};\n }\n\n if (!url) {\n return url;\n }\n\n url = String(url.__esModule ? url.default : url); // If url is already wrapped in quotes, remove them\n\n if (/^['\"].*['\"]$/.test(url)) {\n url = url.slice(1, -1);\n }\n\n if (options.hash) {\n url += options.hash;\n } // Should url be wrapped?\n // See https://drafts.csswg.org/css-values-3/#urls\n\n\n if (/[\"'() \\t\\n]|(%20)/.test(url) || options.needQuotes) {\n return \"\\\"\".concat(url.replace(/\"/g, '\\\\\"').replace(/\\n/g, \"\\\\n\"), \"\\\"\");\n }\n\n return url;\n};\n\n//# sourceURL=webpack://todo-list/./node_modules/css-loader/dist/runtime/getUrl.js?");
40 |
41 | /***/ }),
42 |
43 | /***/ "./node_modules/css-loader/dist/runtime/noSourceMaps.js":
44 | /*!**************************************************************!*\
45 | !*** ./node_modules/css-loader/dist/runtime/noSourceMaps.js ***!
46 | \**************************************************************/
47 | /***/ ((module) => {
48 |
49 | 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?");
50 |
51 | /***/ }),
52 |
53 | /***/ "./src/assets/css/style.css":
54 | /*!**********************************!*\
55 | !*** ./src/assets/css/style.css ***!
56 | \**********************************/
57 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
58 |
59 | 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/assets/css/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/assets/css/style.css?");
60 |
61 | /***/ }),
62 |
63 | /***/ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js":
64 | /*!****************************************************************************!*\
65 | !*** ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js ***!
66 | \****************************************************************************/
67 | /***/ ((module) => {
68 |
69 | 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?");
70 |
71 | /***/ }),
72 |
73 | /***/ "./node_modules/style-loader/dist/runtime/insertBySelector.js":
74 | /*!********************************************************************!*\
75 | !*** ./node_modules/style-loader/dist/runtime/insertBySelector.js ***!
76 | \********************************************************************/
77 | /***/ ((module) => {
78 |
79 | 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?");
80 |
81 | /***/ }),
82 |
83 | /***/ "./node_modules/style-loader/dist/runtime/insertStyleElement.js":
84 | /*!**********************************************************************!*\
85 | !*** ./node_modules/style-loader/dist/runtime/insertStyleElement.js ***!
86 | \**********************************************************************/
87 | /***/ ((module) => {
88 |
89 | 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?");
90 |
91 | /***/ }),
92 |
93 | /***/ "./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js":
94 | /*!**********************************************************************************!*\
95 | !*** ./node_modules/style-loader/dist/runtime/setAttributesWithoutAttributes.js ***!
96 | \**********************************************************************************/
97 | /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
98 |
99 | 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?");
100 |
101 | /***/ }),
102 |
103 | /***/ "./node_modules/style-loader/dist/runtime/styleDomAPI.js":
104 | /*!***************************************************************!*\
105 | !*** ./node_modules/style-loader/dist/runtime/styleDomAPI.js ***!
106 | \***************************************************************/
107 | /***/ ((module) => {
108 |
109 | 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?");
110 |
111 | /***/ }),
112 |
113 | /***/ "./node_modules/style-loader/dist/runtime/styleTagTransform.js":
114 | /*!*********************************************************************!*\
115 | !*** ./node_modules/style-loader/dist/runtime/styleTagTransform.js ***!
116 | \*********************************************************************/
117 | /***/ ((module) => {
118 |
119 | 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?");
120 |
121 | /***/ }),
122 |
123 | /***/ "./src/components/ClearAllButton.js":
124 | /*!******************************************!*\
125 | !*** ./src/components/ClearAllButton.js ***!
126 | \******************************************/
127 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
128 |
129 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\nconst btnClearAllCompleted = () => {\n const btnClearCompleted = document.createElement('button');\n btnClearCompleted.setAttribute('type', 'button');\n btnClearCompleted.setAttribute('name', 'btnClearCompleted');\n btnClearCompleted.innerText = 'Clear all completed';\n btnClearCompleted.classList.add('btn', 'btn-clear-completed');\n\n return btnClearCompleted;\n};\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (btnClearAllCompleted);\n\n//# sourceURL=webpack://todo-list/./src/components/ClearAllButton.js?");
130 |
131 | /***/ }),
132 |
133 | /***/ "./src/components/CreateNewTaskItem.js":
134 | /*!*********************************************!*\
135 | !*** ./src/components/CreateNewTaskItem.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 */ });\n/* harmony import */ var _modules_Task_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../modules/Task.js */ \"./src/modules/Task.js\");\n/* harmony import */ var _modules_DataStore_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../modules/DataStore.js */ \"./src/modules/DataStore.js\");\n/* harmony import */ var _TaskListItem_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./TaskListItem.js */ \"./src/components/TaskListItem.js\");\n\n\n\n\n\nconst formAddTask = () => {\n const form = document.createElement('form');\n form.id = 'form-add-task';\n form.innerHTML = `\n `;\n\n form.addEventListener('submit', (event) => {\n event.preventDefault();\n\n if (localStorage.getItem('tasks') === 'undefined') {\n localStorage.setItem('tasks', JSON.stringify([]));\n }\n\n _modules_DataStore_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"].tasks = JSON.parse(localStorage.getItem('tasks'));\n\n const { newTask } = form.elements;\n const task = new _modules_Task_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"](newTask.value, false, _modules_DataStore_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"].tasks.length + 1);\n task.addTask();\n (0,_TaskListItem_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"])(task);\n (0,_TaskListItem_js__WEBPACK_IMPORTED_MODULE_2__.refreshTaskList)();\n newTask.value = '';\n newTask.style.focus = false;\n });\n return form;\n};\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (formAddTask);\n\n//# sourceURL=webpack://todo-list/./src/components/CreateNewTaskItem.js?");
140 |
141 | /***/ }),
142 |
143 | /***/ "./src/components/HeadingTaskListItem.js":
144 | /*!***********************************************!*\
145 | !*** ./src/components/HeadingTaskListItem.js ***!
146 | \***********************************************/
147 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
148 |
149 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"checkRefreshButtonEvent\": () => (/* binding */ checkRefreshButtonEvent),\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _TaskListItem_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./TaskListItem.js */ \"./src/components/TaskListItem.js\");\n\n\nconst todoListHeading = () => {\n const todoHeading = document.createElement('li');\n todoHeading.innerHTML = ` Today's Tasks
\n `;\n todoHeading.classList.add('todo-heading');\n\n return todoHeading;\n};\n\nconst checkRefreshButtonEvent = () => {\n const btnRefreshList = document.querySelector('.btn-refresh');\n\n btnRefreshList.addEventListener('click', (event) => {\n event.stopPropagation();\n\n (0,_TaskListItem_js__WEBPACK_IMPORTED_MODULE_0__.refreshTaskList)();\n });\n\n (0,_TaskListItem_js__WEBPACK_IMPORTED_MODULE_0__.clearAllCompletedTasksEvent)();\n};\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (todoListHeading);\n\n//# sourceURL=webpack://todo-list/./src/components/HeadingTaskListItem.js?");
150 |
151 | /***/ }),
152 |
153 | /***/ "./src/components/TaskListItem.js":
154 | /*!****************************************!*\
155 | !*** ./src/components/TaskListItem.js ***!
156 | \****************************************/
157 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
158 |
159 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"refreshTaskList\": () => (/* binding */ refreshTaskList),\n/* harmony export */ \"isTaskCompleted\": () => (/* binding */ isTaskCompleted),\n/* harmony export */ \"isTaskClicked\": () => (/* binding */ isTaskClicked),\n/* harmony export */ \"checkTaskDragEvents\": () => (/* binding */ checkTaskDragEvents),\n/* harmony export */ \"clearAllCompletedTasksEvent\": () => (/* binding */ clearAllCompletedTasksEvent),\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _modules_DataStore_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../modules/DataStore.js */ \"./src/modules/DataStore.js\");\n/* harmony import */ var _modules_Task_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../modules/Task.js */ \"./src/modules/Task.js\");\n\n\n\nconst refreshTaskList = () => {\n const tasks = document.querySelectorAll('.task');\n tasks.forEach((prevTask) => {\n prevTask.parentNode.removeChild(prevTask);\n });\n\n _modules_DataStore_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].tasks = _modules_DataStore_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].updateTaskIndex();\n _modules_DataStore_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].tasks.forEach((task) => {\n // eslint-disable-next-line\n renderTaskItem(task);\n });\n};\n\nconst isTaskCompleted = () => {\n const completedTasks = document.querySelectorAll('input[name=\"isCompleted\"]');\n completedTasks.forEach((btnIsCompleted) => {\n btnIsCompleted.addEventListener('click', () => {\n if (btnIsCompleted.checked) {\n btnIsCompleted.checked = true;\n btnIsCompleted.parentNode.nextSibling.nextSibling.style.textDecoration = 'line-through';\n } else {\n btnIsCompleted.checked = false;\n btnIsCompleted.parentNode.nextSibling.nextSibling.style.textDecoration = '';\n }\n });\n });\n};\n\nconst isTaskClicked = () => {\n const allTasks = document.querySelectorAll('.task');\n\n allTasks.forEach((task) => {\n const newTaskDescription = ['', ''];\n const taskDescription = task.childNodes[0].childNodes[2];\n const btnEllipsis = task.childNodes[2];\n const btnDeleteTask = task.childNodes[4];\n\n task.addEventListener('click', () => {\n newTaskDescription[0] = taskDescription.innerText;\n newTaskDescription.push(parseInt(task.id, 10));\n taskDescription.contentEditable = true;\n taskDescription.focus();\n\n // The following two lines of code are used to move the cursor\n // to the end of existing text in contentEditable HTML5 elements.\n document.execCommand('selectAll', false, null);\n document.getSelection().collapseToEnd();\n\n taskDescription.style.outline = 'none';\n task.style.backgroundColor = 'lightgreen';\n taskDescription.style.color = 'blue';\n btnEllipsis.classList.add('d-off');\n btnDeleteTask.classList.remove('d-off');\n\n if (!btnDeleteTask.classList.contains('d-off')) {\n btnDeleteTask.addEventListener('click', (event) => {\n event.stopImmediatePropagation();\n\n const task = btnDeleteTask.parentNode;\n const targetTask = new _modules_Task_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"]();\n targetTask.removeTask(parseInt(task.id, 10));\n task.parentNode.removeChild(task);\n refreshTaskList();\n });\n }\n\n allTasks.forEach((inactiveTask) => {\n if (task !== inactiveTask) {\n inactiveTask.childNodes[0].childNodes[2].contentEditable = false;\n inactiveTask.style.backgroundColor = '#fff';\n inactiveTask.childNodes[0].childNodes[2].style.color = '#000';\n inactiveTask.childNodes[0].childNodes[2].style.opacity = '0.8';\n inactiveTask.childNodes[0].childNodes[2].style.border = 'none';\n inactiveTask.childNodes[4].classList.add('d-off');\n inactiveTask.childNodes[2].classList.remove('d-off');\n }\n });\n });\n\n taskDescription.addEventListener('input', () => {\n newTaskDescription[1] = task.childNodes[0].childNodes[2].innerText;\n });\n\n taskDescription.addEventListener('focusout', () => {\n if (newTaskDescription[0] !== newTaskDescription[1] && newTaskDescription[1] !== '') {\n const targetTask = new _modules_Task_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"]();\n targetTask.editTask(newTaskDescription[1], parseInt(task.id, 10) - 1);\n newTaskDescription.length = 2;\n }\n });\n });\n};\n\nconst dragDropIndices = [];\nconst checkTaskDragEvents = () => {\n const taskItems = document.querySelectorAll('.task');\n\n taskItems.forEach((task) => {\n task.addEventListener('dragstart', () => {\n if (dragDropIndices.length === 0) {\n dragDropIndices.push(parseInt(task.id, 10) - 1);\n }\n });\n\n task.addEventListener('dragover', (event) => {\n event.preventDefault();\n\n task.classList.add('over');\n });\n\n task.addEventListener('drop', (event) => {\n event.stopImmediatePropagation();\n\n dragDropIndices.push(parseInt(task.id, 10) - 1);\n task.classList.remove('over');\n _modules_DataStore_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].swapTasks(dragDropIndices[0], dragDropIndices[1]);\n refreshTaskList();\n }, (function runAfterAll() {\n dragDropIndices.length = 0;\n }()));\n\n task.addEventListener('dragleave', () => {\n task.classList.remove('over');\n });\n });\n};\n\nconst clearAllCompletedTasksEvent = () => {\n const btnClearAllCompleted = document.querySelector('.btn-clear-completed');\n\n btnClearAllCompleted.addEventListener('click', (event) => {\n event.stopPropagation();\n\n let completedTasksArray = [];\n const completedTasks = document.querySelectorAll('input[name=\"isCompleted\"]');\n [...completedTasks].filter((btnChecked) => btnChecked.checked === true);\n completedTasks.forEach((btnCheck) => {\n if (btnCheck.checked === true) {\n const targetTaskItem = btnCheck.parentNode.parentNode.parentNode;\n completedTasksArray.push(targetTaskItem);\n }\n });\n\n completedTasksArray.forEach((item) => {\n const targetTask = new _modules_Task_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"]();\n targetTask.removeTask(parseInt(item.id, 10));\n item.parentNode.removeChild(item);\n }, (function runAfterAll() {\n completedTasksArray = [];\n refreshTaskList();\n }()));\n });\n};\n\nconst renderTaskItem = (task) => {\n if (!task) return;\n\n const tasksContainer = document.querySelector('.todo-list');\n const taskItem = document.createElement('li');\n taskItem.innerHTML = `\n \n `;\n taskItem.id = task.index;\n taskItem.setAttribute('draggable', 'true');\n taskItem.classList.add('task');\n\n tasksContainer.appendChild(taskItem);\n\n isTaskClicked();\n checkTaskDragEvents();\n isTaskCompleted();\n clearAllCompletedTasksEvent();\n};\n\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (renderTaskItem);\n\n//# sourceURL=webpack://todo-list/./src/components/TaskListItem.js?");
160 |
161 | /***/ }),
162 |
163 | /***/ "./src/index.js":
164 | /*!**********************!*\
165 | !*** ./src/index.js ***!
166 | \**********************/
167 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
168 |
169 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _assets_css_style_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./assets/css/style.css */ \"./src/assets/css/style.css\");\n/* harmony import */ var _assets_img_todo_list_desktop_v_snapshot_png__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./assets/img/todo-list-desktop-v-snapshot.png */ \"./src/assets/img/todo-list-desktop-v-snapshot.png\");\n/* harmony import */ var _modules_Task_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./modules/Task.js */ \"./src/modules/Task.js\");\n/* harmony import */ var _modules_DataStore_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./modules/DataStore.js */ \"./src/modules/DataStore.js\");\n/* harmony import */ var _components_HeadingTaskListItem_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./components/HeadingTaskListItem.js */ \"./src/components/HeadingTaskListItem.js\");\n/* harmony import */ var _components_TaskListItem_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./components/TaskListItem.js */ \"./src/components/TaskListItem.js\");\n/* harmony import */ var _components_CreateNewTaskItem_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./components/CreateNewTaskItem.js */ \"./src/components/CreateNewTaskItem.js\");\n/* harmony import */ var _components_ClearAllButton_js__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./components/ClearAllButton.js */ \"./src/components/ClearAllButton.js\");\n\n\n\n\n\n\n\n\n\n\nwindow.addEventListener('DOMContentLoaded', () => {\n const todoList = document.querySelector('.todo-list');\n todoList.appendChild((0,_components_HeadingTaskListItem_js__WEBPACK_IMPORTED_MODULE_4__[\"default\"])());\n todoList.appendChild((0,_components_CreateNewTaskItem_js__WEBPACK_IMPORTED_MODULE_6__[\"default\"])());\n\n if (localStorage.getItem('tasks') === 'undefined' || localStorage.getItem('tasks') === null) {\n localStorage.setItem('tasks', JSON.stringify([]));\n }\n\n _modules_DataStore_js__WEBPACK_IMPORTED_MODULE_3__[\"default\"].tasks = JSON.parse(localStorage.getItem('tasks'));\n\n _modules_DataStore_js__WEBPACK_IMPORTED_MODULE_3__[\"default\"].tasks.forEach((task) => {\n if (task?.description === '') {\n const taskToBeRemoved = new _modules_Task_js__WEBPACK_IMPORTED_MODULE_2__[\"default\"]();\n taskToBeRemoved.removeTask(parseInt(task.index, 10));\n (0,_components_TaskListItem_js__WEBPACK_IMPORTED_MODULE_5__.refreshTaskList)();\n } else (0,_components_TaskListItem_js__WEBPACK_IMPORTED_MODULE_5__[\"default\"])(task);\n });\n\n todoList.parentNode.appendChild((0,_components_ClearAllButton_js__WEBPACK_IMPORTED_MODULE_7__[\"default\"])());\n\n (0,_components_HeadingTaskListItem_js__WEBPACK_IMPORTED_MODULE_4__.checkRefreshButtonEvent)();\n});\n\n//# sourceURL=webpack://todo-list/./src/index.js?");
170 |
171 | /***/ }),
172 |
173 | /***/ "./src/modules/DataStore.js":
174 | /*!**********************************!*\
175 | !*** ./src/modules/DataStore.js ***!
176 | \**********************************/
177 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
178 |
179 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ DataStore)\n/* harmony export */ });\nclass DataStore {\n static constructor() {\n this.tasks = [];\n }\n\n static getTasks() {\n this.tasks = JSON.parse(localStorage.getItem('tasks'));\n return JSON.parse(localStorage.getItem('tasks'));\n }\n\n static saveTasks(task) {\n this.tasks.push(task);\n localStorage.setItem('tasks', JSON.stringify(this.tasks));\n }\n\n static editTask(newDescription, index) {\n this.tasks[index].description = newDescription;\n localStorage.setItem('tasks', JSON.stringify(this.tasks));\n }\n\n static deleteTask(index) {\n this.tasks = this.tasks.filter((task) => task.index !== index);\n localStorage.setItem('tasks', JSON.stringify(this.tasks));\n }\n\n static updateTaskIndex() {\n for (let i = 0; i < this.tasks.length; i += 1) {\n this.tasks[i].index = i + 1;\n }\n\n localStorage.setItem('tasks', JSON.stringify(this.tasks));\n\n return this.tasks;\n }\n\n static swapTasks(taskOneIndex, taskTwoIndex) {\n const temp = this.tasks[taskTwoIndex];\n this.tasks[taskTwoIndex] = this.tasks[taskOneIndex];\n this.tasks[taskOneIndex] = temp;\n\n DataStore.updateTaskIndex();\n }\n}\n\n//# sourceURL=webpack://todo-list/./src/modules/DataStore.js?");
180 |
181 | /***/ }),
182 |
183 | /***/ "./src/modules/Task.js":
184 | /*!*****************************!*\
185 | !*** ./src/modules/Task.js ***!
186 | \*****************************/
187 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
188 |
189 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (/* binding */ Task)\n/* harmony export */ });\n/* harmony import */ var _DataStore_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./DataStore.js */ \"./src/modules/DataStore.js\");\n\n\nclass Task {\n constructor(description, isCompleted = false, index) {\n this.description = description;\n this.isCompleted = isCompleted;\n this.index = index;\n }\n\n addTask() {\n _DataStore_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].saveTasks(this);\n }\n\n removeTask(index) {\n this.index = index;\n _DataStore_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].deleteTask(this.index);\n }\n\n editTask(description, index) {\n this.index = index;\n _DataStore_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].editTask(description, this.index);\n }\n}\n\n//# sourceURL=webpack://todo-list/./src/modules/Task.js?");
190 |
191 | /***/ }),
192 |
193 | /***/ "./src/assets/fonts/cocogoose-pro-regular.ttf":
194 | /*!****************************************************!*\
195 | !*** ./src/assets/fonts/cocogoose-pro-regular.ttf ***!
196 | \****************************************************/
197 | /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
198 |
199 | eval("module.exports = __webpack_require__.p + \"6731ef40f4416b5c9069.ttf\";\n\n//# sourceURL=webpack://todo-list/./src/assets/fonts/cocogoose-pro-regular.ttf?");
200 |
201 | /***/ }),
202 |
203 | /***/ "./src/assets/img/todo-list-desktop-v-snapshot.png":
204 | /*!*********************************************************!*\
205 | !*** ./src/assets/img/todo-list-desktop-v-snapshot.png ***!
206 | \*********************************************************/
207 | /***/ ((module, __unused_webpack_exports, __webpack_require__) => {
208 |
209 | eval("module.exports = __webpack_require__.p + \"a0d3b87095ec6aee15f3.png\";\n\n//# sourceURL=webpack://todo-list/./src/assets/img/todo-list-desktop-v-snapshot.png?");
210 |
211 | /***/ })
212 |
213 | /******/ });
214 | /************************************************************************/
215 | /******/ // The module cache
216 | /******/ var __webpack_module_cache__ = {};
217 | /******/
218 | /******/ // The require function
219 | /******/ function __webpack_require__(moduleId) {
220 | /******/ // Check if module is in cache
221 | /******/ var cachedModule = __webpack_module_cache__[moduleId];
222 | /******/ if (cachedModule !== undefined) {
223 | /******/ return cachedModule.exports;
224 | /******/ }
225 | /******/ // Create a new module (and put it into the cache)
226 | /******/ var module = __webpack_module_cache__[moduleId] = {
227 | /******/ id: moduleId,
228 | /******/ // no module.loaded needed
229 | /******/ exports: {}
230 | /******/ };
231 | /******/
232 | /******/ // Execute the module function
233 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
234 | /******/
235 | /******/ // Return the exports of the module
236 | /******/ return module.exports;
237 | /******/ }
238 | /******/
239 | /******/ // expose the modules object (__webpack_modules__)
240 | /******/ __webpack_require__.m = __webpack_modules__;
241 | /******/
242 | /************************************************************************/
243 | /******/ /* webpack/runtime/compat get default export */
244 | /******/ (() => {
245 | /******/ // getDefaultExport function for compatibility with non-harmony modules
246 | /******/ __webpack_require__.n = (module) => {
247 | /******/ var getter = module && module.__esModule ?
248 | /******/ () => (module['default']) :
249 | /******/ () => (module);
250 | /******/ __webpack_require__.d(getter, { a: getter });
251 | /******/ return getter;
252 | /******/ };
253 | /******/ })();
254 | /******/
255 | /******/ /* webpack/runtime/define property getters */
256 | /******/ (() => {
257 | /******/ // define getter functions for harmony exports
258 | /******/ __webpack_require__.d = (exports, definition) => {
259 | /******/ for(var key in definition) {
260 | /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
261 | /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
262 | /******/ }
263 | /******/ }
264 | /******/ };
265 | /******/ })();
266 | /******/
267 | /******/ /* webpack/runtime/global */
268 | /******/ (() => {
269 | /******/ __webpack_require__.g = (function() {
270 | /******/ if (typeof globalThis === 'object') return globalThis;
271 | /******/ try {
272 | /******/ return this || new Function('return this')();
273 | /******/ } catch (e) {
274 | /******/ if (typeof window === 'object') return window;
275 | /******/ }
276 | /******/ })();
277 | /******/ })();
278 | /******/
279 | /******/ /* webpack/runtime/hasOwnProperty shorthand */
280 | /******/ (() => {
281 | /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
282 | /******/ })();
283 | /******/
284 | /******/ /* webpack/runtime/make namespace object */
285 | /******/ (() => {
286 | /******/ // define __esModule on exports
287 | /******/ __webpack_require__.r = (exports) => {
288 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
289 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
290 | /******/ }
291 | /******/ Object.defineProperty(exports, '__esModule', { value: true });
292 | /******/ };
293 | /******/ })();
294 | /******/
295 | /******/ /* webpack/runtime/publicPath */
296 | /******/ (() => {
297 | /******/ var scriptUrl;
298 | /******/ if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + "";
299 | /******/ var document = __webpack_require__.g.document;
300 | /******/ if (!scriptUrl && document) {
301 | /******/ if (document.currentScript)
302 | /******/ scriptUrl = document.currentScript.src
303 | /******/ if (!scriptUrl) {
304 | /******/ var scripts = document.getElementsByTagName("script");
305 | /******/ if(scripts.length) scriptUrl = scripts[scripts.length - 1].src
306 | /******/ }
307 | /******/ }
308 | /******/ // When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration
309 | /******/ // or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.
310 | /******/ if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
311 | /******/ scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/");
312 | /******/ __webpack_require__.p = scriptUrl;
313 | /******/ })();
314 | /******/
315 | /******/ /* webpack/runtime/jsonp chunk loading */
316 | /******/ (() => {
317 | /******/ __webpack_require__.b = document.baseURI || self.location.href;
318 | /******/
319 | /******/ // object to store loaded and loading chunks
320 | /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
321 | /******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
322 | /******/ var installedChunks = {
323 | /******/ "main": 0
324 | /******/ };
325 | /******/
326 | /******/ // no chunk on demand loading
327 | /******/
328 | /******/ // no prefetching
329 | /******/
330 | /******/ // no preloaded
331 | /******/
332 | /******/ // no HMR
333 | /******/
334 | /******/ // no HMR manifest
335 | /******/
336 | /******/ // no on chunks loaded
337 | /******/
338 | /******/ // no jsonp function
339 | /******/ })();
340 | /******/
341 | /************************************************************************/
342 | /******/
343 | /******/ // startup
344 | /******/ // Load entry module and return exports
345 | /******/ // This entry module can't be inlined because the eval devtool is used.
346 | /******/ var __webpack_exports__ = __webpack_require__("./src/index.js");
347 | /******/
348 | /******/ })()
349 | ;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo-list",
3 | "jest": {
4 | "verbose": true,
5 | "resetMocks": false,
6 | "setupFiles": [
7 | "jest-localstorage-mock"
8 | ],
9 | "testEnvironment": "jsdom"
10 | },
11 | "version": "1.0.0",
12 | "description": "",
13 | "private": true,
14 | "scripts": {
15 | "test": "jest --coverage",
16 | "watch": "jest --watch *.js",
17 | "start": "webpack serve --open",
18 | "build": "webpack",
19 | "predeploy": "npm run build",
20 | "deploy": "gh-pages -d dist"
21 | },
22 | "repository": {
23 | "type": "git",
24 | "url": "git+https://github.com/gtekle/todo-list.git"
25 | },
26 | "keywords": [],
27 | "author": "",
28 | "license": "ISC",
29 | "bugs": {
30 | "url": "https://github.com/gtekle/todo-list/issues"
31 | },
32 | "homepage": "https://github.com/gtekle/todo-list#readme",
33 | "devDependencies": {
34 | "@babel/plugin-transform-modules-commonjs": "^7.16.8",
35 | "babel-eslint": "^10.1.0",
36 | "css-loader": "^6.5.1",
37 | "enzyme": "^3.11.0",
38 | "enzyme-adapter-react-16": "^1.15.6",
39 | "eslint": "^7.32.0",
40 | "eslint-config-airbnb-base": "^14.2.1",
41 | "eslint-plugin-import": "^2.25.4",
42 | "hint": "^6.1.9",
43 | "html-webpack-plugin": "^5.5.0",
44 | "jest": "^27.4.7",
45 | "jest-localstorage-mock": "^2.4.18",
46 | "style-loader": "^3.3.1",
47 | "stylelint": "^14.2.0",
48 | "stylelint-config-standard": "^21.0.0",
49 | "stylelint-csstree-validator": "^1.9.0",
50 | "stylelint-scss": "^3.21.0",
51 | "webpack": "^5.65.0",
52 | "webpack-cli": "^4.9.1",
53 | "webpack-dev-server": "^4.7.3"
54 | },
55 | "dependencies": {
56 | "gh-pages": "^3.2.3",
57 | "jquery": "^3.6.0"
58 | }
59 | }
--------------------------------------------------------------------------------
/src/assets/css/style.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::after,
3 | *::before {
4 | box-sizing: border-box;
5 | }
6 |
7 | @font-face {
8 | font-family: cocogoose;
9 | src: url('../fonts/cocogoose-pro-regular.ttf');
10 | }
11 |
12 | body {
13 | background-color: #ddd;
14 | margin: 0;
15 | font-family: Arial, Helvetica, sans-serif;
16 | }
17 |
18 | .btn {
19 | cursor: pointer;
20 | border: none;
21 | background-color: #fff;
22 | margin: 0;
23 | }
24 |
25 | #header,
26 | #footer {
27 | background-color: darkgray;
28 | width: 100%;
29 | height: 5px;
30 | padding: 0;
31 | margin: 0;
32 | }
33 |
34 | #main {
35 | max-width: 94%;
36 | min-height: calc(100vh - 64px);
37 | margin: 12px auto;
38 | text-align: center;
39 | }
40 |
41 | .main-section-title {
42 | color: darkslategray;
43 | font-family: cocogoose, sans-serif;
44 | }
45 |
46 | .todo-list {
47 | list-style: none;
48 | text-align: center;
49 | padding: 0;
50 | background-color: #d3d3d3;
51 | margin: 0;
52 | box-shadow: 4px 4px 4px 2px rgba(0, 0, 0, 0.2);
53 | }
54 |
55 | .todo-heading {
56 | display: flex;
57 | justify-content: space-between;
58 | }
59 |
60 | .todo-heading h2 {
61 | color: darkgray;
62 | font-size: 20px;
63 | line-height: 24px;
64 | font-weight: 700;
65 | }
66 |
67 | .todo-heading .btn-refresh {
68 | text-align: right;
69 | padding: 0;
70 | margin-right: 10px;
71 | }
72 |
73 | .todo-heading i {
74 | color: darkgray;
75 | font-size: 22px;
76 | font-weight: 500;
77 | }
78 |
79 | .btn i:hover {
80 | color: black;
81 | }
82 |
83 | .btn i:active {
84 | color: blueviolet;
85 | }
86 |
87 | #form-add-task {
88 | display: flex;
89 | width: 100%;
90 | text-align: justify;
91 | background-color: #fff;
92 | border-bottom: 1px solid gray;
93 | }
94 |
95 | #form-add-task input {
96 | width: calc(100% - 43px);
97 | margin: 0;
98 | font-size: 17px;
99 | line-height: 32px;
100 | padding: 3px 10px;
101 | font-style: italic;
102 | border: none;
103 | outline: none;
104 | }
105 |
106 | #form-add-task .btn-add-task {
107 | display: flex;
108 | flex-direction: column;
109 | justify-content: center;
110 | align-items: center;
111 | color: grey;
112 | font-weight: 700;
113 | vertical-align: text-top;
114 | font-size: 16px;
115 | line-height: 24px;
116 | }
117 |
118 | .todo-list li {
119 | display: flex;
120 | border-bottom: 1px solid #bbb;
121 | justify-content: space-between;
122 | align-items: center;
123 | background-color: #fff;
124 | padding-left: 10px;
125 | font-size: 16px;
126 | font-weight: 500;
127 | cursor: text;
128 | }
129 |
130 | input[name="isCompleted"] {
131 | cursor: pointer;
132 | }
133 |
134 | .item-description {
135 | display: flex;
136 | align-items: center;
137 | font-size: 18px;
138 | width: 100%;
139 | }
140 |
141 | .todo-list li .task-description {
142 | color: rgba(0, 0, 0, 0.8);
143 | width: 100%;
144 | margin: 0;
145 | padding: 10px;
146 | font-size: 16px;
147 | font-weight: 500;
148 | text-align: left;
149 | }
150 |
151 | .todo-list .fa-ellipsis-v {
152 | color: grey;
153 | font-size: 16px;
154 | padding: 5px;
155 | cursor: move;
156 | z-index: 10;
157 | }
158 |
159 | .todo-list .btn-v-ellipsis {
160 | background-color: transparent;
161 | z-index: 100;
162 | }
163 |
164 | .todo-list .btn-trash {
165 | background-color: transparent;
166 | z-index: 110;
167 | }
168 |
169 | .todo-list .fa-trash {
170 | color: red;
171 | font-size: 16px;
172 | padding: 5px;
173 | }
174 |
175 | .todo-list .fa-ellipsis-v:hover {
176 | color: black;
177 | }
178 |
179 | .todo-list .over {
180 | background-color: #cababa;
181 | }
182 |
183 | .todo-list li > * {
184 | margin-right: 10px;
185 | }
186 |
187 | .btn-clear-completed {
188 | padding: 6px;
189 | margin: 10px;
190 | font-size: 16px;
191 | background-color: transparent;
192 | font-weight: 700;
193 | color: darkslategray;
194 | }
195 |
196 | .d-off {
197 | display: none;
198 | }
199 |
200 | @media screen and (min-width: 768px) {
201 | #main {
202 | max-width: 40%;
203 | min-height: calc(100vh - 88px);
204 | margin: 24px auto;
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/assets/fonts/cocogoose-pro-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gtekle/todo-list/66daf5675b29b0ae9965acaef53895127ddec5e8/src/assets/fonts/cocogoose-pro-regular.ttf
--------------------------------------------------------------------------------
/src/assets/img/todo-list-desktop-v-snapshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gtekle/todo-list/66daf5675b29b0ae9965acaef53895127ddec5e8/src/assets/img/todo-list-desktop-v-snapshot.png
--------------------------------------------------------------------------------
/src/components/ClearAllButton.js:
--------------------------------------------------------------------------------
1 | const btnClearAllCompleted = () => {
2 | const btnClearCompleted = document.createElement('button');
3 | btnClearCompleted.setAttribute('type', 'button');
4 | btnClearCompleted.setAttribute('name', 'btnClearCompleted');
5 | btnClearCompleted.innerText = 'Clear all completed';
6 | btnClearCompleted.classList.add('btn', 'btn-clear-completed');
7 |
8 | return btnClearCompleted;
9 | };
10 |
11 | export default btnClearAllCompleted;
--------------------------------------------------------------------------------
/src/components/CreateNewTaskItem.js:
--------------------------------------------------------------------------------
1 | import Task from '../modules/Task.js';
2 | import DataStore from '../modules/DataStore.js';
3 |
4 | import renderTaskItem, { refreshTaskList } from './TaskListItem.js';
5 |
6 | const formAddTask = () => {
7 | const form = document.createElement('form');
8 | form.id = 'form-add-task';
9 | form.innerHTML = `
10 | `;
13 |
14 | form.addEventListener('submit', (event) => {
15 | event.preventDefault();
16 |
17 | if (localStorage.getItem('tasks') === 'undefined') {
18 | localStorage.setItem('tasks', JSON.stringify([]));
19 | }
20 |
21 | DataStore.tasks = JSON.parse(localStorage.getItem('tasks'));
22 |
23 | const { newTask } = form.elements;
24 | const task = new Task(newTask.value, false, DataStore.tasks.length + 1);
25 | task.addTask();
26 | renderTaskItem(task);
27 | refreshTaskList();
28 | newTask.value = '';
29 | newTask.style.focus = false;
30 | });
31 | return form;
32 | };
33 |
34 | export default formAddTask;
--------------------------------------------------------------------------------
/src/components/HeadingTaskListItem.js:
--------------------------------------------------------------------------------
1 | import { refreshTaskList, clearAllCompletedTasksEvent } from './TaskListItem.js';
2 |
3 | const todoListHeading = () => {
4 | const todoHeading = document.createElement('li');
5 | todoHeading.innerHTML = ` Today's Tasks
6 | `;
8 | todoHeading.classList.add('todo-heading');
9 |
10 | return todoHeading;
11 | };
12 |
13 | export const checkRefreshButtonEvent = () => {
14 | const btnRefreshList = document.querySelector('.btn-refresh');
15 |
16 | btnRefreshList.addEventListener('click', (event) => {
17 | event.stopPropagation();
18 |
19 | refreshTaskList();
20 | });
21 |
22 | clearAllCompletedTasksEvent();
23 | };
24 |
25 | export default todoListHeading;
--------------------------------------------------------------------------------
/src/components/TaskListItem.js:
--------------------------------------------------------------------------------
1 | import DataStore from '../modules/DataStore.js';
2 | import Task from '../modules/Task.js';
3 |
4 | export const refreshTaskList = () => {
5 | const tasks = document.querySelectorAll('.task');
6 | tasks.forEach((prevTask) => {
7 | prevTask.parentNode.removeChild(prevTask);
8 | });
9 |
10 | DataStore.tasks = DataStore.updateTaskIndex();
11 | DataStore.tasks.forEach((task) => {
12 | // eslint-disable-next-line
13 | renderTaskItem(task);
14 | });
15 | };
16 |
17 | export const isTaskCompleted = () => {
18 | const completedTasks = document.querySelectorAll('input[name="isCompleted"]');
19 | completedTasks.forEach((btnIsCompleted) => {
20 | btnIsCompleted.addEventListener('click', () => {
21 | if (btnIsCompleted.checked) {
22 | btnIsCompleted.checked = true;
23 | btnIsCompleted.parentNode.nextSibling.nextSibling.style.textDecoration = 'line-through';
24 | } else {
25 | btnIsCompleted.checked = false;
26 | btnIsCompleted.parentNode.nextSibling.nextSibling.style.textDecoration = '';
27 | }
28 | });
29 | });
30 | };
31 |
32 | export const isTaskClicked = () => {
33 | const allTasks = document.querySelectorAll('.task');
34 |
35 | allTasks.forEach((task) => {
36 | const newTaskDescription = ['', ''];
37 |
38 | const [descriptionDiv, , btnMove, , btnDelete] = task.childNodes;
39 | const [, , description] = descriptionDiv.childNodes;
40 |
41 | task.addEventListener('click', () => {
42 | newTaskDescription[0] = description.innerText;
43 | newTaskDescription.push(parseInt(task.id, 10));
44 | description.contentEditable = true;
45 | description.focus();
46 |
47 | // The following two lines of code are used to move the cursor
48 | // to the end of existing text in contentEditable HTML5 elements.
49 | document.execCommand('selectAll', false, null);
50 | document.getSelection().collapseToEnd();
51 |
52 | task.style.backgroundColor = 'lightgreen';
53 | description.style.outline = 'none';
54 | description.style.color = 'blue';
55 | btnMove.classList.add('d-off');
56 | btnDelete.classList.remove('d-off');
57 |
58 | if (!btnDelete.classList.contains('d-off')) {
59 | btnDelete.addEventListener('click', (event) => {
60 | event.stopImmediatePropagation();
61 |
62 | const task = btnDelete.parentNode;
63 | const targetTask = new Task();
64 | targetTask.removeTask(parseInt(task.id, 10));
65 | task.parentNode.removeChild(task);
66 | refreshTaskList();
67 | });
68 | }
69 |
70 | allTasks.forEach((inactiveTask) => {
71 | if (task !== inactiveTask) {
72 | const [descriptionDiv, , btnMove, , btnDelete] = inactiveTask.childNodes;
73 | const [, , description] = descriptionDiv.childNodes;
74 |
75 | inactiveTask.style.backgroundColor = '#fff';
76 | description.contentEditable = false;
77 | description.style.color = '#000';
78 | description.style.opacity = '0.8';
79 | description.style.border = 'none';
80 | btnDelete.classList.add('d-off');
81 | btnMove.classList.remove('d-off');
82 | }
83 | });
84 | });
85 |
86 | description.addEventListener('input', () => {
87 | newTaskDescription[1] = description.innerText;
88 | });
89 |
90 | description.addEventListener('focusout', () => {
91 | if (newTaskDescription[0] !== newTaskDescription[1] && newTaskDescription[1] !== '') {
92 | const targetTask = new Task();
93 | targetTask.editTask(newTaskDescription[1], parseInt(task.id, 10) - 1);
94 | newTaskDescription.length = 2;
95 | }
96 | });
97 | });
98 | };
99 |
100 | const dragDropIndices = [];
101 | export const checkTaskDragEvents = () => {
102 | const taskItems = document.querySelectorAll('.task');
103 |
104 | taskItems.forEach((task) => {
105 | task.addEventListener('dragstart', () => {
106 | if (dragDropIndices.length === 0) {
107 | dragDropIndices.push(parseInt(task.id, 10) - 1);
108 | }
109 | });
110 |
111 | task.addEventListener('dragover', (event) => {
112 | event.preventDefault();
113 |
114 | task.classList.add('over');
115 | });
116 |
117 | task.addEventListener('drop', (event) => {
118 | event.stopImmediatePropagation();
119 |
120 | dragDropIndices.push(parseInt(task.id, 10) - 1);
121 | task.classList.remove('over');
122 | DataStore.swapTasks(dragDropIndices[0], dragDropIndices[1]);
123 | refreshTaskList();
124 | }, (function runAfterAll() {
125 | dragDropIndices.length = 0;
126 | }()));
127 |
128 | task.addEventListener('dragleave', () => {
129 | task.classList.remove('over');
130 | });
131 | });
132 | };
133 |
134 | export const clearAllCompletedTasksEvent = () => {
135 | const btnClearAllCompleted = document.querySelector('.btn-clear-completed');
136 |
137 | btnClearAllCompleted.addEventListener('click', (event) => {
138 | event.stopImmediatePropagation();
139 |
140 | let completedTasksArray = [];
141 | const completedTasks = document.querySelectorAll('input[name="isCompleted"]');
142 | [...completedTasks].filter((btnChecked) => btnChecked.checked === true);
143 | completedTasks.forEach((btnCheck) => {
144 | if (btnCheck.checked === true) {
145 | const targetTaskItem = btnCheck.parentNode.parentNode.parentNode;
146 | completedTasksArray.push(targetTaskItem);
147 | }
148 | });
149 |
150 | completedTasksArray.forEach((item) => {
151 | const targetListItem = document.getElementById(item.id);
152 | const targetTask = new Task();
153 | targetListItem.parentNode.removeChild(targetListItem);
154 | targetTask.removeTask(parseInt(item.id, 10));
155 | }, (function runAfterAll() {
156 | completedTasksArray = [];
157 | refreshTaskList();
158 | }()));
159 | });
160 | };
161 |
162 | const renderTaskItem = (task) => {
163 | if (!task) return;
164 |
165 | const tasksContainer = document.querySelector('.todo-list');
166 | const taskItem = document.createElement('li');
167 | taskItem.innerHTML = `
169 |
${task.description}
170 |
171 | `;
172 | taskItem.id = task.index;
173 | taskItem.setAttribute('draggable', 'true');
174 | taskItem.classList.add('task');
175 |
176 | tasksContainer.appendChild(taskItem);
177 |
178 | isTaskClicked();
179 | checkTaskDragEvents();
180 | isTaskCompleted();
181 | clearAllCompletedTasksEvent();
182 | };
183 |
184 | export default renderTaskItem;
--------------------------------------------------------------------------------
/src/components/TaskListItem.test.js:
--------------------------------------------------------------------------------
1 | import 'jest-localstorage-mock';
2 | import Task from '../modules/Task.js';
3 | import DataStore from '../modules/DataStore.js';
4 | import renderTaskItem, { isTaskCompleted, clearAllCompletedTasksEvent } from './TaskListItem.js';
5 | import btnClearAllCompleted from './ClearAllButton.js';
6 |
7 | const $ = require('jquery');
8 |
9 | const task = new Task('task bone', false, 1);
10 |
11 | beforeAll(() => {
12 | const todoList = document.createElement('ul');
13 | todoList.classList.add('todo-list');
14 | document.body.appendChild(todoList);
15 | document.body.appendChild(btnClearAllCompleted());
16 | DataStore.tasks = [];
17 | });
18 |
19 | describe('completed status', () => {
20 | test('completed status', () => {
21 | task.addTask(task);
22 | renderTaskItem(task);
23 | const taskStatus = document.getElementById(`chkcompleted-${task.index}`);
24 | const prevTaskStatus = taskStatus.checked;
25 | $(taskStatus).click();
26 | isTaskCompleted();
27 | expect(taskStatus.checked).toBe(!prevTaskStatus);
28 | });
29 | });
30 |
31 | describe('clear all completed tasks', () => {
32 | test('clear all completed tasks', () => {
33 | task.addTask(task);
34 | renderTaskItem(task);
35 | const taskStatus = document.getElementById(`chkcompleted-${task.index}`);
36 | $(taskStatus).click();
37 | clearAllCompletedTasksEvent();
38 | const deletedTask = document.querySelectorAll('li .task');
39 | expect(deletedTask).toHaveLength(0);
40 | });
41 | });
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | To Do List With Webpack
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | TO DO LIST
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import './assets/css/style.css';
2 | import './assets/img/todo-list-desktop-v-snapshot.png';
3 | import Task from './modules/Task.js';
4 | import DataStore from './modules/DataStore.js';
5 |
6 | import todoListHeading,
7 | {
8 | checkRefreshButtonEvent,
9 | } from './components/HeadingTaskListItem.js';
10 | import renderTaskItem, { refreshTaskList } from './components/TaskListItem.js';
11 | import formAddTask from './components/CreateNewTaskItem.js';
12 | import btnClearAllCompleted from './components/ClearAllButton.js';
13 |
14 | window.addEventListener('DOMContentLoaded', () => {
15 | const todoList = document.querySelector('.todo-list');
16 | todoList.appendChild(todoListHeading());
17 | todoList.appendChild(formAddTask());
18 | todoList.parentNode.appendChild(btnClearAllCompleted());
19 |
20 | if (localStorage.getItem('tasks') === 'undefined' || localStorage.getItem('tasks') === null) {
21 | localStorage.setItem('tasks', JSON.stringify([]));
22 | }
23 |
24 | DataStore.tasks = JSON.parse(localStorage.getItem('tasks'));
25 |
26 | DataStore.tasks.forEach((task) => {
27 | if (task?.description === '') {
28 | const taskToBeRemoved = new Task();
29 | taskToBeRemoved.removeTask(parseInt(task.index, 10));
30 | refreshTaskList();
31 | } else renderTaskItem(task);
32 | });
33 |
34 | checkRefreshButtonEvent();
35 | });
--------------------------------------------------------------------------------
/src/modules/DataStore.js:
--------------------------------------------------------------------------------
1 | export default class DataStore {
2 | static constructor() {
3 | this.tasks = [];
4 | }
5 |
6 | static getTasks() {
7 | this.tasks = JSON.parse(localStorage.getItem('tasks'));
8 | return JSON.parse(localStorage.getItem('tasks'));
9 | }
10 |
11 | static saveTasks(task) {
12 | this.tasks.push(task);
13 | localStorage.setItem('tasks', JSON.stringify(this.tasks));
14 | }
15 |
16 | static editTask(newDescription, index) {
17 | this.tasks[index].description = newDescription;
18 | localStorage.setItem('tasks', JSON.stringify(this.tasks));
19 | }
20 |
21 | static deleteTask(index) {
22 | this.tasks = this.tasks.filter((task) => task.index !== index);
23 | localStorage.setItem('tasks', JSON.stringify(this.tasks));
24 | DataStore.updateTaskIndex();
25 | }
26 |
27 | static updateTaskIndex() {
28 | for (let i = 0; i < this.tasks.length; i += 1) {
29 | this.tasks[i].index = i + 1;
30 | }
31 |
32 | localStorage.setItem('tasks', JSON.stringify(this.tasks));
33 |
34 | return this.tasks;
35 | }
36 |
37 | static swapTasks(taskOneIndex, taskTwoIndex) {
38 | let prevTask = this.tasks[taskOneIndex];
39 | let i = taskTwoIndex;
40 |
41 | if (taskOneIndex > taskTwoIndex) {
42 | while (i <= taskOneIndex) {
43 | const temp = this.tasks[i];
44 | this.tasks[i] = prevTask;
45 | prevTask = temp;
46 | i += 1;
47 | }
48 | } else {
49 | while (i >= taskOneIndex) {
50 | const temp = this.tasks[i];
51 | this.tasks[i] = prevTask;
52 | prevTask = temp;
53 | i -= 1;
54 | }
55 | }
56 |
57 | DataStore.updateTaskIndex();
58 | }
59 | }
--------------------------------------------------------------------------------
/src/modules/Task.js:
--------------------------------------------------------------------------------
1 | import DataStore from './DataStore.js';
2 |
3 | export default class Task {
4 | constructor(description, isCompleted = false, index) {
5 | this.description = description;
6 | this.isCompleted = isCompleted;
7 | this.index = index;
8 | }
9 |
10 | addTask() {
11 | DataStore.saveTasks(this);
12 | }
13 |
14 | removeTask(index) {
15 | this.index = index;
16 | DataStore.deleteTask(this.index);
17 | }
18 |
19 | editTask(description, index) {
20 | this.index = index;
21 | DataStore.editTask(description, this.index - 1);
22 | }
23 | }
--------------------------------------------------------------------------------
/src/modules/Task.test.js:
--------------------------------------------------------------------------------
1 | import Task from './Task.js';
2 | import DataStore from './DataStore.js';
3 | import 'jest-localstorage-mock';
4 | import renderTaskItem from '../components/TaskListItem.js';
5 | import btnClearAllCompleted from '../components/ClearAllButton.js';
6 |
7 | const task = new Task('task one', false, 1);
8 |
9 | beforeAll(() => {
10 | const todoList = document.createElement('ul');
11 | todoList.classList.add('todo-list');
12 | document.body.appendChild(todoList);
13 | document.body.appendChild(btnClearAllCompleted());
14 | DataStore.tasks = [];
15 | });
16 |
17 | describe('add Task', () => {
18 | test('add method', () => {
19 | task.addTask(task);
20 | expect(DataStore.tasks.length).toBe(1);
21 | });
22 |
23 | test('render task item to the list', () => {
24 | renderTaskItem(task);
25 | const list = document.querySelectorAll('.task');
26 | expect(list).toHaveLength(1);
27 | });
28 | });
29 |
30 | describe('edit Task', () => {
31 | test('editTask method', () => {
32 | task.editTask('task one edited', task.index);
33 | expect(DataStore.tasks[task.index - 1].description).toBe('task one edited');
34 | });
35 |
36 | test('render task with edit description in DOM', () => {
37 | const targetTask = document.getElementById(task.index);
38 | targetTask.parentNode.removeChild(targetTask);
39 | renderTaskItem(DataStore.tasks[task.index - 1]);
40 | const updateTaskDescription = document.querySelector('.task .task-description');
41 | expect(updateTaskDescription.innerHTML).toBe('task one edited');
42 | });
43 | });
44 |
45 | describe('remove Task', () => {
46 | test('removeTask method', () => {
47 | task.removeTask(task.index);
48 | expect(DataStore.tasks.length).toBe(0);
49 | });
50 |
51 | test('remove task from DOM', () => {
52 | const targetTask = document.getElementById(task.index);
53 | targetTask.parentNode.removeChild(targetTask);
54 | const deletedTask = document.querySelectorAll('li .task');
55 | expect(deletedTask).toHaveLength(0);
56 | });
57 | });
58 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebpackPlugin = require('html-webpack-plugin');
2 | const path = require('path');
3 |
4 | module.exports = {
5 | mode: 'development',
6 | entry: './src/index.js',
7 | output: {
8 | filename: 'main.js',
9 | path: path.resolve(__dirname, 'dist'),
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.css$/i,
15 | use: ['style-loader', 'css-loader'],
16 | },
17 | {
18 | test: /\.(png|svg|jpg|jpeg|gif)$/i,
19 | type: 'asset/resource',
20 | },
21 | {
22 | test: /\.(woff|woff2|eot|ttf|otf)$/i,
23 | type: 'asset/resource',
24 | },
25 | ],
26 | },
27 | devServer: {
28 | static: './dist',
29 | },
30 | plugins: [
31 | new HtmlWebpackPlugin({
32 | title: 'Custom template',
33 | template: './src/index.html',
34 | }),
35 | ],
36 | };
--------------------------------------------------------------------------------