├── .gitignore ├── public └── assets │ └── img │ ├── return_icon.png │ └── icons.svg ├── .babelrc ├── jest.config.js ├── .hintrc ├── src ├── modules │ ├── localStorage.js │ ├── utils.js │ └── Tasks.js ├── index.html ├── index.css └── index.js ├── .eslintrc.json ├── .stylelintrc.json ├── webpack.config.js ├── LICENSE ├── dist ├── index.html └── index.bundle.js ├── package.json ├── README.md ├── tests ├── add-remove.test.js └── edit-update-clearAll.test.js └── .github └── workflows └── linters.yml /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | coverage/ 3 | -------------------------------------------------------------------------------- /public/assets/img/return_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BucurEva87/to-do-list/HEAD/public/assets/img/return_icon.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "plugins": ["@babel/plugin-transform-modules-commonjs"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { defaults } = require('jest-config'); 2 | 3 | module.exports = { 4 | ...defaults, 5 | testEnvironment: 'jsdom', 6 | }; 7 | -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /src/modules/localStorage.js: -------------------------------------------------------------------------------- 1 | export default class LocalStorage { 2 | static tasks = []; 3 | 4 | static init() { 5 | const tasks = JSON.parse(localStorage.getItem('tasks')); 6 | 7 | if (tasks) { 8 | tasks.forEach((task) => { 9 | this.tasks.push(task); 10 | }); 11 | } 12 | } 13 | 14 | static read() { 15 | return this.tasks; 16 | } 17 | 18 | static store(tasks) { 19 | localStorage.setItem('tasks', JSON.stringify(tasks)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.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 | } 26 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["stylelint-config-standard"], 3 | "plugins": ["stylelint-scss", "stylelint-csstree-validator"], 4 | "rules": { 5 | "at-rule-no-unknown": [ 6 | true, 7 | { 8 | "ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen"] 9 | } 10 | ], 11 | "scss/at-rule-no-unknown": [ 12 | true, 13 | { 14 | "ignoreAtRules": ["tailwind", "apply", "variants", "responsive", "screen"] 15 | } 16 | ], 17 | "csstree/validator": true 18 | }, 19 | "ignoreFiles": ["build/**", "dist/**", "**/reset*.css", "**/bootstrap*.css", "**/*.js", "**/*.jsx"] 20 | } 21 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | entry: { 6 | index: './src/index.js', 7 | }, 8 | plugins: [ 9 | new HtmlWebpackPlugin({ 10 | title: 'A minimal TODO list', 11 | template: path.resolve(__dirname, 'src', 'index.html'), 12 | }), 13 | ], 14 | output: { 15 | filename: '[name].bundle.js', 16 | path: path.resolve(__dirname, 'dist'), 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.css$/i, 22 | use: ['style-loader', 'css-loader'], 23 | }, 24 | ], 25 | }, 26 | mode: 'development', 27 | devServer: { 28 | static: { 29 | directory: path.join(__dirname, 'public'), 30 | }, 31 | port: 9000, 32 | open: true, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Eva Lavinia Bucur 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 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | A minimalist TODO list 8 | 9 | 10 |
11 | 17 |
18 | 19 |
20 | 38 | 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | A minimalist TODO list 8 | 9 | 10 |
11 | 17 |
18 | 19 |
20 | 38 | 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@babel/plugin-transform-modules-commonjs": "^7.18.6", 4 | "css-loader": "^6.7.1", 5 | "html-webpack-plugin": "^5.5.0", 6 | "jest": "^29.0.0", 7 | "jest-config": "^29.0.1", 8 | "jest-environment-jsdom": "^29.0.0", 9 | "style-loader": "^3.3.1", 10 | "webpack": "^5.74.0", 11 | "webpack-cli": "^4.10.0", 12 | "webpack-dev-server": "^4.10.0" 13 | }, 14 | "name": "to-do-list", 15 | "description": "![](https://img.shields.io/badge/Microverse-blueviolet)", 16 | "version": "1.0.0", 17 | "main": "index.js", 18 | "scripts": { 19 | "build": "webpack", 20 | "dev": "webpack serve", 21 | "test": "jest --coverage", 22 | "watch": "jest --watch" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/BucurEva87/to-do-list.git" 27 | }, 28 | "keywords": [], 29 | "author": "", 30 | "license": "ISC", 31 | "bugs": { 32 | "url": "https://github.com/BucurEva87/to-do-list/issues" 33 | }, 34 | "homepage": "https://github.com/BucurEva87/to-do-list#readme", 35 | "devDependencies": { 36 | "babel-eslint": "^10.1.0", 37 | "eslint": "^7.32.0", 38 | "eslint-config-airbnb-base": "^14.2.1", 39 | "eslint-plugin-import": "^2.26.0", 40 | "stylelint": "^13.13.1", 41 | "stylelint-config-standard": "^21.0.0", 42 | "stylelint-csstree-validator": "^1.9.0", 43 | "stylelint-scss": "^3.21.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://img.shields.io/badge/Microverse-blueviolet) 2 | 3 | # Minimalist ToDo List 4 | 5 | > A minimalist list of TODOs. 6 | 7 | ## Built With 8 | 9 | - HTML, CSS, JavaScript 10 | - Webpack 11 | - Jest 12 | - NPM 13 | - Node 14 | 15 | ## Getting Started 16 | 17 | In order to obtain a local copy of this repository run: 18 | 19 | - `git clone https://github.com/BucurEva87/to-do-list.git` 20 | - `cd to-do-list` 21 | 22 | ### Prerequisites 23 | 24 | - Install Node and Node Package Manager (NPM) 25 | - Install all the project dependencies running `npm i` 26 | 27 | ### Running tests 28 | 29 | - You can run test suites on this project by running `npm test` 30 | 31 | ## Live Demo 32 | 33 | [Live Demo Link](https://bucureva87.github.io/to-do-list/dist/) 34 | 35 | ## Live Preview 36 | 37 | ![Live Preview](https://i.postimg.cc/PqMCTCfp/Screenshot-from-2022-08-18-15-30-16.png) 38 | 39 | ## Authors 40 | 41 | 👤 **Bucur Liviu-Emanuel (Eva-Lavinia)** 42 | 43 | - GitHub: [@BucurEva87](https://github.com/BucurEva87) 44 | - Twitter: [@BucurEva](https://twitter.com/BucurEva) 45 | - LinkedIn: [Eva-Lavinia Bucur](https://www.linkedin.com/in/eva-lavinia-bucur) 46 | 47 | ## 🤝 Contributing 48 | 49 | Contributions, issues, and feature requests are welcome! 50 | 51 | Feel free to check the [issues page](../../issues/). 52 | 53 | ## Show your support 54 | 55 | Give a ⭐️ if you like this project! 56 | 57 | ## 📝 License 58 | 59 | This project is [MIT](./LICENSE) licensed. 60 | -------------------------------------------------------------------------------- /src/modules/utils.js: -------------------------------------------------------------------------------- 1 | export default { 2 | createElement: (obj) => { 3 | let element; 4 | 5 | if (obj.tagName && ['svg', 'use'].includes(obj.tagName.toLowerCase())) { 6 | element = document.createElementNS('http://www.w3.org/2000/svg', obj.tagName); 7 | if (obj.tagName.toLowerCase() === 'use' && obj.src) { 8 | element.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', obj.src); 9 | } 10 | } else { 11 | element = document.createElement(obj.tagName ? obj.tagName : 'div'); 12 | } 13 | 14 | delete obj.tagName; 15 | 16 | // Itterate through each property of the obj and set it as a 17 | // property of the element 18 | Object.entries(obj).forEach(([prop, value]) => { 19 | // Property is a class or a collection of classes 20 | if (prop === 'class') { 21 | if (typeof value === 'object' && value !== null) { 22 | element.classList.add(...value.filter((v) => v)); 23 | } else if (value) { 24 | element.classList.add(value); 25 | } 26 | } else if (prop === 'data' && typeof value === 'object') { 27 | Object.entries(value).forEach(([prop, value]) => { 28 | element.dataset[prop] = value; 29 | }); 30 | } else if (typeof value === 'boolean') { 31 | element.setAttribute(prop, value); 32 | } else { 33 | element[prop] = value; 34 | } 35 | }); 36 | 37 | return element; 38 | }, 39 | 40 | qs: (selector = '*', element = document) => element.querySelector(selector), 41 | 42 | qsa: (selector = '*', element = document) => [...element.querySelectorAll(selector)], 43 | }; 44 | -------------------------------------------------------------------------------- /tests/add-remove.test.js: -------------------------------------------------------------------------------- 1 | import Tasks from '../src/modules/Tasks.js'; 2 | import utils from '../src/modules/utils.js'; 3 | 4 | /* eslint-disable no-unused-vars */ 5 | const { default: JSDOMEnvironment } = require('jest-environment-jsdom'); 6 | /* eslint-enable no-unused-vars */ 7 | /** 8 | * @jest-environment jsdom 9 | */ 10 | 11 | document.body.innerHTML = `
12 | 30 | 31 |
`; 32 | 33 | describe('Tasks class', () => { 34 | const tasks = new Tasks(); 35 | 36 | test('addTask method adds a task to the tasks property of the Tasks class', () => { 37 | tasks.addTask({ 38 | description: 'Dummy task', 39 | completed: false, 40 | index: 5, 41 | }); 42 | 43 | expect(tasks.tasks).toHaveLength(1); 44 | }); 45 | 46 | test('addTask method adds a task to the DOM list element', () => { 47 | expect(utils.qsa('ul li.task')).toHaveLength(1); 48 | }); 49 | 50 | test('addTask method adds a task to the LocalStorage', () => { 51 | expect(JSON.parse(localStorage.getItem('tasks'))).toHaveLength(1); 52 | }); 53 | 54 | test('remove method removes a task when being given an index', () => { 55 | tasks.remove(1); 56 | 57 | expect(tasks.tasks).toHaveLength(0); 58 | }); 59 | 60 | test('remove method removes a task from the LocalStorage', () => { 61 | expect(JSON.parse(localStorage.getItem('tasks'))).toHaveLength(0); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /.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@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-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 | nodechecker: 64 | name: node_modules checker 65 | runs-on: ubuntu-18.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 71 | -------------------------------------------------------------------------------- /src/modules/Tasks.js: -------------------------------------------------------------------------------- 1 | import utils from './utils.js'; 2 | import LocalStorage from './localStorage.js'; 3 | 4 | export default class Tasks { 5 | tasks = []; 6 | 7 | constructor() { 8 | LocalStorage.init(); 9 | const tasks = LocalStorage.read(); 10 | tasks.sort((a, b) => a.index - b.index).forEach((t) => this.addTask(t)); 11 | this.tasks = tasks; 12 | } 13 | 14 | addTask(task) { 15 | this.tasks.push(task); 16 | 17 | const li = utils.createElement({ 18 | tagName: 'li', 19 | class: 'task', 20 | data: { tabIndex: task.index }, 21 | }); 22 | const button = utils.createElement({ 23 | tagName: 'button', 24 | class: task.completed ? 'pressed' : null, 25 | }); 26 | let svg = utils.createElement({ 27 | tagName: 'svg', 28 | class: 'checkbox', 29 | }); 30 | svg.appendChild( 31 | utils.createElement({ 32 | tagName: 'use', 33 | src: '../public/assets/img/icons.svg#icon-check', 34 | }), 35 | ); 36 | button.appendChild(svg); 37 | li.appendChild(button); 38 | li.appendChild( 39 | utils.createElement({ 40 | tagName: 'p', 41 | contenteditable: true, 42 | textContent: task.description, 43 | }), 44 | ); 45 | let a = utils.createElement({ 46 | tagName: 'a', 47 | href: '#', 48 | class: ['trash-icon', 'hidden'], 49 | }); 50 | svg = utils.createElement({ 51 | tagName: 'svg', 52 | class: 'checkbox', 53 | }); 54 | svg.appendChild( 55 | utils.createElement({ 56 | tagName: 'use', 57 | src: '../public/assets/img/icons.svg#icon-trash', 58 | }), 59 | ); 60 | a.appendChild(svg); 61 | li.appendChild(a); 62 | a = utils.createElement({ 63 | tagName: 'a', 64 | href: '#', 65 | }); 66 | svg = utils.createElement({ 67 | tagName: 'svg', 68 | class: ['checkbox', 'drag-anchor'], 69 | }); 70 | svg.appendChild( 71 | utils.createElement({ 72 | tagName: 'use', 73 | src: '../public/assets/img/icons.svg#icon-more-vert', 74 | }), 75 | ); 76 | a.appendChild(svg); 77 | li.appendChild(a); 78 | utils.qs('.listContainer ul').appendChild(li); 79 | 80 | utils.qs('#refresh').dataset.items = utils.qsa('.task').length; 81 | 82 | LocalStorage.store(this.tasks); 83 | } 84 | 85 | remove(index) { 86 | const taskIndex = this.tasks.findIndex((t) => t.index === index); 87 | 88 | this.tasks.splice(taskIndex, 1); 89 | 90 | const badge = utils.qs('#refresh'); 91 | const tasks = utils.qsa('.task').length; 92 | 93 | if (tasks) { 94 | badge.dataset.items = utils.qsa('.task').length; 95 | } else { 96 | delete badge.dataset.items; 97 | } 98 | 99 | this.tasks.forEach((task, index) => { 100 | task.index = index + 1; 101 | }); 102 | 103 | LocalStorage.store(this.tasks); 104 | } 105 | 106 | update(index, property, newValue) { 107 | const task = this.tasks.find((t) => t.index === index); 108 | 109 | if (property === 'completed') { 110 | task.completed = !task.completed; 111 | } else { 112 | task[property] = newValue; 113 | } 114 | 115 | LocalStorage.store(this.tasks); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/edit-update-clearAll.test.js: -------------------------------------------------------------------------------- 1 | import Tasks from '../src/modules/Tasks.js'; 2 | import utils from '../src/modules/utils.js'; 3 | 4 | /* eslint-disable no-unused-vars */ 5 | const { default: JSDOMEnvironment } = require('jest-environment-jsdom'); 6 | /* eslint-enable no-unused-vars */ 7 | /** 8 | * @jest-environment jsdom 9 | */ 10 | 11 | document.body.innerHTML = `
12 | 30 | 31 |
`; 32 | 33 | describe('Tasks class', () => { 34 | const tasks = new Tasks(); 35 | [ 36 | { description: 'Dummy task 1', completed: false, index: 1 }, 37 | { description: 'Dummy task 2', completed: false, index: 2 }, 38 | { description: 'Dummy task 3', completed: false, index: 3 }, 39 | { description: 'Dummy task 4', completed: false, index: 4 }, 40 | ].forEach((t) => tasks.addTask(t)); 41 | 42 | test('update method correctly updates the completed property in the Tasks array', () => { 43 | tasks.update(2, 'completed'); 44 | 45 | expect(tasks.tasks[1].completed).toBe(true); 46 | }); 47 | test('update method correctly updates the completed property in the LocalStorage', () => { 48 | tasks.update(4, 'completed'); 49 | 50 | expect(JSON.parse(localStorage.getItem('tasks')).find((t) => t.index === 4).completed).toBe( 51 | true, 52 | ); 53 | }); 54 | 55 | test('update method correctly updates the description property in the Tasks array', () => { 56 | tasks.update(3, 'description', "This is not a dummy task any more. It's special!"); 57 | 58 | expect(tasks.tasks[2].description).toBe("This is not a dummy task any more. It's special!"); 59 | }); 60 | test('update method correctly updates the description property in the LocalStorage', () => { 61 | tasks.update(1, 'description', 'This is a special task now too'); 62 | 63 | expect(JSON.parse(localStorage.getItem('tasks')).find((t) => t.index === 1).description).toBe( 64 | 'This is a special task now too', 65 | ); 66 | }); 67 | 68 | test('when the clearAll button triggers the click event all the tasks are removed from the Tasks array', () => { 69 | utils 70 | .qsa('li.task') 71 | .reverse() 72 | .forEach((t) => { 73 | const index = +t.dataset.tabIndex; 74 | const task = tasks.tasks.find((item) => item.index === index); 75 | 76 | if (task.completed) { 77 | tasks.remove(index); 78 | t.remove(); 79 | } 80 | }); 81 | 82 | expect(tasks.tasks).toHaveLength(2); 83 | }); 84 | 85 | test('when the clearAll button triggers the click event all the tasks are removed from the DOM element', () => { 86 | expect(utils.qsa('ul li.task')).toHaveLength(tasks.tasks.length); 87 | }); 88 | 89 | test('when the clearAll button triggers the click event all the tasks are removed from the LocalStorage', () => { 90 | expect(JSON.parse(localStorage.getItem('tasks'))).toHaveLength(tasks.tasks.length); 91 | }); 92 | }); 93 | -------------------------------------------------------------------------------- /public/assets/img/icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: border-box; 7 | } 8 | 9 | :root { 10 | --blue: #2e8ae6; 11 | --gray: #222; 12 | } 13 | 14 | html { 15 | font-size: 10px; 16 | } 17 | 18 | body { 19 | width: 52rem; 20 | max-width: 76.8rem; 21 | margin: 0 auto; 22 | min-height: 100vh; 23 | display: flex; 24 | flex-direction: column; 25 | font-family: 'Open Sans', 'Lucida Grande', tahoma, verdana, arial, sans-serif; 26 | } 27 | 28 | header { 29 | display: flex; 30 | flex-direction: column; 31 | justify-content: center; 32 | align-items: center; 33 | } 34 | 35 | .listContainer .task svg { 36 | width: 2rem; 37 | height: 2rem; 38 | fill: #ccc; 39 | } 40 | 41 | .listContainer .task button svg { 42 | margin: -0.2rem; 43 | width: 1rem; 44 | height: 1rem; 45 | fill: transparent; 46 | } 47 | 48 | .listContainer li a:hover svg { 49 | fill: var(--gray); 50 | } 51 | 52 | .listContainer .task button.pressed svg { 53 | fill: var(--blue); 54 | } 55 | 56 | header #logo { 57 | padding: 1rem; 58 | text-align: center; 59 | margin-bottom: 4rem; 60 | } 61 | 62 | #refresh svg { 63 | width: 2rem; 64 | height: 2rem; 65 | fill: #ccc; 66 | } 67 | 68 | header #logo svg { 69 | width: 5rem; 70 | height: 5rem; 71 | margin: 0 auto; 72 | fill: white; 73 | background-color: var(--blue); 74 | padding: 1rem; 75 | border-radius: 0.5rem; 76 | } 77 | 78 | header #logo h1 { 79 | font-size: 3.6rem; 80 | color: var(--gray); 81 | } 82 | 83 | .listContainer { 84 | display: flex; 85 | flex-direction: column; 86 | width: 100%; 87 | } 88 | 89 | .listContainer ul { 90 | list-style-type: none; 91 | width: 100%; 92 | font-size: 1.8rem; 93 | } 94 | 95 | #refresh { 96 | top: 1.7rem; 97 | } 98 | 99 | .listContainer li a:last-child { 100 | position: absolute; 101 | right: 1rem; 102 | cursor: pointer; 103 | } 104 | 105 | #addNewTask a { 106 | width: 2rem; 107 | height: 2rem; 108 | fill: #ccc; 109 | text-align: center; 110 | margin-top: 0.5rem; 111 | } 112 | 113 | .listContainer p[contenteditable] { 114 | outline: 0 solid transparent; 115 | } 116 | 117 | #addNewTask p[contenteditable] { 118 | min-width: 1rem; 119 | } 120 | 121 | #refresh[data-items]::after { 122 | content: attr(data-items); 123 | position: absolute; 124 | top: -30%; 125 | left: 40%; 126 | width: 2.2rem; 127 | height: 2.2rem; 128 | background-color: red; 129 | border-radius: 50%; 130 | color: white; 131 | border: 0.2rem solid white; 132 | display: flex; 133 | justify-content: center; 134 | align-items: center; 135 | font-size: 1.2rem; 136 | font-weight: bold; 137 | } 138 | 139 | .listContainer li { 140 | display: flex; 141 | padding: 3rem; 142 | border: 0.1rem solid #ddd; 143 | align-items: center; 144 | position: relative; 145 | height: 5rem; 146 | } 147 | 148 | .listContainer li a.trash-icon { 149 | position: absolute; 150 | right: 1rem; 151 | cursor: pointer; 152 | } 153 | 154 | .listContainer .task.focused { 155 | background-color: #e6e5b1; 156 | } 157 | 158 | .listContainer .task button { 159 | margin-right: 1rem; 160 | background-color: transparent; 161 | outline: none; 162 | padding: 0; 163 | border: 0.2rem solid #c1c1c3; 164 | color: transparent; 165 | width: 1.6rem; 166 | height: 1.6rem; 167 | border-radius: 0.2rem; 168 | cursor: pointer; 169 | } 170 | 171 | .listContainer .task button.pressed { 172 | border: transparent; 173 | } 174 | 175 | .listContainer .task button.pressed + p:not(:focus) { 176 | text-decoration: line-through; 177 | font-style: italic; 178 | color: var(--gray); 179 | } 180 | 181 | .listContainer .task svg.drag-anchor { 182 | cursor: move; 183 | } 184 | 185 | .listContainer p[contenteditable]:empty:not(:focus)::before { 186 | content: attr(data-placeholder); 187 | font-style: italic; 188 | font-weight: 300; 189 | } 190 | 191 | #refresh:hover svg { 192 | fill: var(--gray); 193 | } 194 | 195 | #footer { 196 | height: 5rem; 197 | border: none; 198 | color: #888; 199 | background-color: #eee; 200 | cursor: pointer; 201 | } 202 | 203 | #footer:hover { 204 | text-decoration: underline; 205 | color: var(--gray); 206 | } 207 | 208 | #refresh.focus { 209 | animation: swirl 2s; 210 | } 211 | 212 | @keyframes swirl { 213 | 0% { 214 | transform: rotate(0deg); 215 | } 216 | 217 | 100% { 218 | transform: rotate(-720deg); 219 | } 220 | } 221 | 222 | .hidden { 223 | display: none; 224 | } 225 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import utils from './modules/utils.js'; 2 | import Tasks from './modules/Tasks.js'; 3 | import './index.css'; 4 | 5 | const tasks = new Tasks(); 6 | 7 | const list = utils.qs('.listContainer ul'); 8 | const addTask = utils.qs('#addNewTask'); 9 | const addTaskIcon = utils.qs('#addNewTask object'); 10 | const deleteButton = utils.qs('#footer'); 11 | 12 | // Completing tasks 13 | list.addEventListener('click', (e) => { 14 | const button = e.target.closest('button'); 15 | 16 | if (!button) return; 17 | 18 | button.classList.toggle('pressed'); 19 | 20 | const index = +button.parentElement.dataset.tabIndex; 21 | tasks.update(index, 'completed'); 22 | }); 23 | 24 | // Adding a new task 25 | const createNewTask = (target) => { 26 | tasks.addTask({ 27 | description: target.textContent, 28 | completed: false, 29 | index: tasks.tasks.length, 30 | }); 31 | target.textContent = ''; 32 | target.blur(); 33 | }; 34 | 35 | addTask.addEventListener('keyup', (e) => { 36 | const keyCode = e.which || e.keyCode; 37 | 38 | if (keyCode === 13) { 39 | createNewTask(e.target); 40 | } 41 | }); 42 | addTaskIcon.addEventListener('click', (e) => { 43 | e.preventDefault(); 44 | 45 | const p = e.target.parentElement.previousElementSibling; 46 | 47 | if (!p.textContent) return; 48 | 49 | createNewTask(p); 50 | }); 51 | 52 | // Show/hide trash icon when a task is focused 53 | list.addEventListener('click', (e) => { 54 | const { target } = e; 55 | 56 | if (target.tagName !== 'P' || !target.closest('li.task')) return; 57 | 58 | utils.qs('.trash-icon', target.parentElement).classList.remove('hidden'); 59 | utils.qs('a:last-child', target.parentElement).classList.add('hidden'); 60 | target.closest('li.task').classList.add('focused'); 61 | }); 62 | utils.qsa('li.task p').forEach((p) => { 63 | p.addEventListener('blur', (e) => { 64 | e.preventDefault(); 65 | 66 | const { target } = e; 67 | 68 | if (target.tagName !== 'P' || !target.closest('li.task')) return; 69 | 70 | target.closest('li.task').classList.remove('focused'); 71 | setTimeout(() => { 72 | utils.qs('.trash-icon', target.parentElement).classList.add('hidden'); 73 | utils.qs('a:last-child', target.parentElement).classList.remove('hidden'); 74 | }, 100); 75 | }); 76 | }); 77 | 78 | // Delete a single task 79 | list.addEventListener('click', (e) => { 80 | e.preventDefault(); 81 | 82 | const { target } = e; 83 | 84 | if (!target.closest('a')?.classList.contains('trash-icon')) return; 85 | 86 | const li = target.closest('li.task'); 87 | 88 | li.remove(); 89 | tasks.remove(+li.dataset.tabIndex); 90 | }); 91 | 92 | // Delete all selected tasks 93 | deleteButton.addEventListener('click', () => { 94 | utils.qsa('button.pressed').forEach((b) => b.closest('li.task').remove()); 95 | 96 | tasks.tasks.filter((t) => t.completed).forEach((t) => tasks.remove(t.index)); 97 | }); 98 | 99 | // Apperently we DO have a problem with WebKit browsers when it comes to contenteditable elements 100 | // receiving focus when the mouse is clicked on the same line the element is, but not on the element 101 | // itself 102 | list.addEventListener('click', (e) => { 103 | const { target } = e; 104 | 105 | if (target.tagName !== 'P' || !target.closest('li.task') || !target.contentEditable) { 106 | return; 107 | } 108 | 109 | target.contentEditable = true; 110 | target.focus(); 111 | }); 112 | list.addEventListener( 113 | 'blur', 114 | (e) => { 115 | const { target } = e; 116 | 117 | if (target.tagName !== 'P' || !target.closest('li.task') || !target.contentEditable) { 118 | return; 119 | } 120 | 121 | target.contentEditable = false; 122 | }, 123 | true, 124 | ); 125 | 126 | // Rename a task 127 | list.addEventListener('keypress', (e) => { 128 | const keyCode = e.which || e.keyCode; 129 | const { target } = e; 130 | const li = target.closest('li.task'); 131 | 132 | if (!target.contentEditable || !li) return; 133 | 134 | if (keyCode === 13) { 135 | const index = +li.dataset.tabIndex; 136 | tasks.update(index, 'description', target.textContent); 137 | li.classList.remove('focused'); 138 | target.blur(); 139 | } 140 | }); 141 | 142 | // Delete all tasks when clicking the refresh arrows 143 | utils.qs('#refresh').addEventListener('click', (e) => { 144 | e.target.closest('a').classList.add('focus'); 145 | }); 146 | 147 | utils.qs('#refresh').addEventListener('animationend', (e) => { 148 | e.target.closest('a').classList.remove('focus'); 149 | 150 | utils.qsa('li.task', list).forEach((li) => { 151 | li.remove(); 152 | tasks.remove(li.dataset.tabIndex); 153 | }); 154 | }); 155 | 156 | // Drag and drop 157 | -------------------------------------------------------------------------------- /dist/index.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/index.css": 14 | /*!*************************************************************!*\ 15 | !*** ./node_modules/css-loader/dist/cjs.js!./src/index.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, \"*,\\n*::before,\\n*::after {\\n margin: 0;\\n padding: 0;\\n box-sizing: border-box;\\n}\\n\\n:root {\\n --blue: #2e8ae6;\\n --gray: #222;\\n}\\n\\nhtml {\\n font-size: 10px;\\n}\\n\\nbody {\\n width: 52rem;\\n max-width: 76.8rem;\\n margin: 0 auto;\\n min-height: 100vh;\\n display: flex;\\n flex-direction: column;\\n font-family: 'Open Sans', 'Lucida Grande', tahoma, verdana, arial, sans-serif;\\n}\\n\\nheader {\\n display: flex;\\n flex-direction: column;\\n justify-content: center;\\n align-items: center;\\n}\\n\\n.listContainer .task svg {\\n width: 2rem;\\n height: 2rem;\\n fill: #ccc;\\n}\\n\\n.listContainer .task button svg {\\n margin: -0.2rem;\\n width: 1rem;\\n height: 1rem;\\n fill: transparent;\\n}\\n\\n.listContainer li a:hover svg {\\n fill: var(--gray);\\n}\\n\\n.listContainer .task button.pressed svg {\\n fill: var(--blue);\\n}\\n\\nheader #logo {\\n padding: 1rem;\\n text-align: center;\\n margin-bottom: 4rem;\\n}\\n\\n#refresh svg {\\n width: 2rem;\\n height: 2rem;\\n fill: #ccc;\\n}\\n\\nheader #logo svg {\\n width: 5rem;\\n height: 5rem;\\n margin: 0 auto;\\n fill: white;\\n background-color: var(--blue);\\n padding: 1rem;\\n border-radius: 0.5rem;\\n}\\n\\nheader #logo h1 {\\n font-size: 3.6rem;\\n color: var(--gray);\\n}\\n\\n.listContainer {\\n display: flex;\\n flex-direction: column;\\n width: 100%;\\n}\\n\\n.listContainer ul {\\n list-style-type: none;\\n width: 100%;\\n font-size: 1.8rem;\\n}\\n\\n#refresh {\\n top: 1.7rem;\\n}\\n\\n.listContainer li a:last-child {\\n position: absolute;\\n right: 1rem;\\n cursor: pointer;\\n}\\n\\n#addNewTask a {\\n width: 2rem;\\n height: 2rem;\\n fill: #ccc;\\n text-align: center;\\n margin-top: 0.5rem;\\n}\\n\\n.listContainer p[contenteditable] {\\n outline: 0 solid transparent;\\n}\\n\\n#addNewTask p[contenteditable] {\\n min-width: 1rem;\\n}\\n\\n#refresh[data-items]::after {\\n content: attr(data-items);\\n position: absolute;\\n top: -30%;\\n left: 40%;\\n width: 2.2rem;\\n height: 2.2rem;\\n background-color: red;\\n border-radius: 50%;\\n color: white;\\n border: 0.2rem solid white;\\n display: flex;\\n justify-content: center;\\n align-items: center;\\n font-size: 1.2rem;\\n font-weight: bold;\\n}\\n\\n.listContainer li {\\n display: flex;\\n padding: 3rem;\\n border: 0.1rem solid #ddd;\\n align-items: center;\\n position: relative;\\n height: 5rem;\\n}\\n\\n.listContainer li a.trash-icon {\\n position: absolute;\\n right: 1rem;\\n cursor: pointer;\\n}\\n\\n.listContainer .task.focused {\\n background-color: #e6e5b1;\\n}\\n\\n.listContainer .task button {\\n margin-right: 1rem;\\n background-color: transparent;\\n outline: none;\\n padding: 0;\\n border: 0.2rem solid #c1c1c3;\\n color: transparent;\\n width: 1.6rem;\\n height: 1.6rem;\\n border-radius: 0.2rem;\\n cursor: pointer;\\n}\\n\\n.listContainer .task button.pressed {\\n border: transparent;\\n}\\n\\n.listContainer .task button.pressed + p:not(:focus) {\\n text-decoration: line-through;\\n font-style: italic;\\n color: var(--gray);\\n}\\n\\n.listContainer .task svg.drag-anchor {\\n cursor: move;\\n}\\n\\n.listContainer p[contenteditable]:empty:not(:focus)::before {\\n content: attr(data-placeholder);\\n font-style: italic;\\n font-weight: 300;\\n}\\n\\n#refresh:hover svg {\\n fill: var(--gray);\\n}\\n\\n#footer {\\n height: 5rem;\\n border: none;\\n color: #888;\\n background-color: #eee;\\n cursor: pointer;\\n}\\n\\n#footer:hover {\\n text-decoration: underline;\\n color: var(--gray);\\n}\\n\\n#refresh.focus {\\n animation: swirl 2s;\\n}\\n\\n@keyframes swirl {\\n 0% {\\n transform: rotate(0deg);\\n }\\n\\n 100% {\\n transform: rotate(-720deg);\\n }\\n}\\n\\n.hidden {\\n display: none;\\n}\\n\", \"\"]);\n// Exports\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (___CSS_LOADER_EXPORT___);\n\n\n//# sourceURL=webpack://to-do-list/./src/index.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://to-do-list/./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://to-do-list/./node_modules/css-loader/dist/runtime/noSourceMaps.js?"); 40 | 41 | /***/ }), 42 | 43 | /***/ "./src/index.css": 44 | /*!***********************!*\ 45 | !*** ./src/index.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_index_css__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! !!../node_modules/css-loader/dist/cjs.js!./index.css */ \"./node_modules/css-loader/dist/cjs.js!./src/index.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_index_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_index_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"] && _node_modules_css_loader_dist_cjs_js_index_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals ? _node_modules_css_loader_dist_cjs_js_index_css__WEBPACK_IMPORTED_MODULE_6__[\"default\"].locals : undefined);\n\n\n//# sourceURL=webpack://to-do-list/./src/index.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://to-do-list/./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://to-do-list/./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://to-do-list/./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://to-do-list/./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://to-do-list/./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://to-do-list/./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 _modules_utils_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./modules/utils.js */ \"./src/modules/utils.js\");\n/* harmony import */ var _modules_Tasks_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./modules/Tasks.js */ \"./src/modules/Tasks.js\");\n/* harmony import */ var _index_css__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./index.css */ \"./src/index.css\");\n\n\n\n\nconst tasks = new _modules_Tasks_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"]();\n\nconst list = _modules_utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qs('.listContainer ul');\nconst addTask = _modules_utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qs('#addNewTask');\nconst addTaskIcon = _modules_utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qs('#addNewTask object');\nconst deleteButton = _modules_utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qs('#footer');\n\n// Completing tasks\nlist.addEventListener('click', (e) => {\n const button = e.target.closest('button');\n\n if (!button) return;\n\n button.classList.toggle('pressed');\n\n const index = +button.parentElement.dataset.tabIndex;\n tasks.update(index, 'completed');\n});\n\n// Adding a new task\nconst createNewTask = (target) => {\n tasks.addTask({\n description: target.textContent,\n completed: false,\n index: tasks.tasks.length,\n });\n target.textContent = '';\n target.blur();\n};\n\naddTask.addEventListener('keyup', (e) => {\n const keyCode = e.which || e.keyCode;\n\n if (keyCode === 13) {\n createNewTask(e.target);\n }\n});\naddTaskIcon.addEventListener('click', (e) => {\n e.preventDefault();\n\n const p = e.target.parentElement.previousElementSibling;\n\n if (!p.textContent) return;\n\n createNewTask(p);\n});\n\n// Show/hide trash icon when a task is focused\nlist.addEventListener('click', (e) => {\n const { target } = e;\n\n if (target.tagName !== 'P' || !target.closest('li.task')) return;\n\n _modules_utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qs('.trash-icon', target.parentElement).classList.remove('hidden');\n _modules_utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qs('a:last-child', target.parentElement).classList.add('hidden');\n target.closest('li.task').classList.add('focused');\n});\n_modules_utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qsa('li.task p').forEach((p) => {\n p.addEventListener('blur', (e) => {\n e.preventDefault();\n\n const { target } = e;\n\n if (target.tagName !== 'P' || !target.closest('li.task')) return;\n\n target.closest('li.task').classList.remove('focused');\n setTimeout(() => {\n _modules_utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qs('.trash-icon', target.parentElement).classList.add('hidden');\n _modules_utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qs('a:last-child', target.parentElement).classList.remove('hidden');\n }, 100);\n });\n});\n\n// Delete a single task\nlist.addEventListener('click', (e) => {\n e.preventDefault();\n\n const { target } = e;\n\n if (!target.closest('a')?.classList.contains('trash-icon')) return;\n\n const li = target.closest('li.task');\n\n li.remove();\n tasks.remove(+li.dataset.tabIndex);\n});\n\n// Delete all selected tasks\ndeleteButton.addEventListener('click', () => {\n _modules_utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qsa('button.pressed').forEach((b) => b.closest('li.task').remove());\n\n tasks.tasks.filter((t) => t.completed).forEach((t) => tasks.remove(t.index));\n});\n\n// Apperently we DO have a problem with WebKit browsers when it comes to contenteditable elements\n// receiving focus when the mouse is clicked on the same line the element is, but not on the element\n// itself\nlist.addEventListener('click', (e) => {\n const { target } = e;\n\n if (target.tagName !== 'P' || !target.closest('li.task') || !target.contentEditable) {\n return;\n }\n\n target.contentEditable = true;\n target.focus();\n});\nlist.addEventListener(\n 'blur',\n (e) => {\n const { target } = e;\n\n if (target.tagName !== 'P' || !target.closest('li.task') || !target.contentEditable) {\n return;\n }\n\n target.contentEditable = false;\n },\n true,\n);\n\n// Rename a task\nlist.addEventListener('keypress', (e) => {\n const keyCode = e.which || e.keyCode;\n const { target } = e;\n const li = target.closest('li.task');\n\n if (!target.contentEditable || !li) return;\n\n if (keyCode === 13) {\n const index = +li.dataset.tabIndex;\n tasks.update(index, 'description', target.textContent);\n li.classList.remove('focused');\n target.blur();\n }\n});\n\n// Delete all tasks when clicking the refresh arrows\n_modules_utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qs('#refresh').addEventListener('click', (e) => {\n e.target.closest('a').classList.add('focus');\n});\n\n_modules_utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qs('#refresh').addEventListener('animationend', (e) => {\n e.target.closest('a').classList.remove('focus');\n\n _modules_utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qsa('li.task', list).forEach((li) => {\n li.remove();\n tasks.remove(li.dataset.tabIndex);\n });\n});\n\n// Drag and drop\n\n\n//# sourceURL=webpack://to-do-list/./src/index.js?"); 120 | 121 | /***/ }), 122 | 123 | /***/ "./src/modules/Tasks.js": 124 | /*!******************************!*\ 125 | !*** ./src/modules/Tasks.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 */ Tasks)\n/* harmony export */ });\n/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils.js */ \"./src/modules/utils.js\");\n/* harmony import */ var _localStorage_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./localStorage.js */ \"./src/modules/localStorage.js\");\n\n\n\nclass Tasks {\n tasks = [];\n\n constructor() {\n _localStorage_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"].init();\n const tasks = _localStorage_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"].read();\n tasks.sort((a, b) => a.index - b.index).forEach((t) => this.addTask(t));\n this.tasks = tasks;\n }\n\n addTask(task) {\n this.tasks.push(task);\n\n const li = _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].createElement({\n tagName: 'li',\n class: 'task',\n data: { tabIndex: task.index },\n });\n const button = _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].createElement({\n tagName: 'button',\n class: task.completed ? 'pressed' : null,\n });\n let svg = _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].createElement({\n tagName: 'svg',\n class: 'checkbox',\n });\n svg.appendChild(\n _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].createElement({\n tagName: 'use',\n src: '../public/assets/img/icons.svg#icon-check',\n }),\n );\n button.appendChild(svg);\n li.appendChild(button);\n li.appendChild(\n _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].createElement({\n tagName: 'p',\n contenteditable: true,\n textContent: task.description,\n }),\n );\n let a = _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].createElement({\n tagName: 'a',\n href: '#',\n class: ['trash-icon', 'hidden'],\n });\n svg = _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].createElement({\n tagName: 'svg',\n class: 'checkbox',\n });\n svg.appendChild(\n _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].createElement({\n tagName: 'use',\n src: '../public/assets/img/icons.svg#icon-trash',\n }),\n );\n a.appendChild(svg);\n li.appendChild(a);\n a = _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].createElement({\n tagName: 'a',\n href: '#',\n });\n svg = _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].createElement({\n tagName: 'svg',\n class: ['checkbox', 'drag-anchor'],\n });\n svg.appendChild(\n _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].createElement({\n tagName: 'use',\n src: '../public/assets/img/icons.svg#icon-more-vert',\n }),\n );\n a.appendChild(svg);\n li.appendChild(a);\n _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qs('.listContainer ul').appendChild(li);\n\n _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qs('#refresh').dataset.items = _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qsa('.task').length;\n\n _localStorage_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"].store(this.tasks);\n }\n\n remove(index) {\n const taskIndex = this.tasks.findIndex((t) => t.index === index);\n\n this.tasks.splice(taskIndex, 1);\n\n const badge = _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qs('#refresh');\n const tasks = _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qsa('.task').length;\n\n if (tasks) {\n badge.dataset.items = _utils_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].qsa('.task').length;\n } else {\n delete badge.dataset.items;\n }\n\n this.tasks.forEach((task, index) => {\n task.index = index;\n });\n\n _localStorage_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"].store(this.tasks);\n }\n\n update(index, property, newValue) {\n const task = this.tasks.find((t) => t.index === index);\n\n if (property === 'completed') {\n task.completed = !task.completed;\n } else {\n task[property] = newValue;\n }\n\n _localStorage_js__WEBPACK_IMPORTED_MODULE_1__[\"default\"].store(this.tasks);\n }\n}\n\n\n//# sourceURL=webpack://to-do-list/./src/modules/Tasks.js?"); 130 | 131 | /***/ }), 132 | 133 | /***/ "./src/modules/localStorage.js": 134 | /*!*************************************!*\ 135 | !*** ./src/modules/localStorage.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\": () => (/* binding */ LocalStorage)\n/* harmony export */ });\nclass LocalStorage {\n static tasks = [];\n\n static init() {\n const tasks = JSON.parse(localStorage.getItem('tasks'));\n\n if (tasks) {\n tasks.forEach((task) => {\n this.tasks.push(task);\n });\n }\n }\n\n static read() {\n return this.tasks;\n }\n\n static store(tasks) {\n localStorage.setItem('tasks', JSON.stringify(tasks));\n }\n}\n\n\n//# sourceURL=webpack://to-do-list/./src/modules/localStorage.js?"); 140 | 141 | /***/ }), 142 | 143 | /***/ "./src/modules/utils.js": 144 | /*!******************************!*\ 145 | !*** ./src/modules/utils.js ***! 146 | \******************************/ 147 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 148 | 149 | eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"default\": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({\n createElement: (obj) => {\n let element;\n\n if (obj.tagName && ['svg', 'use'].includes(obj.tagName.toLowerCase())) {\n element = document.createElementNS('http://www.w3.org/2000/svg', obj.tagName);\n if (obj.tagName.toLowerCase() === 'use' && obj.src) {\n element.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', obj.src);\n }\n } else {\n element = document.createElement(obj.tagName ? obj.tagName : 'div');\n }\n\n delete obj.tagName;\n\n // Itterate through each property of the obj and set it as a\n // property of the element\n Object.entries(obj).forEach(([prop, value]) => {\n // Property is a class or a collection of classes\n if (prop === 'class') {\n if (typeof value === 'object' && value !== null) {\n element.classList.add(...value.filter((v) => v));\n } else if (value) {\n element.classList.add(value);\n }\n } else if (prop === 'data' && typeof value === 'object') {\n Object.entries(value).forEach(([prop, value]) => {\n element.dataset[prop] = value;\n });\n } else if (typeof value === 'boolean') {\n element.setAttribute(prop, value);\n } else {\n element[prop] = value;\n }\n });\n\n return element;\n },\n\n qs: (selector = '*', element = document) => element.querySelector(selector),\n\n qsa: (selector = '*', element = document) => [...element.querySelectorAll(selector)],\n});\n\n\n//# sourceURL=webpack://to-do-list/./src/modules/utils.js?"); 150 | 151 | /***/ }) 152 | 153 | /******/ }); 154 | /************************************************************************/ 155 | /******/ // The module cache 156 | /******/ var __webpack_module_cache__ = {}; 157 | /******/ 158 | /******/ // The require function 159 | /******/ function __webpack_require__(moduleId) { 160 | /******/ // Check if module is in cache 161 | /******/ var cachedModule = __webpack_module_cache__[moduleId]; 162 | /******/ if (cachedModule !== undefined) { 163 | /******/ return cachedModule.exports; 164 | /******/ } 165 | /******/ // Create a new module (and put it into the cache) 166 | /******/ var module = __webpack_module_cache__[moduleId] = { 167 | /******/ id: moduleId, 168 | /******/ // no module.loaded needed 169 | /******/ exports: {} 170 | /******/ }; 171 | /******/ 172 | /******/ // Execute the module function 173 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); 174 | /******/ 175 | /******/ // Return the exports of the module 176 | /******/ return module.exports; 177 | /******/ } 178 | /******/ 179 | /************************************************************************/ 180 | /******/ /* webpack/runtime/compat get default export */ 181 | /******/ (() => { 182 | /******/ // getDefaultExport function for compatibility with non-harmony modules 183 | /******/ __webpack_require__.n = (module) => { 184 | /******/ var getter = module && module.__esModule ? 185 | /******/ () => (module['default']) : 186 | /******/ () => (module); 187 | /******/ __webpack_require__.d(getter, { a: getter }); 188 | /******/ return getter; 189 | /******/ }; 190 | /******/ })(); 191 | /******/ 192 | /******/ /* webpack/runtime/define property getters */ 193 | /******/ (() => { 194 | /******/ // define getter functions for harmony exports 195 | /******/ __webpack_require__.d = (exports, definition) => { 196 | /******/ for(var key in definition) { 197 | /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { 198 | /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); 199 | /******/ } 200 | /******/ } 201 | /******/ }; 202 | /******/ })(); 203 | /******/ 204 | /******/ /* webpack/runtime/hasOwnProperty shorthand */ 205 | /******/ (() => { 206 | /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) 207 | /******/ })(); 208 | /******/ 209 | /******/ /* webpack/runtime/make namespace object */ 210 | /******/ (() => { 211 | /******/ // define __esModule on exports 212 | /******/ __webpack_require__.r = (exports) => { 213 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 214 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 215 | /******/ } 216 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 217 | /******/ }; 218 | /******/ })(); 219 | /******/ 220 | /******/ /* webpack/runtime/nonce */ 221 | /******/ (() => { 222 | /******/ __webpack_require__.nc = undefined; 223 | /******/ })(); 224 | /******/ 225 | /************************************************************************/ 226 | /******/ 227 | /******/ // startup 228 | /******/ // Load entry module and return exports 229 | /******/ // This entry module can't be inlined because the eval devtool is used. 230 | /******/ var __webpack_exports__ = __webpack_require__("./src/index.js"); 231 | /******/ 232 | /******/ })() 233 | ; --------------------------------------------------------------------------------