Template
1
Fork 0
todo-list/app.js
Leonardo Devai 259fde814c Review and modernize all 42 projects to the updated standard
Pinned 2026 toolchains (Go 1.26, Rust 1.98/edition 2024, Python 3.14 + uv, Node 24,
Zig 0.16, NixOS 26.05), postgres 18 / mongo 8, lockfiles built from, non-root
runtimes, .dockerignore, per-project LICENSE, READMEs with the git.devai.io clone
line, checkout@v7 CI. Security fixes in the legacy Rust APIs (any-password login,
self-assigned admin, hard-coded JWT secret), JWT alg/exp/sub enforcement across the
blog series, safe markdown links in the frontends, and many smaller bugs — every
project was built, run and exercised end to end.

Adds scripts/publish.sh + a CI publish job that splits every folder into its own
repo at git.devai.io/templates/<folder>.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01128fhuZbgivaSJvtMf4s1G
2026-09-27 21:10:38 +02:00

69 lines
2 KiB
JavaScript

const form = document.getElementById("form");
const input = document.getElementById("input");
const list = document.getElementById("list");
const countEl = document.getElementById("count");
// The single source of truth: an array of {text, done} objects. The page is
// only ever a drawing of this array. localStorage keeps it between visits, but
// it can only hold text, so we store the array as JSON and parse it back.
let tasks = JSON.parse(localStorage.getItem("tasks") || "[]");
function save() {
localStorage.setItem("tasks", JSON.stringify(tasks));
}
// Change the array, save, render: every action below follows those three steps.
// render() throws the old list away and draws a fresh one from `tasks`, so the
// screen can never drift out of sync with the data.
function render() {
list.replaceChildren();
for (const task of tasks) {
const li = document.createElement("li");
li.className = task.done ? "item done" : "item";
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = task.done;
checkbox.setAttribute("aria-label", task.text);
checkbox.addEventListener("change", () => {
task.done = checkbox.checked;
save();
render();
});
const span = document.createElement("span");
span.className = "text";
span.textContent = task.text;
const del = document.createElement("button");
del.className = "del";
del.type = "button";
del.textContent = "✕";
del.setAttribute("aria-label", `Delete "${task.text}"`);
del.addEventListener("click", () => {
tasks = tasks.filter((t) => t !== task);
save();
render();
});
li.append(checkbox, span, del);
list.append(li);
}
const left = tasks.filter((t) => !t.done).length;
countEl.textContent = `${left} left`;
}
form.addEventListener("submit", (event) => {
event.preventDefault();
const text = input.value.trim();
if (!text) return;
tasks.push({ text, done: false });
input.value = "";
save();
render();
});
render();