├── 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
--------------------------------------------------------------------------------
/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 |
18 |
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 | 
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="")}))}})()})();
--------------------------------------------------------------------------------