├── README.md ├── css └── main.css ├── img └── favicon.ico ├── index.html └── js └── main.js /README.md: -------------------------------------------------------------------------------- 1 | ## .preview 2 | ![To-Do List App](https://i.ibb.co/h73STxF/screencapture-localhost-5500-2020-11-29-22-11-39.png) 3 | 4 | ## .features 5 | - [x] Show tasks list 6 | - [x] Add new task 7 | - [ ] Complete task 8 | - [x] Delete task 9 | - [x] Edit task 10 | - [x] Clear all tasks 11 | - [ ] Clear complete tasks 12 | - [x] Save on local storage 13 | - [x] Reverse sort by id 14 | - [x] Show message if not task 15 | - [x] Show toast when add, delete task 16 | - [x] Change alert box to modal 17 | - [x] Custom scrollbar 18 | - [x] Responsivity 19 | 20 | ## .tools 21 | - Css Framework: [Semantic UI](https://semantic-ui.com) 22 | - Toast or Notification: [Noty](https://ned.im/noty) 23 | 24 | ## .contact 25 | - Email: [dev.mehdineysi@gmail.com](mailto:dev.mehdineysi@gmail.com) 26 | - Instagram: [@neysidev](https://instagram.com/neysidev) 27 | - Twitter: [@neysidev](https://twitter.com/neysidev) 28 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | /* Variables */ 2 | :root { 3 | --dark: #1b1c1d; 4 | --blue: #2185d0; 5 | --green: #1ebc30; 6 | --red: #db2828; 7 | --white: #ffffff; 8 | --gray: #babac0; 9 | --secondary: #fbfdff; 10 | } 11 | 12 | /* Default Settings */ 13 | * { 14 | padding: 0; 15 | margin: 0; 16 | box-sizing: border-box; 17 | } 18 | body { 19 | color: var(--dark); 20 | background: var(--secondary); 21 | font-family: 'Lato', sans-serif; 22 | } 23 | .ui.grid + .grid { 24 | margin-top: 0; 25 | } 26 | 27 | /* Classes */ 28 | body::-webkit-scrollbar { 29 | background-color: var(--secondary); 30 | width: 16px; 31 | } 32 | body::-webkit-scrollbar-track { 33 | background-color: var(--secondary); 34 | } 35 | body::-webkit-scrollbar-thumb { 36 | background-color: var(--gray); 37 | border-radius: 16px; 38 | border: 4px solid var(--white); 39 | } 40 | 41 | /* Header */ 42 | .main-header { 43 | color: var(--white); 44 | background: var(--blue); 45 | padding: 1.5rem 0; 46 | text-align: center; 47 | } 48 | 49 | /* Add Task */ 50 | #add-task { 51 | width: 70%; 52 | margin: 0 auto; 53 | margin-top: 2rem; 54 | } 55 | #add-task input { 56 | margin: 1.5rem auto; 57 | } 58 | 59 | /* Buttons */ 60 | #buttons { 61 | width: 70%; 62 | margin: 0 auto; 63 | margin-bottom: 3rem; 64 | } 65 | 66 | /* Tasks List */ 67 | #tasks-list { 68 | margin: 0 auto; 69 | width: 70%; 70 | list-style: none; 71 | } 72 | #tasks-list li { 73 | width: 100%; 74 | } 75 | #tasks-list li .remove, 76 | #tasks-list li .edit { 77 | opacity: 0; 78 | transition: all 0.3s ease; 79 | } 80 | #tasks-list li:hover .remove, 81 | #tasks-list li:hover .edit { 82 | opacity: 1; 83 | } 84 | #tasks-list li .column:last-child { 85 | text-align: right; 86 | } 87 | #tasks-list li i { 88 | cursor: pointer !important; 89 | } 90 | 91 | /* Alert */ 92 | .alert { 93 | width: 70%; 94 | margin: 0 auto; 95 | margin-bottom: 1rem; 96 | text-align: center; 97 | padding: 0.75rrem 1.25rrem; 98 | border-radius: 10px; 99 | line-height: 1.6rem; 100 | } 101 | .alert-warning { 102 | color: #856404; 103 | background-color: #fff3cd; 104 | border-color: #ffeeba; 105 | } 106 | 107 | /* Noty */ 108 | .noty_theme__metroui.noty_bar .noty_body { 109 | background: var(--green); 110 | color: var(--white); 111 | border-radius: 0.28571429rem; 112 | box-shadow: 0 0 0 1px var(--green) inset, 0 0 0 0 transparent; 113 | } 114 | .noty_theme__metroui.noty_type__error { 115 | background: none; 116 | } 117 | .noty_theme__metroui.noty_bar.noty_type__error .noty_body { 118 | background: var(--red); 119 | color: var(--white); 120 | border-radius: 0.28571429rem !important; 121 | box-shadow: 0 0 0 1px var(--red) inset, 0 0 0 0 transparent; 122 | } 123 | 124 | /* Responsive */ 125 | @media screen and (max-width: 770px) { 126 | #add-task, 127 | #buttons, 128 | #tasks-list { 129 | width: 100%; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/neysidev/todo-list/28551eaf2b0faea4af5500b5fd0e43247ee150f5/img/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | To-Do List App 5 | 6 | 7 | 8 | 9 | 10 | 14 | 20 | 26 | 27 | 28 | 29 | 30 |
31 |
32 |

To-Do List App

33 |
34 |
35 | 36 | 37 |
38 | 39 |
40 | 46 | 47 |
48 | 49 | 50 | 51 |
52 | 59 | 66 |
67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 90 | 91 | 92 | 93 | 109 | 110 | 111 | 112 | 132 | 133 | 134 | 135 | 155 | 156 |
157 | 158 | 159 | 164 | 169 | 174 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | // Elements 2 | const tasksList = document.querySelector("#tasks-list") 3 | const addTaskForm = document.querySelector("form#add-task") 4 | const addTaskInput = document.querySelector("#add-task-input") 5 | const clearAllTasksBtn = document.querySelector("button#clear-all-tasks") 6 | 7 | // Total List Of Tasks 8 | let list = JSON.parse(localStorage.getItem("tasks")) || [] 9 | 10 | /** 11 | * Show All Tasks From Local Storage In Page 12 | */ 13 | function showTasksList() { 14 | tasksList.innerHTML = "" 15 | const list = JSON.parse(localStorage.getItem("tasks")) || [] 16 | 17 | if (list.length === 0) { 18 | clearAllTasksBtn.disabled = true 19 | 20 | const element = String.raw` 21 |
22 | 23 |
24 |
You have nothing task today!
25 |
Enter your tasks today above.
26 |
27 |
28 | ` 29 | 30 | tasksList.style.border = "none" 31 | return tasksList.insertAdjacentHTML("beforeend", element) 32 | } 33 | 34 | clearAllTasksBtn.disabled = false 35 | tasksList.style.border = "1px solid rgba(34,36,38,.15)" 36 | list.reverse().forEach(task => { 37 | const element = String.raw` 38 |
  • 39 |
    40 | 41 | 42 |
    43 |
    44 | 45 | 46 |
    47 |
  • 48 | ` 49 | 50 | tasksList.insertAdjacentHTML("beforeend", element) 51 | }) 52 | 53 | document.querySelectorAll(`li i.edit`).forEach(item => { 54 | item.addEventListener("click", e => { 55 | e.stopPropagation() 56 | showEditModal(+e.target.dataset.id) 57 | }) 58 | }) 59 | 60 | document.querySelectorAll(`li i.trash`).forEach(item => { 61 | item.addEventListener("click", e => { 62 | e.stopPropagation() 63 | showRemoveModal(+e.target.dataset.id) 64 | }) 65 | }) 66 | } 67 | 68 | /** 69 | * Add new task to local storage 70 | */ 71 | function addTask(event) { 72 | event.preventDefault() 73 | 74 | const taskText = addTaskInput.value 75 | if (taskText.trim().length === 0) { 76 | return (addTaskInput.value = "") 77 | } 78 | 79 | list.push({ 80 | id: list.length + 1, 81 | text: taskText, 82 | completed: false, 83 | }) 84 | localStorage.setItem("tasks", JSON.stringify(list)) 85 | addTaskInput.value = "" 86 | 87 | showNotification("success", "Task was successfully added") 88 | showTasksList() 89 | } 90 | 91 | // Change Complete State 92 | function completeTask(id) { 93 | // Get Task 94 | const taskIndex = list.findIndex(t => t.id == id) 95 | const task = list[taskIndex] 96 | 97 | // Change State 98 | task.completed = !task.completed 99 | list[taskIndex] = task 100 | 101 | // Save Changes 102 | localStorage.setItem("tasks", JSON.stringify(list)) 103 | showTasksList() 104 | } 105 | 106 | /** 107 | * Remove task 108 | */ 109 | function removeTask(id) { 110 | list = list.filter(t => t.id !== id) 111 | localStorage.setItem("tasks", JSON.stringify(list)) 112 | 113 | showNotification("error", "Task was successfully deleted") 114 | showTasksList() 115 | } 116 | 117 | /** 118 | * Edit task 119 | */ 120 | function editTask(id) { 121 | const taskText = document.querySelector("#task-text").value 122 | 123 | if (taskText.trim().length === 0) return 124 | const taskIndex = list.findIndex(t => t.id == id) 125 | 126 | list[taskIndex].text = taskText 127 | localStorage.setItem("tasks", JSON.stringify(list)) 128 | 129 | showNotification("success", "Task was successfully updated") 130 | showTasksList() 131 | } 132 | 133 | // Clear All Tasks 134 | function clearAllTasks() { 135 | if (list.length > 0) { 136 | list = [] 137 | localStorage.setItem("tasks", JSON.stringify(list)) 138 | return showTasksList() 139 | } 140 | 141 | new Noty({ 142 | type: "error", 143 | text: ' There is no task to remove.', 144 | layout: "bottomRight", 145 | timeout: 2000, 146 | progressBar: true, 147 | closeWith: ["click"], 148 | theme: "metroui", 149 | }).show() 150 | } 151 | 152 | // Clear Complete Tasks 153 | function clearCompleteTasks() { 154 | if (list.length > 0) { 155 | if (confirm("Are you sure?")) { 156 | const filteredTasks = list.filter(t => t.completed !== true) 157 | localStorage.setItem("tasks", JSON.stringify(filteredTasks)) 158 | return showTasksList() 159 | } 160 | } 161 | 162 | Toastify({ 163 | text: "There is no task to remove", 164 | duration: 3000, 165 | close: true, 166 | gravity: "bottom", 167 | position: "left", 168 | backgroundColor: "linear-gradient(to right, #e45757, #d44747)", 169 | stopOnFocus: true, 170 | }).showToast() 171 | } 172 | 173 | // Show Edit Modal And Pass Data 174 | function showEditModal(id) { 175 | const taskIndex = list.findIndex(t => t.id == id) 176 | const { text } = list[taskIndex] 177 | 178 | document.querySelector("#edit-modal .content #task-id").value = id 179 | document.querySelector("#edit-modal .content #task-text").value = text.trim() 180 | document 181 | .querySelector("#update-button") 182 | .addEventListener("click", () => editTask(+id)) 183 | 184 | $("#edit-modal.modal").modal("show") 185 | } 186 | 187 | // Show Remove Modal 188 | function showRemoveModal(id) { 189 | document 190 | .querySelector("#remove-button") 191 | .addEventListener("click", () => removeTask(+id)) 192 | 193 | $("#remove-modal.modal").modal("show") 194 | } 195 | 196 | // Show Clear All Tasks Modal 197 | function showClearAllTasksModal() { 198 | if (list.length > 0) { 199 | return $("#clear-all-tasks-modal.modal").modal("show") 200 | } 201 | 202 | new Noty({ 203 | type: "error", 204 | text: ' There is no task to remove.', 205 | layout: "bottomRight", 206 | timeout: 2000, 207 | progressBar: true, 208 | closeWith: ["click"], 209 | theme: "metroui", 210 | }).show() 211 | } 212 | 213 | function showNotification(type, text) { 214 | new Noty({ 215 | type, 216 | text: ` ${text}`, 217 | layout: "bottomRight", 218 | timeout: 2000, 219 | progressBar: true, 220 | closeWith: ["click"], 221 | theme: "metroui", 222 | }).show() 223 | } 224 | 225 | // Event Listeners 226 | addTaskForm.addEventListener("submit", addTask) 227 | window.addEventListener("load", () => addTaskInput.focus()) 228 | 229 | showTasksList() 230 | --------------------------------------------------------------------------------