├── .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 |
18 |
19 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | A minimalist TODO list
8 |
9 |
10 |
18 |
19 |
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": "",
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 | 
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 | 
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 = ``;
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 = ``;
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 |
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 | ;
--------------------------------------------------------------------------------