├── src ├── modules │ ├── dragDrop.js │ ├── todo.js │ ├── localStorage.js │ ├── todoStatus.js │ ├── todoCRUD.test.js │ └── todoCRUD.js ├── index.js ├── index.html └── style.css ├── .gitattributes ├── Screenshot.png ├── babel.config.json ├── .babelrc ├── .hintrc ├── .eslintrc.json ├── .stylelintrc.json ├── webpack.config.js ├── dist ├── index.html └── main.js ├── LICENSE ├── package.json ├── .gitignore ├── .github └── workflows │ └── linters.yml └── README.md /src/modules/dragDrop.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-language=JavaScript 2 | -------------------------------------------------------------------------------- /Screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZewdieMc/To-Do-List/HEAD/Screenshot.png -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ] 5 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "plugins": ["@babel/plugin-transform-modules-commonjs"] 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/modules/todo.js: -------------------------------------------------------------------------------- 1 | export default class Todo { 2 | constructor(index, completed = false, description) { 3 | this.completed = completed; 4 | this.index = index; 5 | this.description = description; 6 | } 7 | } -------------------------------------------------------------------------------- /src/modules/localStorage.js: -------------------------------------------------------------------------------- 1 | const storeData = (todoList) => { 2 | localStorage.setItem('todoObjects', JSON.stringify(todoList)); 3 | }; 4 | 5 | const readData = () => JSON.parse(localStorage.getItem('todoObjects')) || []; 6 | 7 | export { storeData, readData }; -------------------------------------------------------------------------------- /src/modules/todoStatus.js: -------------------------------------------------------------------------------- 1 | import { storeData } from './localStorage.js'; 2 | 3 | const clearCompleted = (todoList) => { 4 | if (Array.isArray(todoList.list) && todoList.list.length) { 5 | const incomplete = todoList.list.filter((todo) => !todo.completed); 6 | incomplete.forEach((todo, index) => { 7 | todo.index = index; 8 | }); 9 | storeData(incomplete); 10 | } 11 | }; 12 | 13 | export default clearCompleted; 14 | -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard"], 3 | "plugins": ["stylelint-scss", "stylelint-csstree-validator"], 4 | "rules": { 5 | "at-rule-no-unknown": null, 6 | "scss/at-rule-no-unknown": [ 7 | true, 8 | { 9 | "ignoreAtRules": [ 10 | "tailwind", 11 | "apply", 12 | "variants", 13 | "responsive", 14 | "screen" 15 | ] 16 | } 17 | ] 18 | }, 19 | "csstree/validator": true, 20 | "ignoreFiles": ["build/**", "dist/**", "**/reset*.css", "**/bootstrap*.css"] 21 | } 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: './src/index.js', 6 | output: { 7 | filename: 'main.js', 8 | path: path.resolve(__dirname, 'dist'), 9 | }, 10 | devServer: { 11 | static: './dist', 12 | }, 13 | plugins: [ 14 | new HtmlWebpackPlugin({ 15 | template: path.resolve(__dirname, 'src', 'index.html'), 16 | }), 17 | ], 18 | devtool: false, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.(sass|scss|css)$/i, 23 | use: ['style-loader', 'css-loader', 'sass-loader'], 24 | }, 25 | ], 26 | }, 27 | }; -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | To DO List

Today's To Do

Clear All Completed
-------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './style.css'; 2 | import clearCompleted from './modules/todoStatus.js'; 3 | import todoList from './modules/todoCRUD.js'; 4 | import { readData } from './modules/localStorage.js'; 5 | 6 | window.onload = () => { 7 | const form = document.querySelector('form'); 8 | const inputTodo = document.querySelector('form .input-todo'); 9 | todoList.list = readData(); 10 | 11 | document.querySelector('.clear-todo').addEventListener('click', 12 | () => { 13 | clearCompleted(todoList); 14 | todoList.list = readData(); 15 | todoList.populateList(); 16 | }); 17 | 18 | if (todoList.list.length) { 19 | todoList.populateList(); 20 | } 21 | 22 | form.addEventListener('submit', (e) => { 23 | e.preventDefault(); 24 | }); 25 | 26 | inputTodo.addEventListener('keyup', (e) => { 27 | if (e.keyCode === 13) { 28 | e.preventDefault(); 29 | if (inputTodo.value) todoList.addTodo(inputTodo.value); 30 | inputTodo.value = ''; 31 | } 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | To DO List 11 | 12 | 13 |
14 |
15 |

Today's To Do

16 | 17 |
18 |
19 | 20 | 21 |
22 | 23 |
Clear All Completed
24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Zewdie Habtie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "to-do-list", 3 | "version": "1.0.0", 4 | "description": "Webpack setup project", 5 | "private": true, 6 | "scripts": { 7 | "dev": "webpack --mode development --config webpack.config.js", 8 | "build": "webpack --mode production", 9 | "test": "jest --coverage", 10 | "start": "webpack serve --open --mode development" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/ZewdieMc/To-Do-List.git" 15 | }, 16 | "author": "Zewdie Habtie", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/ZewdieMc/To-Do-List/issues" 20 | }, 21 | "homepage": "https://github.com/ZewdieMc/To-Do-List#readme", 22 | "devDependencies": { 23 | "@babel/core": "^7.21.0", 24 | "@babel/plugin-transform-modules-commonjs": "^7.21.2", 25 | "@babel/preset-env": "^7.20.2", 26 | "add": "^2.0.6", 27 | "babel-core": "^6.26.3", 28 | "babel-loader": "^9.1.2", 29 | "css-loader": "^6.7.3", 30 | "file-loader": "^6.2.0", 31 | "html-webpack-plugin": "^5.5.0", 32 | "jest": "^29.4.3", 33 | "jest-environment-jsdom": "^29.4.3", 34 | "node-sass": "^8.0.0", 35 | "sass": "^1.58.3", 36 | "sass-loader": "^13.2.0", 37 | "style-loader": "^3.3.1", 38 | "webpack": "^5.75.0", 39 | "webpack-cli": "^5.0.1", 40 | "webpack-dev-server": "^4.11.1" 41 | }, 42 | "dependencies": { 43 | "babel-eslint": "^10.1.0", 44 | "eslint": "^7.32.0", 45 | "eslint-config-airbnb-base": "^14.2.1", 46 | "eslint-plugin-import": "^2.27.5", 47 | "hint": "^2.0.0", 48 | "stylelint": "^13.13.1", 49 | "stylelint-config-standard": "^21.0.0", 50 | "stylelint-csstree-validator": "^1.9.0", 51 | "stylelint-scss": "^3.21.0", 52 | "webpack-bundle-analyzer": "^4.8.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | TODO 84 | # Gatsby files 85 | .cache/ 86 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 87 | # https://nextjs.org/blog/next-9-1#public-directory-support 88 | # public 89 | 90 | # vuepress build output 91 | .vuepress/dist 92 | 93 | # Serverless directories 94 | .serverless/ 95 | 96 | # FuseBox cache 97 | .fusebox/ 98 | 99 | # DynamoDB Local files 100 | .dynamodb/ 101 | 102 | # TernJS port file 103 | .tern-port 104 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | padding: 0; 3 | margin: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | font-family: 'Poppins', sans-serif; 9 | background-color: #eee; 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | min-height: 100vh; 14 | } 15 | 16 | .todo-container { 17 | position: relative; 18 | display: flex; 19 | flex-direction: column; 20 | justify-content: center; 21 | box-shadow: 0 3px 10px rgb(0 0 0 / 0.2); 22 | background-color: white; 23 | } 24 | 25 | form > .press-enter, 26 | .todo-header .refresh { 27 | color: #b9abab; 28 | } 29 | 30 | .todo-header, 31 | form { 32 | display: flex; 33 | justify-content: space-between; 34 | align-items: center; 35 | padding: 0.5rem 1rem; 36 | border-bottom: 1px solid #eee; 37 | } 38 | 39 | .todo-header { 40 | padding: 1.5rem 1rem; 41 | } 42 | 43 | .todo-list { 44 | width: 100%; 45 | list-style: none; 46 | padding: 0; 47 | } 48 | 49 | .todo-list .todo-item { 50 | display: flex; 51 | justify-content: space-between; 52 | align-items: center; 53 | padding: 0.5rem 1rem; 54 | border-bottom: 1px solid #eee; 55 | gap: 1.5rem; 56 | } 57 | 58 | .todo-item .hide { 59 | display: none; 60 | } 61 | 62 | .todo-item .todo-completed, 63 | .todo-item .todo-check, 64 | .todo-item .todo-delete { 65 | cursor: pointer; 66 | color: #ccc; 67 | } 68 | 69 | .todo-item .todo-check:hover, 70 | .todo-item .todo-completed:hover { 71 | color: green; 72 | } 73 | 74 | .todo-item .line-through { 75 | text-decoration: line-through; 76 | } 77 | 78 | .todo-item .todo-delete:hover { 79 | color: red; 80 | } 81 | 82 | .todo-list li .todo-ellipsis { 83 | cursor: move; 84 | color: #ccc; 85 | } 86 | 87 | .todo-list li .todo-ellipsis:hover { 88 | color: #000; 89 | } 90 | 91 | input { 92 | border: none; 93 | padding: 10px; 94 | outline: none; 95 | flex-grow: 4; 96 | font-size: 16px; 97 | background-color: transparent; 98 | } 99 | 100 | .todo-edit.completed { 101 | font-weight: bolder; 102 | color: darkgray; 103 | text-decoration: line-through; 104 | } 105 | 106 | .clear-todo { 107 | background-color: #eee; 108 | border: none; 109 | display: flex; 110 | align-items: center; 111 | justify-content: center; 112 | padding: 2rem; 113 | outline: none; 114 | cursor: pointer; 115 | font-size: 16px; 116 | } 117 | 118 | .clear-todo:hover { 119 | text-decoration: underline; 120 | } 121 | 122 | form > input { 123 | font-style: italic; 124 | } 125 | 126 | form .press-enter { 127 | transform: rotate(90deg); 128 | } 129 | -------------------------------------------------------------------------------- /.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-22.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-22.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@7.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-22.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-22.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 | nodechecker: 64 | name: node_modules checker 65 | runs-on: ubuntu-22.04 66 | steps: 67 | - uses: actions/checkout@v2 68 | - name: Check node_modules existence 69 | run: | 70 | if [ -d "node_modules/" ]; then echo -e "\e[1;31mThe node_modules/ folder was pushed to the repo. Please remove it from the GitHub repository and try again."; echo -e "\e[1;32mYou can set up a .gitignore file with this folder included on it to prevent this from happening in the future." && exit 1; fi -------------------------------------------------------------------------------- /src/modules/todoCRUD.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @jest-environment jsdom 3 | */ 4 | import todoList from './todoCRUD.js'; 5 | import clearCompleted from './todoStatus.js'; 6 | 7 | document.body.innerHTML = ` 8 | 9 |
Clear All Completed
10 | `; 11 | describe('Test Add function', () => { 12 | test('Add first Item', () => { 13 | todoList.addTodo('test 1'); 14 | const list = document.querySelectorAll('.todo-list li'); 15 | expect(list).toHaveLength(1); 16 | }); 17 | test('Add second Item', () => { 18 | todoList.addTodo('test 2'); 19 | const list = document.querySelectorAll('.todo-list li'); 20 | expect(list).toHaveLength(2); 21 | }); 22 | }); 23 | 24 | describe('Test Remove function', () => { 25 | test('Remove first Item', () => { 26 | todoList.deleteTodo(0); 27 | const list = document.querySelectorAll('.todo-list li'); 28 | expect(list).toHaveLength(1); 29 | }); 30 | test('Remove second Item', () => { 31 | todoList.deleteTodo(0); 32 | const list = document.querySelectorAll('.todo-list li'); 33 | expect(list).toHaveLength(0); 34 | }); 35 | }); 36 | 37 | describe('Test Edit function', () => { 38 | test('Edit first item', () => { 39 | todoList.addTodo('first todo item'); 40 | todoList.updateTodo(0, 'First todo item'); 41 | const firstTodoItem = document.querySelector('.todo-list li:first-child > input'); 42 | expect(firstTodoItem.value).toEqual('First todo item'); 43 | }); 44 | test('Edit last item function', () => { 45 | todoList.addTodo('second todo item'); 46 | todoList.updateTodo(todoList.list.length - 1, 'Second todo item'); 47 | const lastTodoItem = document.querySelector('.todo-list li:last-child > input'); 48 | expect(lastTodoItem.value).toEqual('Second todo item'); 49 | }); 50 | }); 51 | 52 | describe('Test updateTodoStatus function', () => { 53 | test('Check if todo status changes on completion', () => { 54 | todoList.completeTodo(0); 55 | let firstTodoItem = document.querySelector('.todo-list li:first-child'); 56 | expect(firstTodoItem.dataset.completed).toBe('true'); 57 | todoList.completeTodo(0); 58 | firstTodoItem = document.querySelector('.todo-list li:first-child'); 59 | expect(firstTodoItem.dataset.completed).toBe('false'); 60 | }); 61 | }); 62 | 63 | describe('Test clearAllCmpleted function', () => { 64 | test('Clear completed', () => { 65 | todoList.completeTodo(1); 66 | clearCompleted(todoList.list); 67 | todoList.populateList(); 68 | let listItems = document.querySelectorAll('.todo-list li'); 69 | expect(listItems.length).toBe(1); 70 | todoList.completeTodo(0); 71 | clearCompleted(todoList.list); 72 | todoList.populateList(); 73 | listItems = document.querySelectorAll('.todo-list li'); 74 | expect(listItems.length).toBe(0); 75 | }); 76 | }); -------------------------------------------------------------------------------- /src/modules/todoCRUD.js: -------------------------------------------------------------------------------- 1 | import Todo from './todo.js'; 2 | import { storeData, readData } from './localStorage.js'; 3 | 4 | class TodoList { 5 | constructor() { 6 | this.list = []; 7 | } 8 | 9 | addTodo = (data) => { 10 | this.list.push( 11 | new Todo( 12 | this.list.length, 13 | false, 14 | data, 15 | ), 16 | ); 17 | storeData(this.list); 18 | this.populateList(); 19 | }; 20 | 21 | deleteTodo = (index) => { 22 | this.list.splice(index, 1); 23 | this.list.forEach((todo, i) => { 24 | todo.index = i; 25 | }); 26 | storeData(this.list); 27 | this.populateList(); 28 | }; 29 | 30 | updateTodo = (index, value) => { 31 | if (this.list[index]) this.list[index].description = value; 32 | storeData(this.list); 33 | }; 34 | 35 | completeTodo = (index) => { 36 | if (this.list[index]) { this.list[index].completed = !this.list[index].completed; } 37 | storeData(this.list); 38 | this.populateList(); 39 | }; 40 | 41 | getListsHTML = (lists) => lists.map((todo) => { 42 | const li = document.createElement('li'); 43 | li.classList.add('todo-item'); 44 | li.setAttribute('data-index', todo.index); 45 | li.setAttribute('data-completed', todo.completed); 46 | li.setAttribute('draggable', true); 47 | 48 | const completedIconContainer = document.createElement('div'); 49 | completedIconContainer.className = 'todo-completed'; 50 | const incompleteIconContainer = document.createElement('div'); 51 | incompleteIconContainer.className = 'todo-check'; 52 | 53 | const completedIcon = document.createElement('i'); 54 | completedIcon.className = 'fa-regular fa-circle-check fa-2x'; 55 | const incompleteIcon = document.createElement('i'); 56 | incompleteIcon.className = 'fa-regular fa-square fa-2x'; 57 | 58 | completedIconContainer.appendChild(completedIcon); 59 | incompleteIconContainer.appendChild(incompleteIcon); 60 | 61 | const todoDescription = document.createElement('input'); 62 | todoDescription.type = 'text'; 63 | todoDescription.className = 'todo-edit'; 64 | todoDescription.value = todo.description; 65 | if (todo.completed) todoDescription.classList.add('completed'); 66 | 67 | const ellipsisIcon = document.createElement('i'); 68 | ellipsisIcon.className = 'fas fa-ellipsis-v todo-ellipsis fa-2x'; 69 | 70 | const trashIconContainer = document.createElement('div'); 71 | trashIconContainer.className = 'todo-delete hide'; 72 | 73 | todoDescription.addEventListener('input', () => { 74 | this.updateTodo(li.dataset.index, todoDescription.value); 75 | }); 76 | 77 | todoDescription.addEventListener('mousedown', () => { 78 | li.style.backgroundColor = '#f1f8b5'; 79 | trashIconContainer.classList.remove('hide'); 80 | ellipsisIcon.classList.add('hide'); 81 | }); 82 | 83 | li.addEventListener('mouseout', () => { 84 | li.style.backgroundColor = '#fff'; 85 | }); 86 | 87 | trashIconContainer.addEventListener('click', () => { 88 | this.deleteTodo(li.dataset.index); 89 | }); 90 | 91 | completedIconContainer.addEventListener('click', () => { 92 | this.completeTodo(li.dataset.index); 93 | }); 94 | 95 | incompleteIconContainer.addEventListener('click', () => { 96 | this.completeTodo(li.dataset.index); 97 | }); 98 | 99 | const trashIcon = document.createElement('i'); 100 | trashIcon.className = 'fa-regular fa-trash-can fa-2x'; 101 | trashIconContainer.appendChild(trashIcon); 102 | 103 | li.append(todo.completed ? completedIconContainer : incompleteIconContainer); 104 | li.append(todoDescription); 105 | li.append(ellipsisIcon); 106 | li.append(trashIconContainer); 107 | 108 | li.addEventListener('dragstart', (event) => { 109 | event.dataTransfer.dropEffect = 'move'; 110 | event.dataTransfer.setData('text/html', li.innerHTML); 111 | }); 112 | li.addEventListener('dragover', (event) => { 113 | event.dataTransfer.dropEffect = 'move'; 114 | event.preventDefault(); 115 | }); 116 | li.addEventListener('drop', (event) => { 117 | event.preventDefault(); 118 | const data = event.dataTransfer.getData('text/html'); 119 | li.innerHTML = data; 120 | }); 121 | 122 | return li; 123 | }); 124 | 125 | populateList = () => { 126 | const listContainer = document.querySelector('.todo-list'); 127 | listContainer.innerHTML = ''; 128 | listContainer.append(...this.getListsHTML(readData())); 129 | } 130 | } 131 | 132 | const todoList = new TodoList(); 133 | export default todoList; 134 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |

TO DO List

6 | 7 |
8 | 9 | 10 | 11 | # 📗 Table of Contents 12 | 13 | - [📖 About the Project](#about-project) 14 | - [🛠 Built With](#built-with) 15 | - [Tech Stack](#tech-stack) 16 | - [Key Features](#key-features) 17 | - [🚀 Live Demo](#live-demo) 18 | - [💻 Getting Started](#getting-started) 19 | - [Setup](#setup) 20 | - [Prerequisites](#prerequisites) 21 | - [Install](#install) 22 | - [Usage](#usage) 23 | - [Run tests](#run-tests) 24 | - [Deployment](#triangular_flag_on_post-deployment) 25 | - [👥 Authors](#authors) 26 | - [🔭 Future Features](#future-features) 27 | - [🤝 Contributing](#contributing) 28 | - [⭐️ Show your support](#support) 29 | - [🙏 Acknowledgements](#acknowledgements) 30 | - [❓ FAQ](#faq) 31 | - [📝 License](#license) 32 | 33 | 34 | 35 | # 📖 To Do List 36 | 37 | To Do List is a simple and powerful application built with webpack. 38 | 39 | ## 🛠 Built With 40 | webpack 41 | ## Screenshoot 42 | ![screenshot](./Screenshot.png) 43 | ### Tech Stack 44 | 45 |
46 | Structure 47 | 50 |
51 | 52 |
53 | Style 54 | 57 |
58 | 59 |
60 | Linters 61 | 66 |
67 | 68 | 69 | 70 | ### Key Features 71 | 72 | - Set up a simple wabpack boiletplate for other projects. 73 | - HtmlWebpackPlugin 74 | - Webpack server 75 | - Loaders 76 | 77 |

(back to top)

78 | 79 | 80 | 81 | ## 🚀 Live Demo 82 | See the [Live Demo](https://react-todo-zed.netlify.app/) of the project. 83 | 84 |

(back to top)

85 | 86 | 87 | 88 | 89 | ## 💻 Getting Started 90 | 91 | To get a local copy up and running, follow these steps. 92 | 93 | 94 | ### Prerequisites 95 | 96 | In order to run this project you need: 97 | Open terminal on the same folder of the project and run: 98 | ```sh 99 | npm install 100 | ``` 101 | 102 | ### Setup 103 | 104 | Clone this repository to your desired folder: 105 | ```sh 106 | cd my-folder 107 | git clone git@github.com/ZewdieMc/To-Do-List.git 108 | ``` 109 | ### Install 110 | 111 | Install this project with: 112 | ```sh 113 | cd To-Do-List 114 | npm install 115 | ``` 116 | 117 | ### Usage 118 | 119 | To run the project: 120 | - `npm start` 121 | 122 | ### Run tests 123 | 124 | To run tests, run the following command: 125 | 126 | To check for html errors run: 127 | ```sh 128 | npx hint . 129 | ``` 130 | To check for css errors run: 131 | ```sh 132 | npx stylelint "**/*.{css,scss}" 133 | ``` 134 | To check for js errors run: 135 | ```sh 136 | npx eslint . 137 | ``` 138 | 139 | ### Deployment 140 | 141 | Deployed using Github Pages. 142 | 143 |

(back to top)

144 | 145 | 146 | 147 | ## 👥 Authors 148 | 149 | 👤 Zewdie Habtie 150 | 151 | - GitHub: [@ZewdieMc](https://github.com/ZewdieMc) 152 | - Twitter: [@HabtieZewdie](https://twitter.com/HabtieZewdie) 153 | - LinkedIn: [Zewdie Habtie](https://linkedin.com/in/zewdie-habtie-sisay-947153172) 154 |

(back to top)

155 | 156 | 157 | 158 | ## 🔭 Future Features 159 | 160 | - Implement `Drag` and `Drop` events to the todo items. 161 | 162 | 163 |

(back to top)

164 | 165 | 166 | 167 | ## 🤝 Contributing 168 | 169 | Contributions, issues, and feature requests are welcome! 170 | 171 | Feel free to check the [issues page](https://github.com/ZewdieMc/To-Do-List/issues). 172 | 173 |

(back to top)

174 | 175 | 176 | 177 | ## ⭐️ Show your support 178 | 179 | If you like this project send your feedback to encourage me to do more. 180 | 181 |

(back to top)

182 | 183 | 184 | 185 | ## 🙏 Acknowledgments 186 | 187 | I would like to thank Microverse for offering me this opportunity to learn, and practice my skills. 188 | 189 |

(back to top)

190 | 191 | 192 | 193 | ## ❓ FAQ 194 | 195 | -Why use linters? 196 | 197 | - The use of linters helps to diagnose and fix technical issues, also linters can help teams achieve a more readable and consistent style, through the enforcement of its rules. 198 | 199 |

(back to top)

200 | 201 | 202 | 203 | ## 📝 License 204 | 205 | This project is [MIT](./LICENSE) licensed. 206 | (Check the LICENSE file) 207 | 208 |

(back to top)

209 | -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";var e={913:(e,t,o)=>{o.d(t,{Z:()=>s});var n=o(81),r=o.n(n),a=o(645),i=o.n(a)()(r());i.push([e.id,'*{padding:0;margin:0;box-sizing:border-box}body{font-family:"Poppins",sans-serif;background-color:#eee;display:flex;align-items:center;justify-content:center;min-height:100vh}.todo-container{position:relative;display:flex;flex-direction:column;justify-content:center;box-shadow:0 3px 10px rgba(0,0,0,.2);background-color:#fff}form>.press-enter,.todo-header .refresh{color:#b9abab}.todo-header,form{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #eee}.todo-header{padding:1.5rem 1rem}.todo-list{width:100%;list-style:none;padding:0}.todo-list .todo-item{display:flex;justify-content:space-between;align-items:center;padding:.5rem 1rem;border-bottom:1px solid #eee;gap:1.5rem}.todo-item .hide{display:none}.todo-item .todo-completed,.todo-item .todo-check,.todo-item .todo-delete{cursor:pointer;color:#ccc}.todo-item .todo-check:hover,.todo-item .todo-completed:hover{color:green}.todo-item .line-through{text-decoration:line-through}.todo-item .todo-delete:hover{color:red}.todo-list li .todo-ellipsis{cursor:move;color:#ccc}.todo-list li .todo-ellipsis:hover{color:#000}input{border:none;padding:10px;outline:none;flex-grow:4;font-size:16px;background-color:rgba(0,0,0,0)}.todo-edit.completed{font-weight:bolder;color:#a9a9a9;text-decoration:line-through}.clear-todo{background-color:#eee;border:none;display:flex;align-items:center;justify-content:center;padding:2rem;outline:none;cursor:pointer;font-size:16px}.clear-todo:hover{text-decoration:underline}form>input{font-style:italic}form .press-enter{transform:rotate(90deg)}',""]);const s=i},645:e=>{e.exports=function(e){var t=[];return t.toString=function(){return this.map((function(t){var o="",n=void 0!==t[5];return t[4]&&(o+="@supports (".concat(t[4],") {")),t[2]&&(o+="@media ".concat(t[2]," {")),n&&(o+="@layer".concat(t[5].length>0?" ".concat(t[5]):""," {")),o+=e(t),n&&(o+="}"),t[2]&&(o+="}"),t[4]&&(o+="}"),o})).join("")},t.i=function(e,o,n,r,a){"string"==typeof e&&(e=[[null,e,void 0]]);var i={};if(n)for(var s=0;s0?" ".concat(l[5]):""," {").concat(l[1],"}")),l[5]=a),o&&(l[2]?(l[1]="@media ".concat(l[2]," {").concat(l[1],"}"),l[2]=o):l[2]=o),r&&(l[4]?(l[1]="@supports (".concat(l[4],") {").concat(l[1],"}"),l[4]=r):l[4]="".concat(r)),t.push(l))}},t}},81:e=>{e.exports=function(e){return e[1]}},379:e=>{var t=[];function o(e){for(var o=-1,n=0;n{var t={};e.exports=function(e,o){var n=function(e){if(void 0===t[e]){var o=document.querySelector(e);if(window.HTMLIFrameElement&&o instanceof window.HTMLIFrameElement)try{o=o.contentDocument.head}catch(e){o=null}t[e]=o}return t[e]}(e);if(!n)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");n.appendChild(o)}},216:e=>{e.exports=function(e){var t=document.createElement("style");return e.setAttributes(t,e.attributes),e.insert(t,e.options),t}},565:(e,t,o)=>{e.exports=function(e){var t=o.nc;t&&e.setAttribute("nonce",t)}},795:e=>{e.exports=function(e){var t=e.insertStyleElement(e);return{update:function(o){!function(e,t,o){var n="";o.supports&&(n+="@supports (".concat(o.supports,") {")),o.media&&(n+="@media ".concat(o.media," {"));var r=void 0!==o.layer;r&&(n+="@layer".concat(o.layer.length>0?" ".concat(o.layer):""," {")),n+=o.css,r&&(n+="}"),o.media&&(n+="}"),o.supports&&(n+="}");var a=o.sourceMap;a&&"undefined"!=typeof btoa&&(n+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(a))))," */")),t.styleTagTransform(n,e,t.options)}(t,e,o)},remove:function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(t)}}}},589:e=>{e.exports=function(e,t){if(t.styleSheet)t.styleSheet.cssText=e;else{for(;t.firstChild;)t.removeChild(t.firstChild);t.appendChild(document.createTextNode(e))}}}},t={};function o(n){var r=t[n];if(void 0!==r)return r.exports;var a=t[n]={id:n,exports:{}};return e[n](a,a.exports,o),a.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},o.d=(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.nc=void 0,(()=>{var e=o(379),t=o.n(e),n=o(795),r=o.n(n),a=o(569),i=o.n(a),s=o(565),d=o.n(s),c=o(216),l=o.n(c),p=o(589),u=o.n(p),m=o(913),f={};f.styleTagTransform=u(),f.setAttributes=d(),f.insert=i().bind(null,"head"),f.domAPI=r(),f.insertStyleElement=l(),t()(m.Z,f),m.Z&&m.Z.locals&&m.Z.locals;const h=e=>{localStorage.setItem("todoObjects",JSON.stringify(e))},v=()=>JSON.parse(localStorage.getItem("todoObjects"))||[];class g{constructor(e,t=!1,o){this.completed=t,this.index=e,this.description=o}}const y=new class{constructor(){this.list=[]}addTodo=e=>{this.list.push(new g(this.list.length,!1,e)),h(this.list),this.populateList()};deleteTodo=e=>{this.list.splice(e,1),this.list.forEach(((e,t)=>{e.index=t})),h(this.list),this.populateList()};updateTodo=(e,t)=>{this.list[e]&&(this.list[e].description=t),h(this.list)};completeTodo=e=>{this.list[e]&&(this.list[e].completed=!this.list[e].completed),h(this.list),this.populateList()};getListsHTML=e=>e.map((e=>{const t=document.createElement("li");t.classList.add("todo-item"),t.setAttribute("data-index",e.index),t.setAttribute("data-completed",e.completed),t.setAttribute("draggable",!0);const o=document.createElement("div");o.className="todo-completed";const n=document.createElement("div");n.className="todo-check";const r=document.createElement("i");r.className="fa-regular fa-circle-check fa-2x";const a=document.createElement("i");a.className="fa-regular fa-square fa-2x",o.appendChild(r),n.appendChild(a);const i=document.createElement("input");i.type="text",i.className="todo-edit",i.value=e.description,i.classList.add(e.completed?"completed":"incomplete");const s=document.createElement("i");s.className="fas fa-ellipsis-v todo-ellipsis fa-2x";const d=document.createElement("div");d.className="todo-delete hide",i.addEventListener("input",(()=>{this.updateTodo(t.dataset.index,i.value)})),i.addEventListener("mousedown",(()=>{t.style.backgroundColor="#f1f8b5",d.classList.remove("hide"),s.classList.add("hide")})),t.addEventListener("mouseout",(()=>{t.style.backgroundColor="#fff"})),d.addEventListener("click",(()=>{this.deleteTodo(t.dataset.index)})),o.addEventListener("click",(()=>{this.completeTodo(t.dataset.index)})),n.addEventListener("click",(()=>{this.completeTodo(t.dataset.index)}));const c=document.createElement("i");return c.className="fa-regular fa-trash-can fa-2x",d.appendChild(c),t.append(e.completed?o:n),t.append(i),t.append(s),t.append(d),t.addEventListener("dragstart",(e=>{e.dataTransfer.dropEffect="move",e.dataTransfer.setData("text/html",t.innerHTML)})),t.addEventListener("dragover",(e=>{e.dataTransfer.dropEffect="move",e.preventDefault()})),t.addEventListener("drop",(e=>{e.preventDefault();const o=e.dataTransfer.getData("text/html");t.innerHTML=o})),t}));populateList=()=>{const e=document.querySelector(".todo-list");e.innerHTML="",e.append(...this.getListsHTML(v()))}};window.onload=()=>{const e=document.querySelector("form"),t=document.querySelector("form .input-todo");y.list=v(),document.querySelector(".clear-todo").addEventListener("click",(()=>{(e=>{if(Array.isArray(e.list)&&e.list.length){const t=e.list.filter((e=>!e.completed));t.forEach(((e,t)=>{e.index=t})),h(t)}})(y),y.list=v(),y.populateList()})),y.list.length&&y.populateList(),e.addEventListener("submit",(e=>{e.preventDefault()})),t.addEventListener("keyup",(e=>{13===e.keyCode&&(e.preventDefault(),t.value&&y.addTodo(t.value),t.value="")}))}})()})(); --------------------------------------------------------------------------------