<title>todo</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="./style.css">
- <script src="./script.js"></script>
+ <script src="./script.js" defer></script>
</head>
<body>
- <h1>Hello, World!</h1>
+ <header>
+ <div>
+ <h3>todo</h3>
+ <nav>
+ <a href="./index.html">home</a>
+ <a href="./archived.html">archived</a>
+ <a href="./settings.html">settings</a>
+ </nav>
+ </div>
+ <hr>
+ </header>
+ <div id="main">
+ <div>
+ <button id="createTodo">
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g fill="none" stroke="currentColor" stroke-dasharray="16" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+ <path d="M5 12h14">
+ <animate fill="freeze" attributeName="stroke-dashoffset" dur="0.5s" values="16;0"/>
+ </path>
+ <path stroke-dashoffset="16" d="M12 5v14">
+ <animate fill="freeze" attributeName="stroke-dashoffset" begin="0.5s" dur="0.5s" to="0"/>
+ </path>
+ </g>
+ </svg>
+ </button>
+ <div id="todos"></div>
+ <button id="showCompleted">
+ <svg id="showCompleted_open" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path fill="currentColor" d="m7 10l5 5l5-5z" />
+ </svg>
+ <svg id="showCompleted_close" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path fill="currentColor" d="m7 15l5-5l5 5z" />
+ </svg>
+ <p id="showCompleted_text"><show>show</show><hide>hide</hide> completed</p>
+ </button>
+ <div>
+ <div id="todos-completed" class="hidden"></div>
+ </div>
+ </div>
+ </div>
+ <footer></footer>
</body>
</html>
-window.alert("Welcome!");
+var todos = {
+ uncompleted: [],
+ completed: []
+};
+var archived = [];
+
+if (localStorage.getItem("todos")) {
+
+ todos = JSON.parse(localStorage.getItem("todos"));
+}
+
+if (localStorage.getItem("archived")) {
+
+ archived = JSON.parse(localStorage.getItem("archived"));
+}
+
+// TODO: URL sharing data override here...
+
+document.getElementById("createTodo").addEventListener("click", () => {
+
+ todos.uncompleted = [
+ {
+ description: ""
+ }
+ ].concat(todos.uncompleted);
+
+ hydrateUncompleted();
+ backupData();
+});
+
+document.getElementById("showCompleted").addEventListener("click", () => {
+
+ document.getElementById("todos-completed").classList.toggle("hidden");
+ resizeTextHeights();
+});
+
+const backupData = () => {
+
+ localStorage.setItem("todos", JSON.stringify(todos));
+ localStorage.setItem("archived", JSON.stringify(archived));
+}
+
+const todosElement = document.getElementById("todos");
+const completedElement = document.getElementById("todos-completed");
+
+const todoElement = (item, index, isCompleted) => {
+
+ let completedText = (isCompleted) ? "completed" : "uncompleted";
+
+ return `
+ <todo id="todo-${completedText}-${index}">
+ <div id="todo-${completedText}-${index}-completion" class="completion">
+ ${(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>'}
+ </div>
+ <div class="data">
+ <textarea id="todo-${completedText}-${index}-description">${item.description}</textarea>
+ <div>
+ <p id="todo-${completedText}-${index}-archive">archive</p>
+ <p id="todo-${completedText}-${index}-delete">delete</p>
+ </div>
+ </div>
+ </todo>
+ `;
+}
+
+const resizeTextHeights = () => {
+
+ for (const element of document.querySelectorAll("todo textarea")) {
+
+ element.style.height = "0";
+ element.style.height = element.scrollHeight + "px";
+ }
+}
+
+const applyListeners = () => {
+
+ resizeTextHeights();
+
+ for (var i = 0; i < todos.uncompleted.length; i++) {
+
+ let index = i; // fixes scoping issue - what a disaster
+
+ document.getElementById(`todo-uncompleted-${i}-description`).addEventListener("input", (event) => {
+
+ event.target.style.height = "0";
+ event.target.style.height = event.target.scrollHeight + "px";
+
+ todos.uncompleted[index].description = event.target.value;
+
+ backupData();
+ });
+
+ document.getElementById(`todo-uncompleted-${i}-completion`).addEventListener("click", (event) => {
+
+ todos.completed = [
+ todos.uncompleted[index]
+ ].concat(todos.completed);
+ todos.uncompleted.splice(index, 1);
+
+ hydrateTodos();
+ backupData();
+ });
+
+ document.getElementById(`todo-uncompleted-${i}-archive`).addEventListener("click", (event) => {
+
+ archived = [
+ todos.uncompleted[index]
+ ].concat(archived);
+ todos.uncompleted.splice(index, 1);
+
+ hydrateTodos();
+ backupData();
+ });
+
+ document.getElementById(`todo-uncompleted-${i}-delete`).addEventListener("click", (event) => {
+
+ todos.uncompleted.splice(index, 1);
+
+ hydrateTodos();
+ backupData();
+ });
+ }
+
+ for (var i = 0; i < todos.completed.length; i++) {
+
+ let index = i; // fixes scoping issue - what a disaster
+
+ document.getElementById(`todo-completed-${i}-description`).addEventListener("input", (event) => {
+
+ todos.completed[index].description = event.target.value;
+
+ backupData();
+ });
+
+ document.getElementById(`todo-completed-${i}-completion`).addEventListener("click", (event) => {
+
+ todos.uncompleted = [
+ todos.completed[index]
+ ].concat(todos.uncompleted);
+ todos.completed.splice(index, 1);
+
+ hydrateTodos();
+ backupData();
+ });
+
+ document.getElementById(`todo-completed-${i}-archive`).addEventListener("click", (event) => {
+
+ archived = [
+ todos.completed[index]
+ ].concat(archived);
+ todos.completed.splice(index, 1);
+
+ hydrateTodos();
+ backupData();
+ });
+
+ document.getElementById(`todo-completed-${i}-delete`).addEventListener("click", (event) => {
+
+ todos.completed.splice(index, 1);
+
+ hydrateTodos();
+ backupData();
+ });
+ }
+}
+
+const hydrateUncompleted = () => {
+
+ todosElement.innerHTML = todos.uncompleted
+ .map((x, index) => todoElement(x, index, false))
+ .join("");
+}
+
+const hydrateCompleted = () => {
+
+ completedElement.innerHTML = todos.completed
+ .map((x, index) => todoElement(x, index, true))
+ .join("");
+}
+
+const hydrateTodos = () => {
+
+ hydrateUncompleted();
+ hydrateCompleted();
+
+ applyListeners();
+};
+
+hydrateTodos();
+:root {
+ --text-primary: #000;
+ --background-primary: #fff;
+ --background-secondary: #eee;
+ --background-tertiary: #ccc;
+}
+
body {
- background-color: gray;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ color: var(--text-primary);
+ background-color: var(--background-primary);
+ font-family: Arial;
+}
+
+h3 {
+ margin: 0;
+ user-select: none;
+}
+
+header {
+ width: 100%;
+ max-width: 750px;
+ & > div {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: center;
+ gap: 0.5rem;
+ }
+ & > hr {
+ border: 1px solid var(--text-primary);
+ }
+}
+
+nav {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.25rem;
+}
+
+#main {
+ display: flex;
+ width: 100%;
+ justify-content: center;
+ & > div, #todos, #todos-completed {
+ width: 400px;
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ }
+}
+
+todo {
+ background-color: var(--background-secondary);
+ border-radius: 0.5rem;
+ display: flex;
+ /* align-items: center; */
+ padding: 0.5rem;
+ gap: 0.5rem;
+ & .completion {
+ cursor: pointer;
+ }
+ & .data {
+ flex-grow: 0.97;
+ & textarea {
+ width: 100%;
+ resize: none;
+ border: unset;
+ border-radius: 0.25rem;
+ &:focus-visible {
+ outline: unset;
+ }
+ }
+ & > div {
+ margin-top: 0.25rem;
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: right;
+ gap: 0.5rem;
+ & > p {
+ user-select: none;
+ font-size: 0.9rem;
+ margin: 0;
+ cursor: pointer;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+ }
+}
+
+button {
+ background-color: var(--background-secondary);
+ border: unset;
+ border-radius: 0.5rem;
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 1rem;
+ cursor: pointer;
+}
+
+a, a:visited, a:active {
+ user-select: none;
+ color: var(--text-primary);
+ background-color: var(--background-secondary);
+ text-decoration: none;
+ padding: 0.25rem;
+ border-radius: 0.5rem;
+}
+
+button:active, a:hover {
+ background-color: var(--background-tertiary);
+}
+
+.hidden,
+div:has(> #todos-completed.hidden),
+#main div:has(#todos-completed.hidden) #showCompleted_close,
+#main div:not(:has(#todos-completed.hidden)) #showCompleted_open,
+#main div:has(#todos-completed.hidden) hide,
+#main div:not(:has(#todos-completed.hidden)) show {
+ display: none;
}