├── .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 | ![](https://img.shields.io/badge/Todo-webpack-blueviolet) 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 | ![screenshot](./screen.gif) 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 |
20 | 21 | 24 |
25 |
26 | 27 |
28 |
29 | Clear All 30 |
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 \r\n
\r\n \r\n
\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 |
20 | 21 | 24 |
25 |
26 | 27 |
28 |
29 | Clear All 30 |
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 |
10 | 11 | 14 |
15 |
16 | 17 |
18 |
19 | Clear All 20 |
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 |
19 | 20 | 23 |
24 |
25 | 26 |
27 |
28 | Clear All 29 |
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 += `
9 |
10 | 11 | 12 |
13 | 14 |
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 | }; --------------------------------------------------------------------------------