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

Configure Feed

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

Finish up management logic, add importing

Amazingca a0a8ef51 40de8662

+279 -12
+28
archived.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <title>todo</title> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <link rel="stylesheet" type="text/css" href="./style.css"> 7 + <script src="./archived.js" defer></script> 8 + </head> 9 + <body> 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 + <div id="archived"></div> 24 + </div> 25 + </div> 26 + <footer></footer> 27 + </body> 28 + </html>
+107
archived.js
··· 1 + var todos = { 2 + uncompleted: [], 3 + completed: [] 4 + }; 5 + var archived = []; 6 + var theme = { 7 + colorScheme: "default", 8 + css: "" 9 + }; 10 + 11 + if (localStorage.getItem("todos")) { 12 + 13 + todos = JSON.parse(localStorage.getItem("todos")); 14 + } 15 + 16 + if (localStorage.getItem("archived")) { 17 + 18 + archived = JSON.parse(localStorage.getItem("archived")); 19 + } 20 + 21 + if (localStorage.getItem("theme")) { 22 + 23 + theme = JSON.parse(localStorage.getItem("theme")); 24 + } 25 + 26 + const backupData = () => { 27 + 28 + localStorage.setItem("todos", JSON.stringify(todos)); 29 + localStorage.setItem("archived", JSON.stringify(archived)); 30 + localStorage.setItem("theme", JSON.stringify(theme)); 31 + } 32 + 33 + const archivedElement = document.getElementById("archived"); 34 + 35 + const todoElement = (item, index) => { 36 + 37 + return ` 38 + <todo id="todo-archived-${index}"> 39 + <div class="data"> 40 + <textarea id="todo-archived-${index}-description">${item.description}</textarea> 41 + <div> 42 + <p id="todo-archived-${index}-restore">restore</p> 43 + <p id="todo-archived-${index}-delete">delete</p> 44 + </div> 45 + </div> 46 + </todo> 47 + `; 48 + } 49 + 50 + const resizeTextHeights = () => { 51 + 52 + for (const element of document.querySelectorAll("todo textarea")) { 53 + 54 + element.style.height = "0"; 55 + element.style.height = element.scrollHeight + "px"; 56 + } 57 + } 58 + 59 + const applyListeners = () => { 60 + 61 + resizeTextHeights(); 62 + 63 + for (var i = 0; i < archived.length; i++) { 64 + 65 + let index = i; // fixes scoping issue - what a disaster 66 + 67 + document.getElementById(`todo-archived-${i}-description`).addEventListener("input", (event) => { 68 + 69 + event.target.style.height = "0"; 70 + event.target.style.height = event.target.scrollHeight + "px"; 71 + 72 + archived[index].description = event.target.value; 73 + 74 + backupData(); 75 + }); 76 + 77 + document.getElementById(`todo-archived-${i}-restore`).addEventListener("click", (event) => { 78 + 79 + todos.uncompleted = [ 80 + archived[index] 81 + ].concat(todos.uncompleted); 82 + archived.splice(index, 1); 83 + 84 + hydrateTodos(); 85 + backupData(); 86 + }); 87 + 88 + document.getElementById(`todo-archived-${i}-delete`).addEventListener("click", (event) => { 89 + 90 + archived.splice(index, 1); 91 + 92 + hydrateTodos(); 93 + backupData(); 94 + }); 95 + } 96 + } 97 + 98 + const hydrateTodos = () => { 99 + 100 + archivedElement.innerHTML = archived 101 + .map((x, index) => todoElement(x, index)) 102 + .join(""); 103 + 104 + applyListeners(); 105 + }; 106 + 107 + hydrateTodos();
+1 -1
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" defer></script> 7 + <script src="./index.js" defer></script> 8 8 </head> 9 9 <body> 10 10 <header>
+31 -7
script.js index.js
··· 3 3 completed: [] 4 4 }; 5 5 var archived = []; 6 + var theme = { 7 + colorScheme: "default", 8 + css: "" 9 + }; 6 10 7 11 if (localStorage.getItem("todos")) { 8 12 ··· 14 18 archived = JSON.parse(localStorage.getItem("archived")); 15 19 } 16 20 17 - // TODO: URL sharing data override here... 21 + if (localStorage.getItem("theme")) { 22 + 23 + theme = JSON.parse(localStorage.getItem("theme")); 24 + } 25 + 26 + const backupData = () => { 27 + 28 + localStorage.setItem("todos", JSON.stringify(todos)); 29 + localStorage.setItem("archived", JSON.stringify(archived)); 30 + localStorage.setItem("theme", JSON.stringify(theme)); 31 + } 32 + 33 + if (window.location.hash.length > 0) { 34 + 35 + try { 36 + 37 + ({todos, archived, theme} = JSON.parse(atob(window.location.hash.substring(1)))); // using parenthesis around this is required because we're doing object literal destructuring without a declaration 38 + 39 + backupData(); 40 + } catch (e) { 41 + 42 + console.error(e); 43 + 44 + window.alert("The import URL provided was not valid!"); 45 + } 46 + } 18 47 19 48 document.getElementById("createTodo").addEventListener("click", () => { 20 49 ··· 25 54 ].concat(todos.uncompleted); 26 55 27 56 hydrateUncompleted(); 57 + applyListeners(); 28 58 backupData(); 29 59 }); 30 60 ··· 33 63 document.getElementById("todos-completed").classList.toggle("hidden"); 34 64 resizeTextHeights(); 35 65 }); 36 - 37 - const backupData = () => { 38 - 39 - localStorage.setItem("todos", JSON.stringify(todos)); 40 - localStorage.setItem("archived", JSON.stringify(archived)); 41 - } 42 66 43 67 const todosElement = document.getElementById("todos"); 44 68 const completedElement = document.getElementById("todos-completed");
+36
settings.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <title>todo</title> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <link rel="stylesheet" type="text/css" href="./style.css"> 7 + <script src="./settings.js" defer></script> 8 + </head> 9 + <body> 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="exportData"> 24 + <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"> 25 + <path fill="currentColor" d="M6 22q-.825 0-1.412-.587T4 20V10q0-.825.588-1.412T6 8h2q.425 0 .713.288T9 9t-.288.713T8 10H6v10h12V10h-2q-.425 0-.712-.288T15 9t.288-.712T16 8h2q.825 0 1.413.588T20 10v10q0 .825-.587 1.413T18 22zm5.288-6.288Q11 15.425 11 15V4.825l-.9.9q-.3.3-.7.288T8.7 5.7q-.275-.3-.287-.7t.287-.7l2.6-2.6q.15-.15.325-.212T12 1.425t.375.063t.325.212l2.6 2.6q.275.275.275.688T15.3 5.7q-.3.3-.712.3t-.713-.3L13 4.825V15q0 .425-.288.713T12 16t-.712-.288" /> 26 + </svg> 27 + <p id="exportData_text">export as link</p> 28 + </button> 29 + <h3>theming</h3> 30 + <h3>override css</h3> 31 + <textarea id="override_css"></textarea> 32 + </div> 33 + </div> 34 + <footer></footer> 35 + </body> 36 + </html>
+70
settings.js
··· 1 + var todos = { 2 + uncompleted: [], 3 + completed: [] 4 + }; 5 + var archived = []; 6 + var theme = { 7 + colorScheme: "default", 8 + css: "" 9 + }; 10 + 11 + if (localStorage.getItem("todos")) { 12 + 13 + todos = JSON.parse(localStorage.getItem("todos")); 14 + } 15 + 16 + if (localStorage.getItem("archived")) { 17 + 18 + archived = JSON.parse(localStorage.getItem("archived")); 19 + } 20 + 21 + if (localStorage.getItem("theme")) { 22 + 23 + theme = JSON.parse(localStorage.getItem("theme")); 24 + } 25 + 26 + const exportDataText = document.getElementById("exportData_text"); 27 + 28 + document.getElementById("exportData").addEventListener("click", async (event) => { 29 + 30 + const dataExport = { 31 + todos: todos, 32 + archived: archived, 33 + theme: theme 34 + }; 35 + 36 + const exportURL = window.location.href.replace("settings.html", "") + "index.html#" + btoa(JSON.stringify(dataExport)); 37 + 38 + if (!window.navigator.userAgent.includes("Mozilla")) { 39 + 40 + try { 41 + 42 + await navigator.share({ 43 + title: "todo data export", 44 + url: exportURL 45 + }); 46 + 47 + exportDataText.innerHTML = "shared!"; 48 + setTimeout(() => { 49 + exportDataText.innerHTML = "export as link"; 50 + }, 2000); 51 + } catch (e) { 52 + 53 + console.error(e); 54 + } 55 + } else { 56 + 57 + navigator.clipboard.writeText(exportURL); 58 + 59 + exportDataText.innerHTML = "link copied!"; 60 + setTimeout(() => { 61 + exportDataText.innerHTML = "export as link"; 62 + }, 2000); 63 + } 64 + }); 65 + 66 + const override_css = document.getElementById("override_css"); 67 + 68 + const update = () => { 69 + 70 + }
+6 -4
style.css
··· 16 16 17 17 h3 { 18 18 margin: 0; 19 - user-select: none; 20 19 } 21 20 22 21 header { ··· 44 43 display: flex; 45 44 width: 100%; 46 45 justify-content: center; 47 - & > div, #todos, #todos-completed { 46 + & > div, #todos, #todos-completed, #archived { 48 47 width: 400px; 49 48 display: flex; 50 49 flex-direction: column; ··· 80 79 justify-content: right; 81 80 gap: 0.5rem; 82 81 & > p { 83 - user-select: none; 82 + /* user-select: none; */ 84 83 font-size: 0.9rem; 85 84 margin: 0; 86 85 cursor: pointer; ··· 104 103 cursor: pointer; 105 104 } 106 105 106 + #override_css { 107 + resize: vertical; 108 + } 109 + 107 110 a, a:visited, a:active { 108 - user-select: none; 109 111 color: var(--text-primary); 110 112 background-color: var(--background-secondary); 111 113 text-decoration: none;