├── .babelrc
├── .eslintrc.json
├── .github
└── workflows
│ └── linters.yml
├── .gitignore
├── .hintrc
├── .stylelintrc.json
├── README.md
├── dist
├── index.html
└── main.bundle.js
├── index.html
├── package-lock.json
├── package.json
├── screen.gif
├── src
├── __mock__
│ └── todos.test.js
├── index.html
├── index.js
├── style.css
├── todos.js
└── todosRender.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "test": {
4 | "plugins": ["@babel/plugin-transform-modules-commonjs"]
5 | }
6 | }
7 | }
--------------------------------------------------------------------------------
/.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": [ 1, {
18 | "js": "always", "json": "always"
19 | }]
20 | },
21 | "ignorePatterns": [
22 | "dist/",
23 | "build/"
24 | ]
25 | }
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # .gitignore
2 | node_modules/
--------------------------------------------------------------------------------
/.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 | }
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "stylelint-config-standard"
4 | ],
5 | "plugins": [
6 | "stylelint-scss",
7 | "stylelint-csstree-validator"
8 | ],
9 | "rules": {
10 | "at-rule-no-unknown": null,
11 | "scss/at-rule-no-unknown": true,
12 | "csstree/validator": true
13 | },
14 | "ignoreFiles": [
15 | "build/**",
16 | "dist/**",
17 | "**/reset*.css",
18 | "**/bootstrap*.css",
19 | "**/*.js",
20 | "**/*.jsx"
21 | ]
22 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Todo-list-webpack
4 |
5 | > Todo App is for listing tasks. These tasks are stored in the local storage and can be marked as completed, edited and deleted. It is built using JavaScript, jest, HTML and CSS.
6 |
7 | 
8 |
9 | ## Built With
10 |
11 | - Major languages
12 | ```bash
13 | - (HTML, CSS, JavaScript, webpack)
14 | ```
15 |
16 |
17 | - Frameworks / Libraries
18 | ```bash
19 | - Jest
20 | ```
21 |
22 | - Technologies used
23 |
24 | ``` bash
25 | - Git(version control)
26 | ```
27 |
28 | ## Live Demo
29 | [GitHub Pages](https://omarsalem7.github.io/Todo-list-webpack/)
30 | [Netlify](https://todo-webpack-v1.netlify.app/)
31 |
32 |
33 | ## Getting Started
34 | To get a local copy up and running follow these simple example steps.
35 |
36 | ### Prerequisites
37 | - A text editor(preferably Visual Studio Code)
38 |
39 | ### Install
40 | - [Git](https://git-scm.com/downloads)
41 | - [Node](https://nodejs.org/en/download/)
42 | ### Usage
43 | #### Clone this repository
44 |
45 | ```bash
46 | $ git clone https://github.com/omarsalem7/Todo-list-webpack.git
47 | $ cd Todo-list-webpack
48 | ```
49 | #### Run project
50 |
51 | ```bash
52 | $ npm install
53 | $ npm build
54 | $ npm start
55 | ```
56 |
57 | #### Open page in browser
58 | ```bash
59 | $ runs on http://localhost:3000/
60 | ```
61 |
62 | ## Authors
63 |
64 | 👤 **Omar Salem**
65 |
66 | - GitHub: [Omar Salem](https://github.com/omarsalem7)
67 | - Twitter: [Omar Salem](https://twitter.com/Omar80491499)
68 | - LinkedIn: [Omar Salem](https://www.linkedin.com/in/omar-salem-a6945b177/)
69 |
70 |
71 | ## 🤝 Contributing
72 |
73 | Contributions, issues, and feature requests are welcome!
74 |
75 | Feel free to check the [issues page](../../issues/).
76 |
77 | ## Show your support
78 |
79 | Give a ⭐ if you like this project!
80 |
81 | ## 📝 License
82 |
83 | This project is [Minimalist](https://web.archive.org/web/20180320194056/http://www.getminimalist.com:80/) licensed.
84 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | My Todo list
8 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
Today's Todo
19 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/dist/main.bundle.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, \"* {\\r\\n margin: 0;\\r\\n padding: 0;\\r\\n}\\r\\n\\r\\n.hello {\\r\\n color: rgb(112, 189, 122);\\r\\n}\\r\\n\\r\\n.container {\\r\\n box-shadow: 0 2px 8px rgb(0 0 0 / 15%);\\r\\n max-width: 40%;\\r\\n margin: 50px auto;\\r\\n}\\r\\n\\r\\n.title {\\r\\n padding: 3%;\\r\\n color: orange;\\r\\n}\\r\\n\\r\\nform {\\r\\n display: flex;\\r\\n justify-content: space-between;\\r\\n padding-right: 0;\\r\\n position: relative;\\r\\n border-top: 0.6px solid rgb(216, 216, 216);\\r\\n border-right: none;\\r\\n border-bottom: 0.6px solid rgb(216, 216, 216);\\r\\n border-left: none;\\r\\n}\\r\\n\\r\\n.todo-edit {\\r\\n border: none;\\r\\n}\\r\\n\\r\\nform input[type='text'] {\\r\\n border: none;\\r\\n font-style: italic;\\r\\n width: 85%;\\r\\n padding: 3%;\\r\\n}\\r\\n\\r\\nform .add-btn {\\r\\n position: relative;\\r\\n right: 3.2%;\\r\\n}\\r\\n\\r\\ninput[type='text']:focus {\\r\\n outline: none;\\r\\n}\\r\\n\\r\\n.todo-item {\\r\\n padding: 3%;\\r\\n border-bottom: 0.6px solid rgb(216, 216, 216);\\r\\n display: flex;\\r\\n flex-direction: row;\\r\\n justify-content: space-between;\\r\\n}\\r\\n\\r\\nbutton {\\r\\n background: none;\\r\\n color: inherit;\\r\\n border: none;\\r\\n padding: 0;\\r\\n font: inherit;\\r\\n cursor: pointer;\\r\\n outline: inherit;\\r\\n}\\r\\n\\r\\n.clear {\\r\\n background-color: rgba(240, 240, 240, 0.493);\\r\\n text-align: center;\\r\\n padding: 3%;\\r\\n}\\r\\n\\r\\n.clear a {\\r\\n cursor: pointer;\\r\\n color: rgba(255, 166, 0, 0.507);\\r\\n}\\r\\n\\r\\n.clear a:hover {\\r\\n color: orange;\\r\\n text-decoration: underline;\\r\\n font-weight: bold;\\r\\n}\\r\\n\\r\\n.checked {\\r\\n text-decoration: line-through;\\r\\n color: rgba(129, 129, 129, 0.548);\\r\\n}\\r\\n\", \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://Todo-list-webpack/./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-webpack/./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-webpack/./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-webpack/./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-webpack/./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-webpack/./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-webpack/./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-webpack/./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-webpack/./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-webpack/./node_modules/style-loader/dist/runtime/styleTagTransform.js?");
110 |
111 | /***/ }),
112 |
113 | /***/ "./src/index.js":
114 | /*!**********************!*\
115 | !*** ./src/index.js ***!
116 | \**********************/
117 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
118 |
119 | 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 _todosRender__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./todosRender */ \"./src/todosRender.js\");\n/* harmony import */ var _todos__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./todos */ \"./src/todos.js\");\n\r\n\r\n\r\n\r\nconst todosList = new _todos__WEBPACK_IMPORTED_MODULE_2__[\"default\"]();\r\n(0,_todosRender__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(todosList);\r\n\r\n// add todo\r\nconst addTodoBtn = document.querySelector('.add-btn');\r\naddTodoBtn.addEventListener('click', () => {\r\n const id = `id${Math.random().toString(16).slice(2)}`;\r\n const description = document.querySelector('.input-todo').value.trim();\r\n const completed = false;\r\n const index = todosList.list.length + 1;\r\n\r\n const newTodo = {\r\n id, description, completed, index,\r\n };\r\n if (description) {\r\n todosList.addTodo(newTodo);\r\n (0,_todosRender__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(todosList);\r\n }\r\n});\r\n\r\n// clear all completed todos\r\nconst clearBtn = document.querySelector('.clear-btn');\r\nclearBtn.addEventListener('click', () => {\r\n todosList.clearCompletedTodos();\r\n (0,_todosRender__WEBPACK_IMPORTED_MODULE_1__[\"default\"])(todosList);\r\n});\r\n\n\n//# sourceURL=webpack://Todo-list-webpack/./src/index.js?");
120 |
121 | /***/ }),
122 |
123 | /***/ "./src/todos.js":
124 | /*!**********************!*\
125 | !*** ./src/todos.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\": () => (/* binding */ Todos)\n/* harmony export */ });\nclass Todos {\r\n constructor() {\r\n this.list = localStorage.getItem('todos')\r\n ? JSON.parse(localStorage.getItem('todos'))\r\n : [];\r\n }\r\n\r\n addTodo(todo) {\r\n this.list.push(todo);\r\n localStorage.setItem('todos', JSON.stringify(this.list));\r\n }\r\n\r\n removeTodo(todoID) {\r\n this.list = this.list.filter((todo) => todo.id !== todoID);\r\n this.list.forEach((todo, index) => {\r\n todo.index = index + 1;\r\n });\r\n localStorage.setItem('todos', JSON.stringify(this.list));\r\n }\r\n\r\n editTodo(todoId, todoDescription) {\r\n this.list = this.list.map((todo) => {\r\n if (todo.id === todoId) {\r\n return { ...todo, description: todoDescription };\r\n }\r\n return todo;\r\n });\r\n localStorage.setItem('todos', JSON.stringify(this.list));\r\n }\r\n\r\n completeTodo(todoId, status) {\r\n const selected = this.list.findIndex((element) => element.id === todoId);\r\n this.list[selected].completed = status;\r\n localStorage.setItem('todos', JSON.stringify(this.list));\r\n }\r\n\r\n clearCompletedTodos() {\r\n this.list = this.list.filter((todo) => !todo.completed);\r\n this.list.forEach((todo, index) => {\r\n todo.index = index + 1;\r\n });\r\n localStorage.setItem('todos', JSON.stringify(this.list));\r\n }\r\n}\r\n\n\n//# sourceURL=webpack://Todo-list-webpack/./src/todos.js?");
130 |
131 | /***/ }),
132 |
133 | /***/ "./src/todosRender.js":
134 | /*!****************************!*\
135 | !*** ./src/todosRender.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 */ });\nconst render = (todosList) => {\r\n const sortedTodos = todosList.list.sort((a, b) => a.index - b.index);\r\n const todosContainer = document.querySelector('.todos');\r\n let todosHtml = '';\r\n sortedTodos.forEach(({ completed, description, id }) => {\r\n const checkedTodo = completed ? 'checked' : '';\r\n const checkClass = completed ? 'checked' : '';\r\n todosHtml += ` \r\n `;\r\n });\r\n todosContainer.innerHTML = todosHtml;\r\n\r\n // remove todo\r\n const removeBtns = document.querySelectorAll('.remove-btn');\r\n removeBtns.forEach((btn) => {\r\n btn.addEventListener('click', (e) => {\r\n const element = btn.parentNode;\r\n element.remove();\r\n todosList.removeTodo(e.target.parentNode.id);\r\n });\r\n });\r\n\r\n // edit todo\r\n const todosContent = document.querySelectorAll('.todo-edit');\r\n todosContent.forEach((todo) => {\r\n todo.addEventListener('focusin', (e) => {\r\n e.target.parentNode.parentNode.style.background = 'rgb(241,240,204)';\r\n e.target.style.background = 'rgb(241,240,204)';\r\n });\r\n todo.addEventListener('focusout', (e) => {\r\n e.target.style.background = 'white';\r\n e.target.parentNode.parentNode.style.background = 'white';\r\n });\r\n todo.addEventListener('input', (e) => {\r\n todosList.editTodo(e.target.id, e.target.value);\r\n });\r\n });\r\n\r\n // Complete Todo\r\n const todosCheck = document.querySelectorAll('.todo-check');\r\n todosCheck.forEach((todo) => {\r\n todo.addEventListener('change', (e) => {\r\n const { id } = e.target;\r\n todosList.completeTodo(id, e.target.checked);\r\n e.target.parentNode.lastElementChild.classList.toggle('checked');\r\n });\r\n });\r\n};\r\n\r\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (render);\r\n\n\n//# sourceURL=webpack://Todo-list-webpack/./src/todosRender.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 | ;
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
10 | Todo list
11 |
13 |
14 |
15 |
16 |
17 |
18 |
Today's Todo
19 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Todo-list-webpack",
3 | "version": "1.0.0",
4 | "description": "",
5 | "private": true,
6 | "scripts": {
7 | "test": "jest",
8 | "start": "webpack serve --open",
9 | "build": "webpack",
10 | "deploy": "gh-pages -d dist"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/omarsalem7/Todo-list-webpack.git"
15 | },
16 | "keywords": [],
17 | "author": "",
18 | "license": "ISC",
19 | "bugs": {
20 | "url": "https://github.com/omarsalem7/Todo-list-webpack/issues"
21 | },
22 | "homepage": "https://omarsalem7.io/Todo-list-webpack#readme",
23 | "devDependencies": {
24 | "@babel/plugin-transform-modules-commonjs": "^7.16.8",
25 | "babel-eslint": "^10.1.0",
26 | "css-loader": "^6.5.1",
27 | "eslint": "^7.32.0",
28 | "eslint-config-airbnb-base": "^14.2.1",
29 | "eslint-plugin-import": "^2.25.4",
30 | "hint": "^6.1.9",
31 | "html-webpack-plugin": "^5.5.0",
32 | "jest": "^27.4.7",
33 | "style-loader": "^3.3.1",
34 | "stylelint": "^13.13.1",
35 | "stylelint-config-standard": "^21.0.0",
36 | "stylelint-csstree-validator": "^1.9.0",
37 | "stylelint-scss": "^3.21.0",
38 | "webpack": "^5.65.0",
39 | "webpack-cli": "^4.9.1",
40 | "webpack-dev-server": "^4.7.2"
41 | },
42 | "dependencies": {
43 | "gh-pages": "^3.2.3",
44 | "lodash": "^4.17.21"
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/screen.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/omarsalem7/Todo-list-webpack/12ad8a9e2c8caec56720258ddf55d6b5e3a0abe4/screen.gif
--------------------------------------------------------------------------------
/src/__mock__/todos.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment jsdom
3 | */
4 | import Todos from '../todos';
5 |
6 | document.body.innerHTML = `
7 |
8 |
Today's Todo
9 |
15 |
16 |
17 |
18 |
21 |
22 | `;
23 |
24 | describe('add and remove', () => {
25 | window.localStorage = Storage.prototype;
26 | test('Add task', () => {
27 | const todoList = new Todos();
28 | const newTodo = {
29 | id: 'id123',
30 | description: 'task1',
31 | completed: false,
32 | index: 1,
33 | };
34 | const newTodo2 = {
35 | id: 'id12sdad3',
36 | description: 'task2',
37 | completed: false,
38 | index: 2,
39 | };
40 | todoList.addTodo(newTodo);
41 | expect(todoList.list).toHaveLength(1);
42 | todoList.addTodo(newTodo2);
43 | expect(todoList.list).toHaveLength(2);
44 | expect(todoList.list[1].description).toBe('task2');
45 | });
46 |
47 | test('remove task', () => {
48 | const todoList = new Todos();
49 | const newTodo = {
50 | id: 'id4d5sa',
51 | description: 'task3',
52 | completed: false,
53 | index: 3,
54 | };
55 | todoList.addTodo(newTodo);
56 | todoList.removeTodo(newTodo.id);
57 | expect(todoList.list[1].description).toBe('task2');
58 | expect(todoList.list).toHaveLength(2);
59 | });
60 | });
61 |
62 | describe('Edit test', () => {
63 | test('Editing', () => {
64 | const todoList = new Todos();
65 | const newTodo3 = {
66 | id: 'gfgdgrg',
67 | description: 'task33',
68 | completed: false,
69 | index: 3,
70 | };
71 | todoList.addTodo(newTodo3);
72 | todoList.editTodo(newTodo3.id, 'asd');
73 | expect(todoList.list[2].description).toBe('asd');
74 | expect(todoList.list).toHaveLength(3);
75 | });
76 | });
77 |
78 | describe('complete test', () => {
79 | test(' updating an items completed status', () => {
80 | const todoList = new Todos();
81 | const newTodo4 = {
82 | id: 'dasasds5sa',
83 | description: 'task5',
84 | completed: false,
85 | index: 4,
86 | };
87 | todoList.addTodo(newTodo4);
88 | todoList.completeTodo(newTodo4.id, true);
89 | expect(todoList.list[3].completed).toBeTruthy();
90 | expect(todoList.list).toHaveLength(4);
91 | });
92 | });
93 |
94 | describe('Clear all completed', () => {
95 | test('clear items completed', () => {
96 | const todoList = new Todos();
97 | todoList.clearCompletedTodos();
98 | expect(todoList.list).toHaveLength(3);
99 | expect(todoList.list[1].completed).toBeFalsy();
100 | });
101 | });
102 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | My Todo list
8 |
10 |
11 |
13 |
14 |
15 |
16 |
17 |
Today's Todo
18 |
24 |
25 |
26 |
27 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import './style.css';
2 | import render from './todosRender';
3 | import Todos from './todos';
4 |
5 | const todosList = new Todos();
6 | render(todosList);
7 |
8 | // add todo
9 | const addTodoBtn = document.querySelector('.add-btn');
10 | addTodoBtn.addEventListener('click', () => {
11 | const id = `id${Math.random().toString(16).slice(2)}`;
12 | const description = document.querySelector('.input-todo').value.trim();
13 | const completed = false;
14 | const index = todosList.list.length + 1;
15 |
16 | const newTodo = {
17 | id, description, completed, index,
18 | };
19 | if (description) {
20 | todosList.addTodo(newTodo);
21 | render(todosList);
22 | }
23 | });
24 |
25 | // clear all completed todos
26 | const clearBtn = document.querySelector('.clear-btn');
27 | clearBtn.addEventListener('click', () => {
28 | todosList.clearCompletedTodos();
29 | render(todosList);
30 | });
31 |
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | .hello {
7 | color: rgb(112, 189, 122);
8 | }
9 |
10 | .container {
11 | box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
12 | max-width: 40%;
13 | margin: 50px auto;
14 | }
15 |
16 | .title {
17 | padding: 3%;
18 | color: orange;
19 | }
20 |
21 | form {
22 | display: flex;
23 | justify-content: space-between;
24 | padding-right: 0;
25 | position: relative;
26 | border-top: 0.6px solid rgb(216, 216, 216);
27 | border-right: none;
28 | border-bottom: 0.6px solid rgb(216, 216, 216);
29 | border-left: none;
30 | }
31 |
32 | .todo-edit {
33 | border: none;
34 | }
35 |
36 | form input[type='text'] {
37 | border: none;
38 | font-style: italic;
39 | width: 85%;
40 | padding: 3%;
41 | }
42 |
43 | form .add-btn {
44 | position: relative;
45 | right: 3.2%;
46 | }
47 |
48 | input[type='text']:focus {
49 | outline: none;
50 | }
51 |
52 | .todo-item {
53 | padding: 3%;
54 | border-bottom: 0.6px solid rgb(216, 216, 216);
55 | display: flex;
56 | flex-direction: row;
57 | justify-content: space-between;
58 | }
59 |
60 | button {
61 | background: none;
62 | color: inherit;
63 | border: none;
64 | padding: 0;
65 | font: inherit;
66 | cursor: pointer;
67 | outline: inherit;
68 | }
69 |
70 | .clear {
71 | background-color: rgba(240, 240, 240, 0.493);
72 | text-align: center;
73 | padding: 3%;
74 | }
75 |
76 | .clear a {
77 | cursor: pointer;
78 | color: rgba(255, 166, 0, 0.507);
79 | }
80 |
81 | .clear a:hover {
82 | color: orange;
83 | text-decoration: underline;
84 | font-weight: bold;
85 | }
86 |
87 | .checked {
88 | text-decoration: line-through;
89 | color: rgba(129, 129, 129, 0.548);
90 | }
91 |
--------------------------------------------------------------------------------
/src/todos.js:
--------------------------------------------------------------------------------
1 | export default class Todos {
2 | constructor() {
3 | this.list = localStorage.getItem('todos')
4 | ? JSON.parse(localStorage.getItem('todos'))
5 | : [];
6 | }
7 |
8 | addTodo(todo) {
9 | this.list.push(todo);
10 | localStorage.setItem('todos', JSON.stringify(this.list));
11 | }
12 |
13 | removeTodo(todoID) {
14 | this.list = this.list.filter((todo) => todo.id !== todoID);
15 | this.list.forEach((todo, index) => {
16 | todo.index = index + 1;
17 | });
18 | localStorage.setItem('todos', JSON.stringify(this.list));
19 | }
20 |
21 | editTodo(todoId, todoDescription) {
22 | this.list = this.list.map((todo) => {
23 | if (todo.id === todoId) {
24 | return { ...todo, description: todoDescription };
25 | }
26 | return todo;
27 | });
28 | localStorage.setItem('todos', JSON.stringify(this.list));
29 | }
30 |
31 | completeTodo(todoId, status) {
32 | const selected = this.list.findIndex((element) => element.id === todoId);
33 | this.list[selected].completed = status;
34 | localStorage.setItem('todos', JSON.stringify(this.list));
35 | }
36 |
37 | clearCompletedTodos() {
38 | this.list = this.list.filter((todo) => !todo.completed);
39 | this.list.forEach((todo, index) => {
40 | todo.index = index + 1;
41 | });
42 | localStorage.setItem('todos', JSON.stringify(this.list));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/todosRender.js:
--------------------------------------------------------------------------------
1 | const render = (todosList) => {
2 | const sortedTodos = todosList.list.sort((a, b) => a.index - b.index);
3 | const todosContainer = document.querySelector('.todos');
4 | let todosHtml = '';
5 | sortedTodos.forEach(({ completed, description, id }) => {
6 | const checkedTodo = completed ? 'checked' : '';
7 | const checkClass = completed ? 'checked' : '';
8 | todosHtml += `
15 | `;
16 | });
17 | todosContainer.innerHTML = todosHtml;
18 |
19 | // remove todo
20 | const removeBtns = document.querySelectorAll('.remove-btn');
21 | removeBtns.forEach((btn) => {
22 | btn.addEventListener('click', (e) => {
23 | const element = btn.parentNode;
24 | element.remove();
25 | todosList.removeTodo(e.target.parentNode.id);
26 | });
27 | });
28 |
29 | // edit todo
30 | const todosContent = document.querySelectorAll('.todo-edit');
31 | todosContent.forEach((todo) => {
32 | todo.addEventListener('focusin', (e) => {
33 | e.target.parentNode.parentNode.style.background = 'rgb(241,240,204)';
34 | e.target.style.background = 'rgb(241,240,204)';
35 | });
36 | todo.addEventListener('focusout', (e) => {
37 | e.target.style.background = 'white';
38 | e.target.parentNode.parentNode.style.background = 'white';
39 | });
40 | todo.addEventListener('input', (e) => {
41 | todosList.editTodo(e.target.id, e.target.value);
42 | });
43 | });
44 |
45 | // Complete Todo
46 | const todosCheck = document.querySelectorAll('.todo-check');
47 | todosCheck.forEach((todo) => {
48 | todo.addEventListener('change', (e) => {
49 | const { id } = e.target;
50 | todosList.completeTodo(id, e.target.checked);
51 | e.target.parentNode.lastElementChild.classList.toggle('checked');
52 | });
53 | });
54 | };
55 |
56 | export default render;
57 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | module.exports = {
5 | mode: 'development',
6 | entry: './src/index.js',
7 | devServer: {
8 | static: './dist',
9 | },
10 | plugins: [
11 | new HtmlWebpackPlugin({
12 | template: './src/index.html',
13 | }),
14 | ],
15 | output: {
16 | filename: '[name].bundle.js',
17 | path: path.resolve(__dirname, 'dist'),
18 | clean: true,
19 | },
20 | module: {
21 | rules: [
22 | {
23 | test: /\.css$/i,
24 | use: ['style-loader', 'css-loader'],
25 | },
26 | ],
27 | },
28 | };
--------------------------------------------------------------------------------