├── .eslintrc.json
├── .gitignore
├── README.md
├── dist
├── index.html
└── main.js
├── package-lock.json
├── package.json
├── src
├── img
│ ├── mockup1.png
│ ├── mockup2.png
│ └── mockup3.png
├── index.js
├── modules
│ ├── app.js
│ ├── filter.js
│ ├── models
│ │ ├── projectModel.js
│ │ ├── storageModel.js
│ │ └── taskModel.js
│ └── views
│ │ ├── projectView.js
│ │ └── taskView.js
└── styles.scss
└── webpack.config.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true
5 | },
6 | "extends": ["airbnb-base", "plugin:prettier/recommended"],
7 | "overrides": [],
8 | "parserOptions": {
9 | "ecmaVersion": "latest",
10 | "sourceType": "module"
11 | },
12 | "plugins": ["prettier"],
13 | "rules": {
14 | "prettier/prettier": ["error", { "singleQuote": true }],
15 | "function-paren-newline": "off",
16 | "no-plusplus": ["error", { "allowForLoopAfterthoughts": true }],
17 | "no-param-reassign": 0,
18 | "operator-linebreak": "off",
19 | "eslintprettier/prettier": ["off"]
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # To-Do List
2 |
3 | 
4 |
5 | 
6 |
7 | 
8 |
9 | ## [Live Demo](https://bartbzd.github.io/todo-list/)
10 |
11 | ## Built with:
12 |
13 | - Webpack
14 | - HTML,SASS
15 | - JavaScript
16 | - npm, date-fns
17 | - Web Storage, local
18 |
19 | ## Extra features added:
20 |
21 | - Favorite starred tasks
22 | - Mobile sidebar modal
23 | - Sort view by filter
24 |
25 | ## Things I learned:
26 |
27 | - Designing UI with modern app-like approach
28 | - Installing Webpack loaders and plugins
29 | - Building CSS animations and keyframes
30 | - Compiling project files with Webpack
31 | - Configuring eslint with prettier
32 | - Using additional array methods
33 |
--------------------------------------------------------------------------------
/dist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
13 |
15 |
17 |
20 | todo.
21 |
22 |
23 |
24 |
25 |
26 |
27 | todo.
28 |
29 |
37 |
38 |
39 |
40 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
119 |
120 |
152 |
153 |
154 |
155 |
156 |
157 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo-list",
3 | "version": "1.0.0",
4 | "description": "To Do List App",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack",
9 | "start": "webpack serve --open"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/bartbzd/todo-list.git"
14 | },
15 | "keywords": [],
16 | "author": "Bart Bieszczad",
17 | "license": "ISC",
18 | "bugs": {
19 | "url": "https://github.com/bartbzd/todo-list/issues"
20 | },
21 | "homepage": "https://github.com/bartbzd/todo-list#readme",
22 | "devDependencies": {
23 | "css-loader": "^6.7.3",
24 | "eslint": "^8.33.0",
25 | "eslint-config-airbnb-base": "^15.0.0",
26 | "eslint-config-prettier": "^8.8.0",
27 | "eslint-plugin-import": "^2.27.5",
28 | "eslint-plugin-prettier": "^4.2.1",
29 | "node-sass": "^8.0.0",
30 | "prettier": "2.8.7",
31 | "sass-loader": "^13.2.0",
32 | "style-loader": "^3.3.1",
33 | "webpack": "^5.75.0",
34 | "webpack-cli": "^5.0.1",
35 | "webpack-dev-server": "^4.11.1"
36 | },
37 | "dependencies": {
38 | "date-fns": "^2.29.3"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/img/mockup1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartbzd/todo-list/f2daaf0fd95d33a674d2c48c5f7f20ec42586bbc/src/img/mockup1.png
--------------------------------------------------------------------------------
/src/img/mockup2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartbzd/todo-list/f2daaf0fd95d33a674d2c48c5f7f20ec42586bbc/src/img/mockup2.png
--------------------------------------------------------------------------------
/src/img/mockup3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartbzd/todo-list/f2daaf0fd95d33a674d2c48c5f7f20ec42586bbc/src/img/mockup3.png
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import './styles.scss';
2 | import appController from './modules/app';
3 |
4 | appController();
5 |
--------------------------------------------------------------------------------
/src/modules/app.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-use-before-define */
2 | /* eslint-disable prettier/prettier */
3 | import { parseISO, isToday, isPast, isThisWeek } from 'date-fns';
4 | import Task from './models/taskModel';
5 | import Project from './models/projectModel';
6 | import storage, { projects, allTasksList } from './models/storageModel';
7 | import createTask from './views/taskView';
8 | import createProject from './views/projectView';
9 |
10 | export default function appController() {
11 | const projectForm = document.querySelector('#project-form');
12 | const projectInput = document.querySelector('#project-name');
13 | const taskForm = document.querySelector('.task-form');
14 | const tasksWrapper = document.querySelector('.t-wrapper');
15 | const formWrapper = document.querySelector('.f-wrapper');
16 | const openWrapper = document.querySelector('.o-wrapper');
17 | const editBtn = document.querySelector('.edit-task-btn');
18 | const addTaskBtn = document.querySelector('.add-btn');
19 | const addBtn = document.querySelector('.add-task-btn');
20 | const addProjectBtn = document.querySelector('.fa-plus');
21 | const titleInput = document.querySelector('#task');
22 | const noteInput = document.querySelector('#note');
23 | const dateInput = document.querySelector('#date');
24 | const formInput = document.querySelector('#projects');
25 | const formStar = document.querySelector('.add-star');
26 | const projectGrp = document.querySelector('.project-grp');
27 | const input = document.querySelector('#project-name');
28 | const selectAll = document.querySelector('.all');
29 | const selectStarred = document.querySelector('.starred');
30 | const selectToday = document.querySelector('.today');
31 | const selectWeek = document.querySelector('.week');
32 | const themeIcon = document.querySelector('.theme');
33 | const mobileMenu = document.querySelector('.menu-icon');
34 | const sidebar = document.querySelector('.sidebar');
35 | const content = document.querySelector('.content');
36 | const filters = document.querySelector('.filters');
37 | const logo = document.querySelector('header h1');
38 |
39 | let componentColor = window
40 | .getComputedStyle(document.documentElement)
41 | .getPropertyValue('--component');
42 | let primaryColor = window
43 | .getComputedStyle(document.documentElement)
44 | .getPropertyValue('--primary');
45 | const textColor = window
46 | .getComputedStyle(document.documentElement)
47 | .getPropertyValue('--dk-text');
48 | const subtextColor = window
49 | .getComputedStyle(document.documentElement)
50 | .getPropertyValue('--dk-subtext');
51 |
52 | let taskIndex = 0;
53 | let projectIndex;
54 | let currProject;
55 | let lastProject;
56 | let selected = '';
57 |
58 | const showForm = () => {
59 | formWrapper.style.animation = 'ease-out formRight 0.1s';
60 | formWrapper.style.display = 'flex';
61 | setTimeout(() => {
62 | formWrapper.style.animation = '';
63 | }, 100);
64 | };
65 | const hideForm = () => {
66 | formWrapper.style.animation = 'ease-out formRight reverse 0.1s';
67 | setTimeout(() => {
68 | formWrapper.style.display = 'none';
69 | formWrapper.style.animation = '';
70 | }, 100);
71 | };
72 | const showTasksRight = () => {
73 | tasksWrapper.style.display = 'flex';
74 | tasksWrapper.style.animation = 'ease-out taskRight reverse 0.1s';
75 | setTimeout(() => {
76 | tasksWrapper.style.animation = '';
77 | }, 100);
78 | };
79 | const hideTasksRight = () => {
80 | tasksWrapper.style.animation = 'ease-out taskRight 0.1s';
81 | setTimeout(() => {
82 | tasksWrapper.style.display = 'none';
83 | tasksWrapper.style.animation = '';
84 | }, 100);
85 | };
86 | const showTasksLeft = () => {
87 | tasksWrapper.style.display = 'flex';
88 | tasksWrapper.style.animation = 'ease-out formRight 0.1s';
89 | setTimeout(() => {
90 | tasksWrapper.style.animation = '';
91 | }, 100);
92 | };
93 | const hideTasksLeft = () => {
94 | tasksWrapper.style.animation = 'ease-out formRight reverse 0.1s';
95 | setTimeout(() => {
96 | tasksWrapper.style.display = 'none';
97 | tasksWrapper.style.animation = '';
98 | }, 100);
99 | };
100 | const openTask = () => {
101 | openWrapper.style.display = 'flex';
102 | openWrapper.style.animation = 'ease-out taskRight reverse 0.1s';
103 | setTimeout(() => {
104 | openWrapper.style.animation = '';
105 | }, 100);
106 | };
107 | const closeTask = () => {
108 | openWrapper.style.animation = 'ease-out taskRight 0.1s';
109 | setTimeout(() => {
110 | openWrapper.style.display = 'none';
111 | openWrapper.style.animation = '';
112 | }, 100);
113 | };
114 |
115 | function isProjectValid() {
116 | const project = document.querySelector('#project-name');
117 | if (!project.value) {
118 | project.setCustomValidity('Project cannot be empty');
119 | project.reportValidity();
120 | return false;
121 | }
122 | return true;
123 | }
124 | function isTaskValid() {
125 | const task = document.querySelector('#task');
126 | if (!task.value) {
127 | task.setCustomValidity('Task cannot be empty');
128 | task.reportValidity();
129 | return false;
130 | }
131 | return true;
132 | }
133 |
134 | function toggleBtnText() {
135 | const title = document.querySelector('.form-title-header');
136 | title.textContent = 'Edit Task';
137 | editBtn.classList.toggle('hidden');
138 | addBtn.classList.toggle('hidden');
139 | }
140 | function toggleComplete(e, project) {
141 | const wrapper = e.target.closest('.task');
142 | taskIndex = e.target.closest('.task').getAttribute('data-id');
143 | const task = project.getTasks()[taskIndex];
144 | const selectedTask = e.target;
145 |
146 | if (!task.isComplete) {
147 | wrapper.addEventListener('click', renderTasksOpenView);
148 | }
149 | task.isComplete = !task.isComplete;
150 | if (task.isComplete) {
151 | wrapper.removeEventListener('click', renderTasksOpenView);
152 | }
153 |
154 | const checkmarkClasses = ['fa-regular', 'fa-solid', 'fa-circle', 'fa-circle-check'];
155 | checkmarkClasses.forEach((className) => {
156 | if (selectedTask.classList.contains('check')) {
157 | selectedTask.classList.toggle(className);
158 | }
159 | });
160 |
161 | const title = selectedTask.closest('.task').querySelector('.task-title');
162 | const edit = selectedTask.closest('.task').querySelector('.edit');
163 | const trash = selectedTask.closest('.task').querySelector('.delete');
164 | const star = selectedTask.closest('.task').querySelector('.fa-star');
165 |
166 | title.style.transition = '0.2s ease-in-out';
167 | wrapper.style.transition = '0.2s ease-in-out';
168 | edit.style.transition = '0.2s ease-in-out';
169 | trash.style.transition = '0.2s ease-in-out';
170 | star.style.transition = '0.2s ease-in-out';
171 | if (title.style.textDecoration === '' && title.style.color !== '#d2d8f7a6') {
172 | wrapper.style.backgroundColor = 'transparent';
173 | wrapper.style.boxShadow = 'none';
174 | title.style.textDecoration = 'line-through';
175 | title.style.color = subtextColor;
176 | edit.style.opacity = '0';
177 | trash.style.opacity = '1';
178 | star.style.opacity = '0';
179 |
180 | setTimeout(() => {
181 | edit.style.display = 'none';
182 | trash.style.display = 'flex';
183 | star.style.display = 'none';
184 | }, 100);
185 | } else {
186 | wrapper.style.backgroundColor = componentColor;
187 | title.style.textDecoration = '';
188 | title.style.color = textColor;
189 | edit.style.opacity = '0';
190 | trash.style.opacity = '0';
191 | star.style.opacity = '1';
192 |
193 | setTimeout(() => {
194 | renderTasks(currProject);
195 | edit.style.display = 'flex';
196 | trash.style.display = 'none';
197 | star.style.display = 'flex';
198 | }, 100);
199 | }
200 | }
201 | function toggleFormStar() {
202 | formStar.classList.toggle('starred');
203 | formStar.classList.toggle('fa-regular');
204 | formStar.classList.toggle('fa-solid');
205 | }
206 | function togglePlusBtn() {
207 | addProjectBtn.classList.toggle('plus');
208 | addProjectBtn.classList.toggle('rotated');
209 | }
210 | function toggleAddProject() {
211 | document.querySelector('form').reset();
212 | selected = '';
213 | togglePlusBtn();
214 | projectForm.hidden = !projectForm.hidden;
215 |
216 | if (!projectForm.hidden) {
217 | projectForm.style.animation = 'ease-out formVertical 0.2s';
218 | for (let i = 0; i < projectGrp.children.length; i++) {
219 | projectGrp.children[i].style.animation = 'ease-out formVertical 0.2s';
220 | }
221 | projectGrp.insertBefore(projectForm, projectGrp.firstChild);
222 | input.focus();
223 | }
224 |
225 | const projectBtns = document.querySelectorAll('.project-btn-grp .options');
226 | projectBtns.forEach((btn) => {
227 | btn.style.opacity = '0';
228 | });
229 |
230 | if (projectForm.hidden) {
231 | filters.classList.remove('filtersHide');
232 | selected = '';
233 | resetProjects();
234 | renderProjects();
235 | updateSelectedProject();
236 | updateSelectedFilter();
237 | }
238 | }
239 | function toggleEditProject(e) {
240 | projectForm.hidden = !projectForm.hidden;
241 | const projectBtns = document.querySelectorAll('.options');
242 |
243 | projectBtns.forEach((btn) => {
244 | btn.style.display = 'none';
245 | btn.style.opacity = '0';
246 | });
247 |
248 | if (!projectForm.hidden) {
249 | projectForm.style.animation = 'ease-out appearForm 0.2s';
250 | projectIndex = Number(e.target.closest('.project').getAttribute('data-id'));
251 | const selectedIndex = projectGrp.children.item(projectIndex);
252 |
253 | projectGrp.insertBefore(projectForm, selectedIndex);
254 | input.value = projects[projectIndex].name;
255 | input.focus();
256 |
257 | selected = e.target.closest('.project');
258 | selected.classList.toggle('edited');
259 | selected.style.display = 'none';
260 |
261 | currProject = projects[projectIndex];
262 | }
263 | togglePlusBtn();
264 | }
265 | function toggleOverflow() {
266 | const note = document.querySelector('#open-note');
267 | const botLine = document.querySelector('.bot-note-line');
268 | if (note.scrollHeight > note.clientHeight) {
269 | botLine.classList.add('visible');
270 | } else botLine.classList.remove('visible');
271 | }
272 |
273 | function resetStar() {
274 | document.querySelector('.add-star').className = 'add-star fa-regular fa-star';
275 | }
276 | function resetProjects() {
277 | document.querySelector('.project-grp').innerHTML = '';
278 | document.querySelector('select').innerHTML = '';
279 | }
280 | function resetTasks() {
281 | document.querySelector('.tasks').innerHTML = '';
282 | }
283 | function resetForm() {
284 | if (editBtn.classList.contains('hidden')) {
285 | document.querySelector('.task-form').reset();
286 | document.querySelector('form').reset();
287 | }
288 | }
289 | function resetFilters() {
290 | const filtersList = document.querySelectorAll('.filter');
291 | filtersList.forEach((filter) => {
292 | filter.style.backgroundColor = 'transparent';
293 | });
294 | }
295 | function resetSelectedProject() {
296 | const folders = document.querySelectorAll('.folder');
297 | folders.forEach((folder) => {
298 | folder.className = 'folder fa-regular fa-folder';
299 | });
300 | const projectsList = document.querySelectorAll('.project');
301 | projectsList.forEach((project) => {
302 | project.style.backgroundColor = 'transparent';
303 | });
304 | }
305 | function resetMobileAnimations() {
306 | content.style.animation = '';
307 | sidebar.style.animation = '';
308 | }
309 |
310 | function updateOpenTask(e) {
311 | const project = document.querySelector('#open-project');
312 | const folder = document.querySelector('.open-folder');
313 | const title = document.querySelector('#open-title');
314 | const note = document.querySelector('#open-note');
315 | const date = document.querySelector('.open-date');
316 | const star = document.querySelector('.open-star');
317 | const id = e.target.closest('.task').getAttribute('data-id');
318 | const isStarred = currProject.tasks[id].getIsStarred();
319 |
320 | title.textContent = currProject.tasks[id].title;
321 |
322 | if (currProject.name === 'All') {
323 | project.textContent = 'All';
324 | folder.className = 'material-symbols-rounded open-folder';
325 | folder.textContent = 'inbox';
326 | } else if (currProject.name === 'Starred') {
327 | project.textContent = 'Starred';
328 | folder.className = 'fa-solid fa-star open-folder';
329 | folder.textContent = '';
330 | } else if (currProject.name === 'Today') {
331 | project.textContent = 'Today';
332 | folder.className = 'material-symbols-rounded';
333 | folder.textContent = 'today';
334 | } else if (currProject.name === 'Week') {
335 | project.textContent = 'Today';
336 | folder.className = 'material-symbols-rounded';
337 | folder.textContent = 'date_range';
338 | } else {
339 | folder.className = 'material-symbols-rounded open-folder';
340 | project.textContent = currProject.tasks[id].project;
341 | folder.textContent = 'folder';
342 | }
343 |
344 | if (isStarred === false) {
345 | star.style.display = 'none';
346 | } else star.style.display = 'inline-block';
347 |
348 | if (currProject.tasks[id].note === '') {
349 | note.textContent = 'No note';
350 | note.style.textAlign = 'center';
351 | } else {
352 | note.style.textAlign = 'left';
353 | note.textContent = currProject.tasks[id].note;
354 | }
355 |
356 | const selectedDate = parseISO(currProject.tasks[id].date);
357 | if (currProject.tasks[id].date === '') {
358 | date.textContent = '';
359 | } else if (isPast(selectedDate) && !isToday(selectedDate)) {
360 | date.textContent = 'Past Due';
361 | date.style.color = '#E34A4A';
362 | } else {
363 | date.textContent = selectedDate.toLocaleDateString();
364 | }
365 | }
366 | function updateProjectsIndex() {
367 | for (let i = 0; i < projects.length; i++) {
368 | projects[i].index = i;
369 | }
370 | }
371 | function updateSelectedProject() {
372 | updateProjectsIndex();
373 | resetFilters();
374 | const projectsList = document.querySelectorAll('.project');
375 | let foundProject = false;
376 | projectsList.forEach((project, index) => {
377 | if (foundProject) return;
378 | const i = project.querySelector('i');
379 | const p = project.querySelector('p');
380 | if (p.textContent === currProject.name && index === currProject.index) {
381 | p.closest('.project').style.backgroundColor = componentColor;
382 | i.closest('.folder').className = 'folder fa-solid fa-folder';
383 | foundProject = true;
384 | }
385 | });
386 | }
387 | function updateSelectedFilter() {
388 | const filtersList = ['All', 'Starred', 'Today', 'Week'];
389 | const arr = [selectAll, selectStarred, selectToday, selectWeek];
390 | for (let i = 0; i < filtersList.length; i++) {
391 | if (filtersList[i] === currProject.name) {
392 | arr[i].style.transition = '0.2s ease-out';
393 | arr[i].style.backgroundColor = componentColor;
394 | }
395 | }
396 | }
397 | function updateColors() {
398 | componentColor = window
399 | .getComputedStyle(document.documentElement)
400 | .getPropertyValue('--component');
401 | primaryColor = window
402 | .getComputedStyle(document.documentElement)
403 | .getPropertyValue('--primary');
404 | }
405 |
406 | function renderTasksOpenView(e) {
407 | hideTasksLeft();
408 |
409 | setTimeout(() => {
410 | openTask();
411 | updateOpenTask(e);
412 | toggleOverflow();
413 | }, 100);
414 | }
415 | function renderFormView() {
416 | resetForm();
417 | resetStar();
418 | if (!projectForm.hidden) {
419 | toggleAddProject();
420 | }
421 | document.querySelector('select').value = currProject.name;
422 | document.querySelector('.form-title-header').textContent = 'Add Task';
423 |
424 | hideTasksRight();
425 | setTimeout(() => {
426 | showForm();
427 | titleInput.focus();
428 | }, 100);
429 | }
430 | function renderEditView(e, project) {
431 | e.stopImmediatePropagation();
432 | if (!projectForm.hidden) {
433 | toggleAddProject();
434 | }
435 | taskIndex = e.currentTarget.closest('.task').getAttribute('data-id');
436 |
437 | titleInput.value = project.getTasks()[taskIndex].title;
438 | noteInput.value = project.getTasks()[taskIndex].note;
439 | dateInput.value = project.getTasks()[taskIndex].date;
440 |
441 | const projectName = e.currentTarget
442 | .closest('.task')
443 | .getAttribute('data-project-name');
444 | document.querySelector('select').value = projectName;
445 | lastProject = projects.find(({ name }) => name === projectName);
446 | if (project.getTasks()[taskIndex].getIsStarred()) {
447 | formStar.classList.add('starred');
448 | formStar.classList.remove('fa-regular');
449 | formStar.classList.add('fa-solid');
450 | } else {
451 | formStar.classList.remove('starred');
452 | formStar.classList.add('fa-regular');
453 | formStar.classList.remove('fa-solid');
454 | }
455 |
456 | hideTasksRight();
457 | setTimeout(() => {
458 | showForm();
459 | titleInput.focus();
460 | toggleBtnText();
461 | }, 100);
462 | }
463 | function renderTasksView(e) {
464 | e.preventDefault();
465 | if (addBtn.classList.contains('hidden')) {
466 | toggleBtnText();
467 | }
468 | if (formWrapper.style.display === 'flex') {
469 | hideForm();
470 | setTimeout(() => {
471 | showTasksRight();
472 | }, 100);
473 | return;
474 | }
475 | if (openWrapper.style.display === 'flex') {
476 | closeTask();
477 | setTimeout(() => {
478 | showTasksLeft();
479 | }, 100);
480 | }
481 | }
482 |
483 | function handleProjectClick(e) {
484 | resetFilters();
485 | resetSelectedProject();
486 | const projectWrappers = document.querySelectorAll('.project');
487 | const project = e.currentTarget.closest('.project');
488 | projectWrappers.forEach((wrapper) => {
489 | wrapper.style.backgroundColor = '';
490 | });
491 | project.style.backgroundColor = componentColor;
492 |
493 | const folder = project.querySelector('.folder');
494 | folder.className = 'folder fa-solid fa-folder';
495 |
496 | projectIndex = Number(project.getAttribute('data-id'));
497 | currProject = projects[projectIndex];
498 |
499 | renderTasks(currProject);
500 | renderTasksView(e);
501 | updateSelectedProject();
502 |
503 | closeSideBarModal();
504 | }
505 | function handleEditProjectClick(e) {
506 | e.stopImmediatePropagation();
507 | toggleEditProject(e);
508 | renderTasks(currProject);
509 | renderTasksView(e);
510 |
511 | if (content.style.display === 'none') {
512 | toggleSideBarFocus();
513 | }
514 | }
515 | function handleDeleteProjectClick(e) {
516 | e.stopPropagation();
517 | e.target.closest('.project').style.animation = 'ease-in formRight reverse 0.2s';
518 | e.target.closest('.project').style.opacity = '0.7';
519 |
520 | setTimeout(() => {
521 | deleteProject(e);
522 | resetProjects();
523 | renderProjects();
524 |
525 | if (projects.length === 0 || currProject === allTasksList) {
526 | updateAllTasks();
527 | currProject = allTasksList;
528 | resetFilters();
529 | updateSelectedFilter();
530 | } else if (projects.length > 0) {
531 | updateSelectedProject();
532 | } else updateSelectedFilter();
533 |
534 | renderTasks(currProject);
535 | renderTasksView(e);
536 | }, 100);
537 | }
538 | function addProjectHandlers() {
539 | const projectWrappers = document.querySelectorAll('.project');
540 | const editBtns = document.querySelectorAll('.edit-p');
541 | const deleteBtns = document.querySelectorAll('.delete-p');
542 |
543 | projectWrappers.forEach((wrapper) => {
544 | wrapper.addEventListener('click', handleProjectClick);
545 | });
546 |
547 | editBtns.forEach((btn) => {
548 | btn.addEventListener('click', handleEditProjectClick);
549 | });
550 |
551 | deleteBtns.forEach((btn) => {
552 | btn.addEventListener('click', handleDeleteProjectClick);
553 | });
554 | }
555 | function renderProjects() {
556 | projects.forEach((project) => {
557 | createProject(project);
558 | });
559 | addProjectHandlers();
560 | }
561 | function storeProject() {
562 | const name = document.querySelector('#project-name').value;
563 | return new Project(name);
564 | }
565 | function addProject() {
566 | if (!isProjectValid()) return;
567 | const newProject = storeProject();
568 | projects.unshift(newProject);
569 | currProject = newProject;
570 | currProject.index = projects.indexOf(newProject);
571 | updateProjectsIndex();
572 |
573 | resetForm();
574 | resetProjects();
575 | renderProjects();
576 | toggleAddProject();
577 | updateSelectedProject();
578 | closeSideBarModal();
579 | storage().saveData();
580 | }
581 | function editProject() {
582 | const name = document.querySelector('#project-name');
583 | if (!name.value) {
584 | name.setCustomValidity('Task cannot be empty');
585 | name.reportValidity();
586 | return;
587 | }
588 |
589 | projects[projectIndex].name = name.value;
590 | currProject.index = projectIndex;
591 | resetForm();
592 | toggleEditProject();
593 | resetProjects();
594 | renderProjects();
595 | updateSelectedProject();
596 | closeSideBarModal();
597 | storage().saveData();
598 | }
599 | function deleteProject(e) {
600 | projectIndex = Number(e.target.closest('.project').getAttribute('data-id'));
601 | projects.splice(projectIndex, 1);
602 | updateProjectsIndex();
603 | storage().saveData();
604 | }
605 |
606 | function addTaskHandlers() {
607 | const taskWrapper = document.querySelectorAll('.task');
608 | const checkmarks = document.querySelectorAll('.fa-circle, .fa-circle-check');
609 | const editBtns = document.querySelectorAll('.edit');
610 | const deleteBtns = document.querySelectorAll('.delete');
611 | const backBtn = document.querySelectorAll('.back-btn');
612 |
613 | backBtn.forEach((button) => {
614 | button.addEventListener('click', renderTasksView);
615 | });
616 | taskWrapper.forEach((task) => {
617 | if (!task.isComplete) {
618 | task.addEventListener('click', renderTasksOpenView);
619 | }
620 | if (task.isComplete) {
621 | task.removeEventListener('click', renderTasksOpenView);
622 | }
623 | });
624 | checkmarks.forEach((checkmark) => {
625 | checkmark.addEventListener('click', (e) => {
626 | toggleComplete(e, currProject);
627 | });
628 | });
629 | editBtns.forEach((button) => {
630 | button.addEventListener('click', (e) => {
631 | renderEditView(e, currProject);
632 | });
633 | });
634 | deleteBtns.forEach((btn) => {
635 | btn.addEventListener('click', (e) => {
636 | e.stopPropagation();
637 | e.target.closest('.task').style.animation = 'ease-in formRight reverse 0.2s';
638 | e.target.closest('.task').style.opacity = '0';
639 |
640 | setTimeout(() => {
641 | toggleComplete(e, currProject);
642 | deleteTask(e, currProject);
643 | }, 200);
644 | });
645 | });
646 | }
647 | function renderTasks(project, selectedTask) {
648 | resetTasks();
649 | if (project.getTasks().length === 0) {
650 | document.querySelector('.tasks').appendChild(document.createElement('p'));
651 | document.querySelector('.tasks p').textContent = 'No tasks found';
652 | document.querySelector('.tasks p').className = 'no-tasks';
653 | }
654 |
655 | project.getTasks().forEach((task) => {
656 | const taskWrapper = createTask(task, project.getTasks());
657 | document.querySelector('.tasks').append(taskWrapper);
658 | taskWrapper.setAttribute('data-project-name', task.project);
659 |
660 | if (task.isStarred) {
661 | taskWrapper.querySelector('.fa-star').classList.replace('fa-regular', 'fa-solid');
662 | }
663 |
664 | if (task.isComplete && task !== selectedTask) {
665 | const wrapper = taskWrapper.closest('.task');
666 | const title = taskWrapper.closest('.task').querySelector('.task-title');
667 | const edit = taskWrapper.closest('.task').querySelector('.edit');
668 | const trash = taskWrapper.closest('.task').querySelector('.delete');
669 | const star = taskWrapper.closest('.task').querySelector('.fa-star');
670 |
671 | title.style.textDecoration = 'line-through';
672 | title.style.color = subtextColor;
673 |
674 | wrapper.style.backgroundColor = 'transparent';
675 | wrapper.style.boxShadow = 'none';
676 | wrapper.removeEventListener('click', renderTasksOpenView);
677 |
678 | edit.style.display = 'none';
679 | trash.style.display = 'flex';
680 | star.style.display = 'none';
681 | }
682 | });
683 | addTaskHandlers();
684 | }
685 | function storeTask() {
686 | const title = document.querySelector('#task').value;
687 | const note = document.querySelector('#note').value;
688 | const project = document.querySelector('#projects').value;
689 | const date = document.querySelector('#date').value;
690 | const isStarred = formStar.classList.contains('starred');
691 |
692 | return new Task(title, note, project, date, isStarred);
693 | }
694 | function addTask(e, project) {
695 | if (!isTaskValid()) return;
696 | e.preventDefault();
697 |
698 | const newTask = storeTask();
699 | project = projects.find(({ name }) => name === formInput.value);
700 |
701 | if (formInput.value === '') {
702 | allTasksList.getTasks().push(newTask);
703 | currProject = allTasksList;
704 | } else {
705 | project.getTasks().push(newTask);
706 | currProject = project;
707 | }
708 |
709 | if (!projectForm.hidden) {
710 | toggleAddProject();
711 | }
712 |
713 | renderTasksView(e);
714 | renderTasks(currProject);
715 | resetForm();
716 | storage().saveData();
717 | }
718 | function editTask(e, project) {
719 | if (!isTaskValid()) return;
720 | e.preventDefault();
721 | const editedTask = storeTask();
722 | const temp = projects.find(({ name }) => name === formInput.value);
723 |
724 | if (
725 | currProject.name === 'Starred' ||
726 | currProject.name === 'Today' ||
727 | currProject.name === 'Week'
728 | ) {
729 | currProject = allTasksList;
730 | }
731 |
732 | if (
733 | formInput.value !== project.name &&
734 | formInput.value !== '' &&
735 | currProject === allTasksList
736 | ) {
737 | temp.getTasks().push(editedTask);
738 | allTasksList.getTasks().splice(taskIndex, 1);
739 | currProject = temp;
740 |
741 | if (lastProject !== undefined) {
742 | lastProject.getTasks().splice(taskIndex, 1);
743 | lastProject = undefined;
744 | }
745 | } else if (formInput.value !== project.name && formInput.value !== '') {
746 | temp.getTasks().push(editedTask);
747 | project.getTasks().splice(taskIndex, 1);
748 | allTasksList.getTasks().splice(taskIndex, 1);
749 | currProject = temp;
750 | } else {
751 | project.getTasks().splice(taskIndex, 1, editedTask);
752 | }
753 |
754 | if (!projectForm.hidden) {
755 | toggleAddProject();
756 | }
757 |
758 | resetProjects();
759 | renderProjects();
760 | renderTasksView(e);
761 | renderTasks(currProject);
762 | updateProjectsIndex();
763 | updateSelectedProject();
764 | storage().saveData();
765 | }
766 | function deleteTask(e, project) {
767 | e.stopImmediatePropagation();
768 | taskIndex = e.target.closest('.task').getAttribute('data-id');
769 | const taskToDelete = project.getTasks()[taskIndex];
770 |
771 | let projectToDeleteFrom;
772 | for (let i = 0; i < projects.length; i++) {
773 | if (projects[i].getTasks().includes(taskToDelete)) {
774 | projectToDeleteFrom = projects[i];
775 | break;
776 | }
777 | }
778 |
779 | if (projectToDeleteFrom !== undefined) {
780 | projectToDeleteFrom.removeTask(taskToDelete);
781 | }
782 |
783 | if (projectToDeleteFrom !== currProject) {
784 | allTasksList.removeTask(taskToDelete);
785 | currProject = allTasksList;
786 | }
787 |
788 | renderTasksView(e);
789 | renderTasks(currProject, taskToDelete);
790 | updateSelectedProject();
791 | updateSelectedFilter();
792 | storage().saveData();
793 | }
794 | function updateAllTasks() {
795 | if (allTasksList.getTasks().length === 0 || allTasksList.getTasks() !== currProject) {
796 | const allTasks = projects.flatMap((project) => project.tasks);
797 | const unassignedTasks = allTasksList
798 | .getTasks()
799 | .filter((task) => task.project === '');
800 | const combinedTasks = allTasks.concat(unassignedTasks);
801 | allTasksList.tasks.length = 0;
802 | allTasksList.tasks.push(...combinedTasks);
803 | } else {
804 | currProject = allTasksList;
805 | }
806 | }
807 | function updateStarredTasks() {
808 | const starredTasks = allTasksList.getTasks().filter((task) => task.isStarred);
809 | currProject = new Project('Starred', starredTasks);
810 | }
811 | function updateTodayTasks() {
812 | const todayTasks = allTasksList
813 | .getTasks()
814 | .filter((task) => isToday(parseISO(task.date)));
815 |
816 | currProject = new Project('Today', todayTasks);
817 | }
818 | function updateWeekTasks() {
819 | const weekTasks = allTasksList
820 | .getTasks()
821 | .filter((task) => isThisWeek(parseISO(task.date)));
822 |
823 | currProject = new Project('Week', weekTasks);
824 | }
825 | function showAll(e) {
826 | resetFilters();
827 | updateAllTasks();
828 | currProject = allTasksList;
829 |
830 | resetSelectedProject();
831 | resetProjects();
832 |
833 | renderProjects();
834 | renderTasksView(e);
835 | renderTasks(currProject);
836 | updateSelectedFilter();
837 | closeSideBarModal();
838 | }
839 | function showStarred(e) {
840 | resetFilters();
841 | updateAllTasks();
842 | updateStarredTasks();
843 | const starredProject = currProject;
844 |
845 | resetSelectedProject();
846 | resetProjects();
847 |
848 | renderProjects();
849 | renderTasksView(e);
850 | renderTasks(starredProject);
851 | currProject = starredProject;
852 | updateSelectedFilter();
853 | closeSideBarModal();
854 | }
855 | function showToday(e) {
856 | resetFilters();
857 | updateAllTasks();
858 | updateTodayTasks();
859 | const todayProject = currProject;
860 |
861 | resetSelectedProject();
862 | resetProjects();
863 |
864 | renderProjects();
865 | renderTasksView(e);
866 | renderTasks(todayProject);
867 | currProject = todayProject;
868 | updateSelectedFilter();
869 | closeSideBarModal();
870 | }
871 | function showWeek(e) {
872 | resetFilters();
873 | updateAllTasks();
874 | updateWeekTasks();
875 | const weekProject = currProject;
876 |
877 | resetSelectedProject();
878 | resetProjects();
879 |
880 | renderProjects();
881 | renderTasksView(e);
882 | renderTasks(weekProject);
883 | currProject = weekProject;
884 | updateSelectedFilter();
885 | closeSideBarModal();
886 | }
887 |
888 | function toggleMobileFocus() {
889 | const header = document.querySelector('header');
890 | content.style.transition = '0.2s ease-out';
891 |
892 | if (titleInput.matches(':focus') || noteInput.matches(':focus')) {
893 | header.classList.add('header');
894 | content.classList.add('mobile-stretch');
895 | content.style.transition = '';
896 | } else {
897 | header.classList.remove('header');
898 | content.classList.remove('mobile-stretch');
899 | content.style.transition = '0.2s ease-out';
900 | }
901 | }
902 | function toggleSideBarFocus() {
903 | if (projectInput.matches(':focus')) {
904 | filters.classList.add('filtersHide');
905 | resetFilters();
906 | } else if (!addProjectBtn.matches(':active')) {
907 | document.querySelector('form').reset();
908 | projectForm.hidden = true;
909 | togglePlusBtn();
910 | resetProjects();
911 | renderProjects();
912 |
913 | const filtersArr = ['All', 'Starred', 'Today', 'Week'];
914 | for (let i = 0; i < filtersArr.length; i++) {
915 | if (currProject.name === filtersArr[i]) {
916 | updateSelectedFilter();
917 | break;
918 | } else {
919 | updateSelectedProject();
920 | }
921 | }
922 | filters.classList.remove('filtersHide');
923 | }
924 | }
925 | function toggleSideBarModal() {
926 | mobileMenu.classList.toggle('active');
927 |
928 | if (mobileMenu.classList.contains('active')) {
929 | sidebar.style.animation = '0.2s formRight ease-out';
930 | sidebar.style.display = 'flex';
931 | content.style.display = 'none';
932 | logo.classList.add('blurred');
933 | updateSelectedFilter();
934 |
935 | setTimeout(() => {
936 | resetMobileAnimations();
937 | }, 200);
938 | } else {
939 | sidebar.style.animation = '0.2s reverse formRight ease-out';
940 | logo.classList.remove('blurred');
941 |
942 | setTimeout(() => {
943 | content.style.display = 'block';
944 | sidebar.style.display = 'none';
945 | resetMobileAnimations();
946 | mobileMenu.checked = false;
947 | }, 100);
948 | }
949 | }
950 | function closeSideBarModal() {
951 | if (mobileMenu.classList.contains('active')) {
952 | toggleSideBarModal();
953 | }
954 | }
955 | function isMobileView() {
956 | if (window.clientWidth >= 480) {
957 | sidebar.style.display = 'flex';
958 | content.style.display = 'block';
959 | } else if (window.clientWidth < 480) {
960 | sidebar.style.display = 'none';
961 | content.style.display = 'block';
962 | mobileMenu.classList.remove('active');
963 | logo.classList.remove('blurred');
964 | mobileMenu.checked = false;
965 | }
966 | }
967 |
968 | function toggleTheme() {
969 | const theme = document.documentElement.getAttribute('data-theme');
970 | const newTheme = theme === 'dark' ? 'light' : 'dark';
971 | document.documentElement.setAttribute('data-theme', newTheme);
972 |
973 | const temp = themeIcon.textContent;
974 | themeIcon.textContent = temp === 'toggle_on' ? 'toggle_off' : 'toggle_on';
975 |
976 | updateColors();
977 | const date = document.querySelector('.open-date');
978 | if (date.textContent === 'Past Due') {
979 | date.style.color = '#E34A4A';
980 | } else {
981 | date.style.color = primaryColor;
982 | }
983 |
984 | updateSelectedProject();
985 | updateSelectedFilter();
986 | }
987 | function initIntro() {
988 | const introTask = new Task(
989 | 'Click me to learn more!',
990 | ' - Expand tasks to view additional details about them. \n\n - Write notes, add dates and star tasks from the form pane. \n\n - Thank you for checking out my project!',
991 | 'Default',
992 | '',
993 | true
994 | );
995 | const introTaskTwo = new Task(
996 | 'Sidebar Info',
997 | ' - Filter created tasks by All, Starred, Today or Week. \n\n - Add projects by clicking (+) and pressing Enter. \n\n - Hover over existing projects to edit or delete them.',
998 | 'Default',
999 | '',
1000 | true
1001 | );
1002 | const introProject = new Project('Default');
1003 | introProject.index = 0;
1004 | currProject = introProject;
1005 | projects.push(introProject);
1006 | introProject.getTasks().push(introTask);
1007 | introProject.getTasks().push(introTaskTwo);
1008 | }
1009 | function findProjects() {
1010 | if (!localStorage.getItem('data')) {
1011 | initIntro();
1012 | } else {
1013 | storage().getData();
1014 | }
1015 |
1016 | updateAllTasks();
1017 | currProject = allTasksList;
1018 | renderTasks(currProject);
1019 | updateSelectedFilter();
1020 | }
1021 | window.addEventListener('resize', isMobileView);
1022 | projectInput.addEventListener('focus', toggleSideBarFocus);
1023 | projectInput.addEventListener('blur', toggleSideBarFocus);
1024 | projectInput.addEventListener('touchend', toggleSideBarFocus);
1025 | mobileMenu.addEventListener('click', toggleSideBarModal);
1026 | titleInput.addEventListener('focus', toggleMobileFocus);
1027 | titleInput.addEventListener('blur', toggleMobileFocus);
1028 | titleInput.addEventListener('touchend', toggleMobileFocus);
1029 | noteInput.addEventListener('focus', toggleMobileFocus);
1030 | noteInput.addEventListener('blur', toggleMobileFocus);
1031 | noteInput.addEventListener('touchend', toggleMobileFocus);
1032 | themeIcon.addEventListener('click', toggleTheme);
1033 | selectAll.addEventListener('click', showAll);
1034 | selectStarred.addEventListener('click', showStarred);
1035 | selectToday.addEventListener('click', showToday);
1036 | selectWeek.addEventListener('click', showWeek);
1037 | addProjectBtn.addEventListener('click', toggleAddProject);
1038 | formStar.addEventListener('click', toggleFormStar);
1039 | addTaskBtn.addEventListener('click', renderFormView);
1040 | addBtn.addEventListener('click', (e) => {
1041 | addTask(e, currProject);
1042 | });
1043 | editBtn.addEventListener('click', (e) => {
1044 | editTask(e, currProject);
1045 | });
1046 | projectForm.addEventListener('keydown', (e) => {
1047 | if (e.key === 'Enter') {
1048 | e.preventDefault();
1049 | if (isProjectValid()) {
1050 | if (selected === '') {
1051 | addProject();
1052 | projectForm.hidden = true;
1053 | } else {
1054 | editProject();
1055 | togglePlusBtn();
1056 | selected.classList.toggle('edited');
1057 | selected = '';
1058 | }
1059 | }
1060 |
1061 | updateSelectedProject();
1062 | renderTasksView(e);
1063 | renderTasks(currProject);
1064 | resetForm();
1065 | }
1066 | });
1067 | projectForm.addEventListener('submit', (e) => {
1068 | e.preventDefault();
1069 | });
1070 | taskForm.addEventListener('keydown', (e) => {
1071 | if (e.key === 'Enter' && !e.shiftKey) {
1072 | e.preventDefault();
1073 | }
1074 | });
1075 |
1076 | document.addEventListener('DOMContentLoaded', (e) => {
1077 | findProjects();
1078 | renderProjects();
1079 | resetTasks();
1080 | renderTasks(currProject);
1081 | renderTasksView(e);
1082 | });
1083 | }
1084 |
--------------------------------------------------------------------------------
/src/modules/filter.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bartbzd/todo-list/f2daaf0fd95d33a674d2c48c5f7f20ec42586bbc/src/modules/filter.js
--------------------------------------------------------------------------------
/src/modules/models/projectModel.js:
--------------------------------------------------------------------------------
1 | export default class Project {
2 | constructor(name, tasks = []) {
3 | this.name = name;
4 | this.tasks = tasks;
5 | }
6 |
7 | getName() {
8 | return this.name;
9 | }
10 |
11 | getTasks() {
12 | return this.tasks;
13 | }
14 |
15 | removeTask(selectedTask) {
16 | // prettier-ignore
17 | const index = this.tasks.findIndex(
18 | (task) => task.title === selectedTask.title
19 | );
20 | if (index !== -1) {
21 | this.tasks.splice(index, 1);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/modules/models/storageModel.js:
--------------------------------------------------------------------------------
1 | import Task from './taskModel';
2 | import Project from './projectModel';
3 |
4 | export const projects = [];
5 | export const allTasksList = new Project('All');
6 |
7 | export default function storage() {
8 | let data = {
9 | projects,
10 | tasks: projects.flatMap((project) => project.getTasks()),
11 | all: allTasksList.getTasks(),
12 | };
13 |
14 | function saveData() {
15 | localStorage.setItem('data', JSON.stringify(data));
16 | }
17 |
18 | function getData() {
19 | const storedData = localStorage.getItem('data');
20 | data = JSON.parse(storedData);
21 |
22 | const storedProjects = data.projects.map((project) => {
23 | const storedTasks = project.tasks.map(
24 | (task) =>
25 | new Task(
26 | task.title,
27 | task.note,
28 | task.project,
29 | task.date,
30 | task.isStarred,
31 | task.isComplete
32 | )
33 | );
34 | return new Project(project.name, storedTasks);
35 | });
36 |
37 | const allTasks = data.all.map(
38 | (task) =>
39 | new Task(
40 | task.title,
41 | task.note,
42 | task.project,
43 | task.date,
44 | task.isStarred,
45 | task.isComplete
46 | )
47 | );
48 | data.projects = storedProjects;
49 | data.tasks = storedProjects.flatMap((project) => project.getTasks());
50 | data.all = allTasks;
51 |
52 | projects.length = 0;
53 | projects.push(...storedProjects);
54 | allTasksList.tasks.length = 0;
55 | allTasksList.tasks.push(...allTasks);
56 | }
57 |
58 | return {
59 | getData,
60 | saveData,
61 | get projects() {
62 | return projects;
63 | },
64 | get allTasksList() {
65 | return allTasksList;
66 | },
67 | };
68 | }
69 |
--------------------------------------------------------------------------------
/src/modules/models/taskModel.js:
--------------------------------------------------------------------------------
1 | export default class Task {
2 | constructor(title, note, project, date, isStarred = false, isComplete = false) {
3 | this.title = title;
4 | this.note = note;
5 | this.project = project;
6 | this.date = date;
7 | this.isStarred = isStarred;
8 | this.isComplete = isComplete;
9 | }
10 |
11 | getTitle() {
12 | return this.title;
13 | }
14 |
15 | getProject() {
16 | return this.project;
17 | }
18 |
19 | getIsStarred() {
20 | return this.isStarred;
21 | }
22 |
23 | getIsComplete() {
24 | return this.isComplete;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/modules/views/projectView.js:
--------------------------------------------------------------------------------
1 | import { projects } from '../models/storageModel';
2 |
3 | export default function createProject(project) {
4 | const div = () => document.createElement('div');
5 | const text = document.createElement('p');
6 | const icon = () => document.createElement('i');
7 |
8 | const wrapper = div();
9 | wrapper.className = 'project';
10 | wrapper.setAttribute('data-id', projects.indexOf(project));
11 | text.className = 'project-name';
12 | text.textContent = project.name;
13 | const folder = icon();
14 | folder.classList.add('folder', 'fa-regular', 'fa-folder');
15 | const group = div();
16 | group.className = 'project-btn-grp';
17 | const edit = icon();
18 | edit.classList.add('options', 'edit-p', 'material-symbols-rounded');
19 | edit.textContent = 'edit';
20 | const trash = icon();
21 | trash.classList.add('options', 'delete-p', 'material-symbols-rounded');
22 | trash.textContent = 'delete';
23 |
24 | const option = () => document.createElement('option');
25 | const pick = option();
26 | pick.value = project.name;
27 | pick.textContent = project.name;
28 |
29 | document.querySelector('select').appendChild(pick);
30 | document.querySelector('.project-grp').appendChild(wrapper);
31 | wrapper.append(folder, text, group);
32 | group.append(edit, trash);
33 | }
34 |
--------------------------------------------------------------------------------
/src/modules/views/taskView.js:
--------------------------------------------------------------------------------
1 | export default function createTask(task, project) {
2 | const taskWrapper = document.createElement('div');
3 | const checkWrapper = document.createElement('div');
4 | const input = document.createElement('input');
5 | const checkmark = document.createElement('i');
6 | const title = document.createElement('p');
7 |
8 | const actions = document.createElement('div');
9 | const edit = document.createElement('i');
10 | const trash = document.createElement('i');
11 | const star = document.createElement('i');
12 |
13 | taskWrapper.setAttribute('data-id', project.indexOf(task));
14 | taskWrapper.classList.add('task');
15 | checkWrapper.classList.add('checkmark');
16 | input.type = 'checkbox';
17 | input.classList.add('hide-check');
18 | if (task.isComplete) {
19 | checkmark.classList.add('fa-solid', 'fa-circle-check');
20 | } else checkmark.classList.add('fa-regular', 'fa-circle');
21 | checkmark.classList.add('check');
22 | title.classList.add('task-title');
23 | title.textContent = task.title;
24 | actions.classList.add('actions');
25 | edit.classList.add('options', 'edit', 'material-symbols-rounded');
26 | edit.textContent = 'edit';
27 | trash.classList.add('options', 'delete', 'material-symbols-rounded');
28 | trash.textContent = 'delete';
29 | trash.style.display = 'none';
30 | star.classList.add('fa-regular', 'fa-star');
31 |
32 | taskWrapper.append(checkWrapper, title, actions);
33 | checkWrapper.append(input, checkmark);
34 | actions.append(edit, trash, star);
35 |
36 | return taskWrapper;
37 | }
38 |
--------------------------------------------------------------------------------
/src/styles.scss:
--------------------------------------------------------------------------------
1 | html[data-theme='light'] {
2 | --bg-color: #e4f0fa;
3 | --primary: #ff7c7c;
4 | --dk-text: #2b3c5b;
5 | --dk-subtext: #7a8aa3;
6 | --card: #e6eef8;
7 | --component-s: #c4cdd1;
8 | --component: #d3dee3;
9 | --grad-base: rgb(250, 177, 135);
10 | --gradient-1: rgb(250, 177, 135);
11 | --gradient-2: rgb(246, 117, 117);
12 | --cal-indicator: invert(72%) sepia(40%) saturate(5103%) hue-rotate(314deg)
13 | brightness(110%) contrast(105%);
14 | }
15 | html[data-theme='dark'] {
16 | --bg-color: #121215;
17 | --primary: #8d8fd2;
18 | --dk-text: #dfe0fb;
19 | --dk-subtext: #84849d;
20 | --card: #18181c;
21 | --component-s: #454254;
22 | --component: #2f2d36;
23 | --grad-base: rgb(125, 90, 242);
24 | --gradient-1: rgb(145, 114, 247);
25 | --gradient-2: rgb(104, 61, 247);
26 | --cal-indicator: invert(57%) sepia(96%) saturate(184%) hue-rotate(200deg)
27 | brightness(85%) contrast(93%);
28 | }
29 |
30 | $bg-color: var(--bg-color);
31 | $primary: var(--primary);
32 | $dk-text: var(--dk-text);
33 | $dk-subtext: var(--dk-subtext);
34 | $card: var(--card);
35 | $component-s: var(--component-s);
36 | $component: var(--component);
37 | $grad-base: var(--grad-base);
38 | $gradient-1: var(--gradient-1);
39 | $gradient-2: var(--gradient-2);
40 | $cal-indicator: var(--cal-indicator);
41 | $transition: 0.2s ease-out;
42 |
43 | * {
44 | margin: 0;
45 | padding: 0;
46 | box-sizing: border-box;
47 | font-family: 'Lexend Deca', sans-serif;
48 | font-weight: 400;
49 | }
50 | html {
51 | height: -webkit-fill-available;
52 | }
53 | body {
54 | background-color: $bg-color;
55 | transition: $transition;
56 | // height: 100vh;
57 | min-height: 100vh;
58 | /* mobile viewport bug fix */
59 | min-height: -webkit-fill-available;
60 | h2,
61 | h3 {
62 | color: $primary;
63 | }
64 | h2 {
65 | font-size: 20px;
66 | }
67 | h3 {
68 | font-size: 16px;
69 | color: $dk-subtext;
70 | display: flex;
71 | align-items: end;
72 | height: 25px;
73 | }
74 | p {
75 | display: flex;
76 | }
77 | p,
78 | span,
79 | i,
80 | q {
81 | color: $dk-subtext;
82 | }
83 | input,
84 | textarea,
85 | select {
86 | border: none;
87 | border-radius: 10px;
88 | padding: 0 10px;
89 | outline-width: 0;
90 | background-color: $component;
91 | color: $dk-text;
92 | resize: none;
93 | transition: $transition;
94 | }
95 | input {
96 | height: 35px;
97 | &:focus {
98 | outline: none;
99 | }
100 | }
101 | textarea {
102 | height: 120px;
103 | padding-top: 10px;
104 | }
105 | select {
106 | height: 35px;
107 | width: 100%;
108 | padding: 0 10px;
109 | -webkit-appearance: none;
110 | -moz-appearance: none;
111 | appearance: none;
112 | user-select: none;
113 | }
114 | button {
115 | transition: $transition;
116 | }
117 | .material-symbols-rounded,
118 | .material-symbols-outlined {
119 | font-size: 18px;
120 | }
121 | .material-symbols-rounded {
122 | font-variation-settings: 'FILL' 1, 'wght' 300, 'GRAD' 200, 'opsz' 48;
123 | }
124 | }
125 | .container {
126 | min-height: 100vh;
127 | }
128 | header {
129 | height: 130px;
130 | display: flex;
131 | align-items: center;
132 | justify-content: center;
133 | padding: 0 10vw;
134 | transition: $transition;
135 | user-select: none;
136 | h1 {
137 | color: $dk-text;
138 | font-size: 48px;
139 | width: 285px;
140 | transition: $transition;
141 | }
142 | span {
143 | background: $grad-base;
144 | background: linear-gradient(130deg, $gradient-1 0%, $gradient-2 100%);
145 | background-clip: text;
146 | -webkit-background-clip: text;
147 | -webkit-text-fill-color: transparent;
148 | transition: $transition;
149 | }
150 | div {
151 | width: 510px;
152 | }
153 | }
154 | .hidden {
155 | display: none;
156 | }
157 | .side-menu {
158 | display: none;
159 | }
160 | .cards {
161 | display: flex;
162 | flex-wrap: nowrap;
163 | justify-content: center;
164 | gap: 24px;
165 | padding: 0 8vw;
166 | .options {
167 | &:hover {
168 | color: $primary;
169 | transform: scale(1.2);
170 | transition: $transition;
171 | }
172 | &:active {
173 | transform: scale(0.9);
174 | }
175 | }
176 | .filter,
177 | .project,
178 | .task,
179 | button,
180 | .fa-plus,
181 | .theme,
182 | .add-star {
183 | cursor: pointer;
184 | }
185 | .sidebar,
186 | .content {
187 | background-color: $card;
188 | border-radius: 24px;
189 | padding: 24px;
190 | height: 50vh;
191 | transition: 0.2s ease-out;
192 | box-shadow: rgba(0, 0, 0, 0.2) 0px 12px 28px 0px, rgba(0, 0, 0, 0.1) 0px 2px 4px 0px;
193 | }
194 | .section-header {
195 | margin-bottom: 16px;
196 | user-select: none;
197 | transition: $transition;
198 | }
199 | .sidebar {
200 | background-color: $card;
201 | width: 270px;
202 | display: flex;
203 | flex-direction: column;
204 | .filters {
205 | margin-bottom: 40px;
206 | }
207 | .filters-title-grp {
208 | display: flex;
209 | justify-content: space-between;
210 | }
211 | .theme {
212 | height: 26px;
213 | font-size: 24px;
214 | color: $component-s;
215 | user-select: none;
216 | transition: $transition;
217 | &:hover {
218 | transform: scale(1.03);
219 | color: $primary;
220 | }
221 | &:active {
222 | transform: scale(0.97);
223 | }
224 | }
225 | .filter {
226 | height: 32px;
227 | .fa-star {
228 | padding: 1px;
229 | width: 18px;
230 | }
231 | }
232 | .filter,
233 | .project {
234 | padding: 8px;
235 | margin-bottom: 8px;
236 | border-radius: 8px;
237 | user-select: none;
238 | i {
239 | margin-right: 6px;
240 | }
241 | p {
242 | user-select: none;
243 | pointer-events: none;
244 | }
245 | }
246 | .filter,
247 | .project-name {
248 | font-size: 14px;
249 | }
250 | .projects-header {
251 | display: flex;
252 | justify-content: space-between;
253 | overflow: hidden;
254 | i {
255 | height: 24px;
256 | line-height: 24px;
257 | font-size: 24px;
258 | transition: $transition;
259 | color: $component-s;
260 | }
261 | .plus:hover {
262 | color: $primary;
263 | transform: scale(1.1);
264 | }
265 | .rotated {
266 | transform: rotate(45deg);
267 | transition: $transition;
268 | &:hover {
269 | color: $primary;
270 | }
271 | }
272 | }
273 | .projects {
274 | flex: 1;
275 | display: flex;
276 | flex-direction: column;
277 | overflow: auto;
278 | }
279 | .project-grp {
280 | overflow-x: hidden;
281 | margin-bottom: 16px;
282 | width: 100%;
283 | i {
284 | font-size: 14px;
285 | }
286 | }
287 | .project {
288 | display: flex;
289 | height: 32px;
290 | padding: 8px;
291 | overflow: hidden;
292 | align-items: center;
293 | opacity: 1;
294 | transition: $transition;
295 | .folder {
296 | pointer-events: none;
297 | }
298 | .project-name {
299 | width: 10vw;
300 | overflow-x: hidden;
301 | }
302 | .project-btn-grp {
303 | margin-left: auto;
304 | display: flex;
305 | i {
306 | font-size: 16px;
307 | }
308 | .options {
309 | opacity: 0;
310 | }
311 | .edit-p {
312 | margin-right: 8px;
313 | }
314 | .delete-p {
315 | margin-right: 0px;
316 | }
317 | }
318 | &:hover {
319 | .project-btn-grp {
320 | .options {
321 | opacity: 1;
322 | }
323 | }
324 | }
325 | }
326 | #project-name {
327 | width: 100%;
328 | height: 32px;
329 | margin-bottom: 8px;
330 | }
331 | .github {
332 | height: 24px;
333 | display: flex;
334 | justify-content: center;
335 | margin-top: auto;
336 | .fa-github {
337 | font-size: 25px;
338 | transition: $transition;
339 | &:hover {
340 | color: $primary;
341 | }
342 | &:active {
343 | transform: scale(0.95);
344 | }
345 | }
346 | }
347 | }
348 |
349 | .content {
350 | width: 510px;
351 | overflow-x: hidden;
352 | .t-wrapper,
353 | .f-wrapper,
354 | .o-wrapper {
355 | height: 100%;
356 | }
357 | .t-wrapper {
358 | display: flex;
359 | flex-direction: column;
360 | .title-grp {
361 | display: flex;
362 | justify-content: space-between;
363 | .current-title {
364 | margin-top: 6px;
365 | margin-right: 12px;
366 | font-size: 14px;
367 | color: #454254;
368 | }
369 | }
370 |
371 | .no-tasks {
372 | color: $dk-subtext;
373 | display: flex;
374 | justify-content: center;
375 | margin-top: 16px;
376 | font-size: 18px;
377 | user-select: none;
378 | }
379 | .tasks {
380 | overflow-x: hidden;
381 | height: 35vh;
382 | padding: 2px;
383 | }
384 | .task {
385 | user-select: none;
386 | z-index: 0;
387 | border-radius: 10px;
388 | height: 35px;
389 | width: 99.5%;
390 | margin-bottom: 8px;
391 | display: flex;
392 | min-width: 0;
393 | padding-right: 10px;
394 | background-color: $component;
395 | box-shadow: rgba(0, 0, 0, 0.05) 0px 4px 4px 0px,
396 | rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;
397 | transition: $transition;
398 | .hide-check {
399 | display: none;
400 | }
401 | .checkmark {
402 | margin: auto 0;
403 | height: 32px;
404 | transition: $transition;
405 | &:active {
406 | transform: scale(0.8);
407 | }
408 | }
409 | .fa-circle,
410 | .fa-circle-check {
411 | height: 32px;
412 | padding: 0 14px;
413 | display: flex;
414 | align-items: center;
415 | }
416 | .task-title {
417 | display: block;
418 | height: 35px;
419 | width: 75%;
420 | padding-right: 8px;
421 | border: none;
422 | line-height: 35px;
423 | font-size: 14px;
424 | font-weight: 300;
425 | user-select: none;
426 | pointer-events: none;
427 | color: $dk-text;
428 | overflow: hidden;
429 | white-space: nowrap;
430 | text-overflow: ellipsis;
431 | }
432 |
433 | & input[type='checkbox'] {
434 | margin-right: 1vw;
435 | }
436 | div:last-of-type {
437 | margin-left: auto;
438 | }
439 | &:hover {
440 | background-color: $component-s;
441 | .actions {
442 | .edit,
443 | .fa-regular {
444 | transition: $transition;
445 | opacity: 1;
446 | }
447 | }
448 | }
449 | .actions {
450 | z-index: 2;
451 | display: flex;
452 | align-items: center;
453 | transition: $transition;
454 | gap: 4px;
455 | .options {
456 | height: 100%;
457 | display: flex;
458 | align-items: center;
459 | }
460 | i {
461 | transition: $transition;
462 | }
463 | .edit {
464 | opacity: 0;
465 | width: 30px;
466 | display: flex;
467 | justify-content: center;
468 | }
469 | span {
470 | display: flex;
471 | }
472 | .fa-regular {
473 | opacity: 0;
474 | }
475 |
476 | .fa-solid {
477 | color: $dk-subtext;
478 | }
479 | }
480 | }
481 | }
482 | .f-wrapper {
483 | display: none;
484 | .task-form {
485 | border-radius: 20px;
486 | width: 100%;
487 | display: flex;
488 | flex-direction: column;
489 | .form-header {
490 | margin-top: 24px;
491 | }
492 | #task {
493 | height: 36px;
494 | }
495 | #note {
496 | height: 15vh;
497 | padding: 10px;
498 | }
499 | .extras-wrapper {
500 | display: flex;
501 | justify-content: space-between;
502 | align-items: center;
503 | gap: 16px;
504 | }
505 | .extras {
506 | width: 45%;
507 | }
508 |
509 | input[type='date'] {
510 | width: 100%;
511 | padding: 0 5px 0 10px;
512 | user-select: none;
513 | &::-webkit-calendar-picker-indicator {
514 | font-size: 16px;
515 | transition: $transition;
516 | filter: $cal-indicator;
517 | }
518 | &::-webkit-calendar-picker-indicator:hover {
519 | scale: (1.1);
520 | filter: $cal-indicator;
521 | }
522 | }
523 |
524 | .fa-star {
525 | font-size: 24px;
526 | margin-top: 70px;
527 | margin-bottom: 6px;
528 | width: 30px;
529 | display: flex;
530 | justify-content: center;
531 | transition: $transition;
532 | color: $primary;
533 | &:hover {
534 | transform: scale(1.1);
535 | }
536 | &:active {
537 | transform: scale(0.9) rotate(72deg);
538 | }
539 | }
540 | .fa-regular {
541 | font-size: 20px;
542 | }
543 | .starred {
544 | transition: $transition;
545 | }
546 |
547 | .btn-group {
548 | margin-top: auto;
549 | display: flex;
550 | }
551 | .back-btn {
552 | background-color: $card;
553 | border: none;
554 | color: $dk-text;
555 | font-size: 30px;
556 | }
557 |
558 | .submit-btn {
559 | width: 130px;
560 | height: 40px;
561 | border-radius: 30px;
562 | border: none;
563 | margin-left: auto;
564 | font-size: 16px;
565 | color: $dk-text;
566 | background: $grad-base;
567 | background: linear-gradient(160deg, $gradient-1 0%, $gradient-2 100%);
568 | box-shadow: rgba(0, 0, 0, 0.15) 0px 5px 15px 0px;
569 | transition: $transition;
570 | user-select: none;
571 | &:hover {
572 | transform: scale(1.02);
573 | }
574 | &:active {
575 | transform: scale(0.97);
576 | }
577 | }
578 | }
579 | }
580 | .o-wrapper {
581 | display: none;
582 |
583 | .expand-view {
584 | display: flex;
585 | flex-direction: column;
586 | width: 100%;
587 | user-select: none;
588 | .expand-header {
589 | margin-bottom: 24px;
590 |
591 | .project-grp {
592 | display: flex;
593 | margin-top: 2px;
594 | i {
595 | font-size: 20px;
596 | margin-right: 6px;
597 | }
598 | p {
599 | font-size: 16px;
600 | }
601 | }
602 | }
603 |
604 | .open-title-header {
605 | display: flex;
606 | justify-content: space-between;
607 | margin: 8px 16px;
608 | transition: $transition;
609 | .fa-star {
610 | font-size: 24px;
611 | color: $primary;
612 | }
613 | }
614 | .note-wrapper {
615 | margin: 8px 16px 0 16px;
616 | }
617 | hr {
618 | height: 4px;
619 | background: $primary;
620 |
621 | border: none;
622 | border-radius: 2px;
623 | }
624 | #open-title {
625 | font-size: 18px;
626 | color: $dk-text;
627 | width: 90%;
628 | white-space: nowrap;
629 | overflow: hidden;
630 | text-overflow: ellipsis;
631 | }
632 | #open-note {
633 | color: $dk-subtext;
634 | display: block;
635 | font-size: 16px;
636 | white-space: pre-line;
637 | height: 27vh;
638 | overflow: auto;
639 | padding-top: 8px;
640 | &::-webkit-scrollbar-corner {
641 | color: transparent;
642 | }
643 | }
644 | .bot-note-line {
645 | display: none;
646 | width: 20px;
647 | margin-left: auto;
648 | margin-right: auto;
649 | margin-top: 14px;
650 | margin-bottom: 0;
651 | }
652 | .visible {
653 | display: block;
654 | }
655 | .extras-wrapper {
656 | margin-top: auto;
657 | padding: 0 16px 8px 8px;
658 | display: flex;
659 | justify-content: space-between;
660 | }
661 | .extras {
662 | display: flex;
663 | align-items: center;
664 | .open-date {
665 | color: $primary;
666 | font-weight: 500;
667 | }
668 | }
669 | .btn-group {
670 | margin-top: auto;
671 | .back-btn {
672 | background-color: $card;
673 | border: none;
674 | color: $dk-text;
675 | font-size: 30px;
676 | }
677 | }
678 | }
679 | }
680 | .add-btn {
681 | margin-top: auto;
682 | width: 130px;
683 | height: 40px;
684 | border-radius: 24px;
685 | border: none;
686 | margin-left: auto;
687 | font-size: 16px;
688 | color: $dk-text;
689 | background: $grad-base;
690 | background: linear-gradient(160deg, $gradient-1 0%, $gradient-2 100%);
691 | transition: $transition;
692 | box-shadow: rgba(0, 0, 0, 0.15) 5px 5px 15px 0px;
693 | user-select: none;
694 | &:hover {
695 | transform: scale(1.02);
696 | }
697 | &:active {
698 | transform: scale(0.98);
699 | }
700 | }
701 | }
702 | }
703 |
704 | .slide-tasks-in {
705 | animation: ease-out taskRight reverse 0.1s;
706 | }
707 | .slide-tasks-out {
708 | animation: ease-out taskRight 0.1s;
709 | }
710 | @keyframes taskRight {
711 | 0% {
712 | transform: translateX(0);
713 | }
714 | 100% {
715 | transform: translateX(120%);
716 | }
717 | }
718 | .slide-form-in {
719 | animation: ease-out formRight 0.1s;
720 | }
721 | .slide-form-out {
722 | animation: ease-out formRight reverse 0.1s;
723 | }
724 | @keyframes formRight {
725 | 0% {
726 | transform: translateX(-100%);
727 | }
728 | 100% {
729 | transform: translateX(0);
730 | }
731 | }
732 | @keyframes formVertical {
733 | 0% {
734 | transform: translateY(-80%);
735 | }
736 | 100% {
737 | transform: translateY(0);
738 | }
739 | }
740 | @keyframes formVerticall {
741 | 0% {
742 | transform: translateY(20%);
743 | }
744 | 100% {
745 | transform: translateY(0);
746 | }
747 | }
748 | @keyframes appearForm {
749 | 0% {
750 | transform: scale(0.95);
751 | opacity: 0;
752 | }
753 | 100% {
754 | transform: scale(1);
755 | opacity: 1;
756 | }
757 | }
758 |
759 | @media screen and (max-width: 480px) {
760 | .side-menu {
761 | display: flex;
762 | justify-content: flex-end;
763 | position: relative;
764 | width: 80px;
765 | height: 45px;
766 | cursor: pointer;
767 |
768 | .menu-icon {
769 | width: 40px;
770 | height: 45px;
771 | position: relative;
772 | cursor: pointer;
773 | z-index: 2;
774 | -webkit-touch-callout: none;
775 | position: absolute;
776 | opacity: 0;
777 | }
778 | .menu-grp {
779 | margin: auto;
780 | position: absolute;
781 | top: 0;
782 | right: 0;
783 | left: 0;
784 | bottom: 0;
785 | width: 25px;
786 | height: 13px;
787 | .menu-line {
788 | position: absolute;
789 | display: block;
790 | width: 25px;
791 | height: 3px;
792 | background: $component;
793 | background-color: $primary;
794 | border-radius: 1px;
795 | transition: all 0.2s cubic-bezier(0.1, 0.82, 0.76, 0.965);
796 | &:first-of-type {
797 | top: 0;
798 | }
799 | &:last-of-type {
800 | bottom: 0;
801 | }
802 | }
803 | }
804 | &.active,
805 | .menu-icon:checked + div {
806 | .menu-line {
807 | &:first-of-type {
808 | transform: rotate(45deg);
809 | top: 5px;
810 | }
811 | &:last-of-type {
812 | transform: rotate(-45deg);
813 | bottom: 5px;
814 | }
815 | }
816 | }
817 | }
818 | .blurred {
819 | filter: blur(4px);
820 | }
821 | html,
822 | body,
823 | .container {
824 | overflow: hidden;
825 | min-height: -webkit-fill-available;
826 | }
827 | header {
828 | height: 110px;
829 | }
830 | select {
831 | font-size: 11px;
832 | }
833 | input[type='date'] {
834 | font-size: 11px;
835 | }
836 | .cards .content .f-wrapper .task-form #task,
837 | select#projects,
838 | input[type='date'] {
839 | height: 32px;
840 | font-size: 12px;
841 | }
842 | .section-header {
843 | font-size: 18px;
844 | }
845 | .form-header,
846 | .form-title-header {
847 | font-size: 16px;
848 | }
849 |
850 | .cards {
851 | .sidebar {
852 | .filter,
853 | .project {
854 | font-size: 13px;
855 | }
856 | .projects-header {
857 | overflow-x: hidden;
858 | }
859 | .project {
860 | .project-name {
861 | font-size: 13px;
862 | width: 140px;
863 | }
864 | }
865 | }
866 | .content {
867 | .add-btn {
868 | font-size: 14px;
869 | }
870 | .f-wrapper {
871 | .task-form {
872 | .form-header {
873 | margin-top: 14px;
874 | margin-bottom: 10px;
875 | }
876 | .fa-star {
877 | margin-top: 50px;
878 | }
879 | #note {
880 | height: 20vh;
881 | font-size: 12px;
882 | }
883 | .extras-wrapper {
884 | margin-bottom: 20px;
885 | }
886 | .fa-regular {
887 | font-size: 24px;
888 | }
889 | }
890 | .expand-view {
891 | .expand-header {
892 | margin-bottom: 8px;
893 | .project-grp p {
894 | font-size: 14px;
895 | margin-top: 1px;
896 | }
897 | .project-grp i {
898 | font-size: 18px;
899 | }
900 | }
901 | }
902 | }
903 | .t-wrapper {
904 | .task {
905 | .task-title {
906 | font-size: 11px;
907 | }
908 | }
909 |
910 | .no-tasks {
911 | font-size: 16px;
912 | }
913 | }
914 | .o-wrapper {
915 | .expand-view {
916 | #open-title {
917 | width: 215px;
918 | font-size: 16px;
919 | margin-top: 4px;
920 | }
921 | #open-note {
922 | font-size: 14px;
923 | height: 35vh;
924 | }
925 | }
926 | }
927 | }
928 | .sidebar,
929 | .content {
930 | height: 70vh;
931 | }
932 | .mobile-stretch {
933 | transition: 0s;
934 | margin-top: 5vh;
935 | height: 60vh;
936 | }
937 | .sidebar {
938 | display: none;
939 | }
940 | }
941 | .header {
942 | opacity: 0;
943 | height: 0;
944 | }
945 | .filtersHide {
946 | display: none;
947 | }
948 | }
949 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: './src/index.js',
5 | output: {
6 | filename: 'main.js',
7 | path: path.resolve(__dirname, 'dist'),
8 | },
9 | module: {
10 | rules: [
11 | {
12 | test: /\.css$/i,
13 | use: ['style-loader', 'css-loader'],
14 | },
15 | {
16 | test: /\.s[ac]ss$/i,
17 | use: ['style-loader', 'css-loader', 'sass-loader'],
18 | },
19 | {
20 | test: /\.(png|svg|jpg|jpeg|gif)$/i,
21 | type: 'asset/resource',
22 | },
23 | ],
24 | },
25 | mode: 'development',
26 | devtool: 'inline-source-map',
27 | devServer: {
28 | static: './dist',
29 | },
30 | };
31 |
--------------------------------------------------------------------------------