/ˈtoʊdoʊ/ — A simple, local, and customizable list
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

More of an inital commit

+356 -4
+42 -2
index.html
··· 4 4 <title>todo</title> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 6 <link rel="stylesheet" type="text/css" href="./style.css"> 7 - <script src="./script.js"></script> 7 + <script src="./script.js" defer></script> 8 8 </head> 9 9 <body> 10 - <h1>Hello, World!</h1> 10 + <header> 11 + <div> 12 + <h3>todo</h3> 13 + <nav> 14 + <a href="./index.html">home</a> 15 + <a href="./archived.html">archived</a> 16 + <a href="./settings.html">settings</a> 17 + </nav> 18 + </div> 19 + <hr> 20 + </header> 21 + <div id="main"> 22 + <div> 23 + <button id="createTodo"> 24 + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> 25 + <g fill="none" stroke="currentColor" stroke-dasharray="16" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"> 26 + <path d="M5 12h14"> 27 + <animate fill="freeze" attributeName="stroke-dashoffset" dur="0.5s" values="16;0"/> 28 + </path> 29 + <path stroke-dashoffset="16" d="M12 5v14"> 30 + <animate fill="freeze" attributeName="stroke-dashoffset" begin="0.5s" dur="0.5s" to="0"/> 31 + </path> 32 + </g> 33 + </svg> 34 + </button> 35 + <div id="todos"></div> 36 + <button id="showCompleted"> 37 + <svg id="showCompleted_open" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> 38 + <path fill="currentColor" d="m7 10l5 5l5-5z" /> 39 + </svg> 40 + <svg id="showCompleted_close" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> 41 + <path fill="currentColor" d="m7 15l5-5l5 5z" /> 42 + </svg> 43 + <p id="showCompleted_text"><show>show</show><hide>hide</hide> completed</p> 44 + </button> 45 + <div> 46 + <div id="todos-completed" class="hidden"></div> 47 + </div> 48 + </div> 49 + </div> 50 + <footer></footer> 11 51 </body> 12 52 </html>
+189 -1
script.js
··· 1 - window.alert("Welcome!"); 1 + var todos = { 2 + uncompleted: [], 3 + completed: [] 4 + }; 5 + var archived = []; 6 + 7 + if (localStorage.getItem("todos")) { 8 + 9 + todos = JSON.parse(localStorage.getItem("todos")); 10 + } 11 + 12 + if (localStorage.getItem("archived")) { 13 + 14 + archived = JSON.parse(localStorage.getItem("archived")); 15 + } 16 + 17 + // TODO: URL sharing data override here... 18 + 19 + document.getElementById("createTodo").addEventListener("click", () => { 20 + 21 + todos.uncompleted = [ 22 + { 23 + description: "" 24 + } 25 + ].concat(todos.uncompleted); 26 + 27 + hydrateUncompleted(); 28 + backupData(); 29 + }); 30 + 31 + document.getElementById("showCompleted").addEventListener("click", () => { 32 + 33 + document.getElementById("todos-completed").classList.toggle("hidden"); 34 + resizeTextHeights(); 35 + }); 36 + 37 + const backupData = () => { 38 + 39 + localStorage.setItem("todos", JSON.stringify(todos)); 40 + localStorage.setItem("archived", JSON.stringify(archived)); 41 + } 42 + 43 + const todosElement = document.getElementById("todos"); 44 + const completedElement = document.getElementById("todos-completed"); 45 + 46 + const todoElement = (item, index, isCompleted) => { 47 + 48 + let completedText = (isCompleted) ? "completed" : "uncompleted"; 49 + 50 + return ` 51 + <todo id="todo-${completedText}-${index}"> 52 + <div id="todo-${completedText}-${index}-completion" class="completion"> 53 + ${(isCompleted) ? '<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'24\' height=\'24\' viewBox=\'0 0 24 24\'><path fill=\'currentColor\' d=\'m10.6 13.8l-2.15-2.15q-.275-.275-.7-.275t-.7.275t-.275.7t.275.7L9.9 15.9q.3.3.7.3t.7-.3l5.65-5.65q.275-.275.275-.7t-.275-.7t-.7-.275t-.7.275zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\' /></svg>' : '<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'24\' height=\'24\' viewBox=\'0 0 24 24\'><path fill=\'currentColor\' d=\'M12 20a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8m0-18A10 10 0 0 0 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2\' /></svg>'} 54 + </div> 55 + <div class="data"> 56 + <textarea id="todo-${completedText}-${index}-description">${item.description}</textarea> 57 + <div> 58 + <p id="todo-${completedText}-${index}-archive">archive</p> 59 + <p id="todo-${completedText}-${index}-delete">delete</p> 60 + </div> 61 + </div> 62 + </todo> 63 + `; 64 + } 65 + 66 + const resizeTextHeights = () => { 67 + 68 + for (const element of document.querySelectorAll("todo textarea")) { 69 + 70 + element.style.height = "0"; 71 + element.style.height = element.scrollHeight + "px"; 72 + } 73 + } 74 + 75 + const applyListeners = () => { 76 + 77 + resizeTextHeights(); 78 + 79 + for (var i = 0; i < todos.uncompleted.length; i++) { 80 + 81 + let index = i; // fixes scoping issue - what a disaster 82 + 83 + document.getElementById(`todo-uncompleted-${i}-description`).addEventListener("input", (event) => { 84 + 85 + event.target.style.height = "0"; 86 + event.target.style.height = event.target.scrollHeight + "px"; 87 + 88 + todos.uncompleted[index].description = event.target.value; 89 + 90 + backupData(); 91 + }); 92 + 93 + document.getElementById(`todo-uncompleted-${i}-completion`).addEventListener("click", (event) => { 94 + 95 + todos.completed = [ 96 + todos.uncompleted[index] 97 + ].concat(todos.completed); 98 + todos.uncompleted.splice(index, 1); 99 + 100 + hydrateTodos(); 101 + backupData(); 102 + }); 103 + 104 + document.getElementById(`todo-uncompleted-${i}-archive`).addEventListener("click", (event) => { 105 + 106 + archived = [ 107 + todos.uncompleted[index] 108 + ].concat(archived); 109 + todos.uncompleted.splice(index, 1); 110 + 111 + hydrateTodos(); 112 + backupData(); 113 + }); 114 + 115 + document.getElementById(`todo-uncompleted-${i}-delete`).addEventListener("click", (event) => { 116 + 117 + todos.uncompleted.splice(index, 1); 118 + 119 + hydrateTodos(); 120 + backupData(); 121 + }); 122 + } 123 + 124 + for (var i = 0; i < todos.completed.length; i++) { 125 + 126 + let index = i; // fixes scoping issue - what a disaster 127 + 128 + document.getElementById(`todo-completed-${i}-description`).addEventListener("input", (event) => { 129 + 130 + todos.completed[index].description = event.target.value; 131 + 132 + backupData(); 133 + }); 134 + 135 + document.getElementById(`todo-completed-${i}-completion`).addEventListener("click", (event) => { 136 + 137 + todos.uncompleted = [ 138 + todos.completed[index] 139 + ].concat(todos.uncompleted); 140 + todos.completed.splice(index, 1); 141 + 142 + hydrateTodos(); 143 + backupData(); 144 + }); 145 + 146 + document.getElementById(`todo-completed-${i}-archive`).addEventListener("click", (event) => { 147 + 148 + archived = [ 149 + todos.completed[index] 150 + ].concat(archived); 151 + todos.completed.splice(index, 1); 152 + 153 + hydrateTodos(); 154 + backupData(); 155 + }); 156 + 157 + document.getElementById(`todo-completed-${i}-delete`).addEventListener("click", (event) => { 158 + 159 + todos.completed.splice(index, 1); 160 + 161 + hydrateTodos(); 162 + backupData(); 163 + }); 164 + } 165 + } 166 + 167 + const hydrateUncompleted = () => { 168 + 169 + todosElement.innerHTML = todos.uncompleted 170 + .map((x, index) => todoElement(x, index, false)) 171 + .join(""); 172 + } 173 + 174 + const hydrateCompleted = () => { 175 + 176 + completedElement.innerHTML = todos.completed 177 + .map((x, index) => todoElement(x, index, true)) 178 + .join(""); 179 + } 180 + 181 + const hydrateTodos = () => { 182 + 183 + hydrateUncompleted(); 184 + hydrateCompleted(); 185 + 186 + applyListeners(); 187 + }; 188 + 189 + hydrateTodos();
+125 -1
style.css
··· 1 + :root { 2 + --text-primary: #000; 3 + --background-primary: #fff; 4 + --background-secondary: #eee; 5 + --background-tertiary: #ccc; 6 + } 7 + 1 8 body { 2 - background-color: gray; 9 + display: flex; 10 + flex-direction: column; 11 + align-items: center; 12 + color: var(--text-primary); 13 + background-color: var(--background-primary); 14 + font-family: Arial; 15 + } 16 + 17 + h3 { 18 + margin: 0; 19 + user-select: none; 20 + } 21 + 22 + header { 23 + width: 100%; 24 + max-width: 750px; 25 + & > div { 26 + display: flex; 27 + flex-wrap: wrap; 28 + justify-content: space-between; 29 + align-items: center; 30 + gap: 0.5rem; 31 + } 32 + & > hr { 33 + border: 1px solid var(--text-primary); 34 + } 35 + } 36 + 37 + nav { 38 + display: flex; 39 + flex-wrap: wrap; 40 + gap: 0.25rem; 41 + } 42 + 43 + #main { 44 + display: flex; 45 + width: 100%; 46 + justify-content: center; 47 + & > div, #todos, #todos-completed { 48 + width: 400px; 49 + display: flex; 50 + flex-direction: column; 51 + gap: 0.25rem; 52 + } 53 + } 54 + 55 + todo { 56 + background-color: var(--background-secondary); 57 + border-radius: 0.5rem; 58 + display: flex; 59 + /* align-items: center; */ 60 + padding: 0.5rem; 61 + gap: 0.5rem; 62 + & .completion { 63 + cursor: pointer; 64 + } 65 + & .data { 66 + flex-grow: 0.97; 67 + & textarea { 68 + width: 100%; 69 + resize: none; 70 + border: unset; 71 + border-radius: 0.25rem; 72 + &:focus-visible { 73 + outline: unset; 74 + } 75 + } 76 + & > div { 77 + margin-top: 0.25rem; 78 + display: flex; 79 + flex-wrap: wrap; 80 + justify-content: right; 81 + gap: 0.5rem; 82 + & > p { 83 + user-select: none; 84 + font-size: 0.9rem; 85 + margin: 0; 86 + cursor: pointer; 87 + &:hover { 88 + text-decoration: underline; 89 + } 90 + } 91 + } 92 + } 93 + } 94 + 95 + button { 96 + background-color: var(--background-secondary); 97 + border: unset; 98 + border-radius: 0.5rem; 99 + width: 100%; 100 + display: flex; 101 + align-items: center; 102 + justify-content: center; 103 + gap: 1rem; 104 + cursor: pointer; 105 + } 106 + 107 + a, a:visited, a:active { 108 + user-select: none; 109 + color: var(--text-primary); 110 + background-color: var(--background-secondary); 111 + text-decoration: none; 112 + padding: 0.25rem; 113 + border-radius: 0.5rem; 114 + } 115 + 116 + button:active, a:hover { 117 + background-color: var(--background-tertiary); 118 + } 119 + 120 + .hidden, 121 + div:has(> #todos-completed.hidden), 122 + #main div:has(#todos-completed.hidden) #showCompleted_close, 123 + #main div:not(:has(#todos-completed.hidden)) #showCompleted_open, 124 + #main div:has(#todos-completed.hidden) hide, 125 + #main div:not(:has(#todos-completed.hidden)) show { 126 + display: none; 3 127 }