1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* This is a minimalist to-do list app using server-side rendering without any client-side JavaScript.
* It uses Val Town's SQLite for data persistence, Deno's standard library for HTML escaping,
* and custom SVG icons for a cohesive design.
*/
import { escapeHtml } from "https://deno.land/x/escape_html/mod.ts";
// Server-side only code
export default async function server(request: Request): Promise<Response> {
const { sqlite } = await import("https://esm.town/v/stevekrouse/sqlite");
const SCHEMA_VERSION = 1
const KEY = new URL(import.meta.url).pathname.split("/").at(-1);
// Create the todos table if it doesn't exist
await sqlite.execute(`
CREATE TABLE IF NOT EXISTS ${KEY}_todos_${SCHEMA_VERSION} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
completed BOOLEAN NOT NULL DEFAULT 0
)
`);
const url = new URL(request.url);
// Handle form submissions
if (request.method === "POST") {
const formData = await request.formData();
if (formData.get("action") === "add") {
const content = formData.get("content");
if (content) {
await sqlite.execute(`INSERT INTO ${KEY}_todos_${SCHEMA_VERSION} (content) VALUES (?)`, [content]);
}
} else if (formData.get("action") === "toggle") {
const id = formData.get("id");
await sqlite.execute(`UPDATE ${KEY}_todos_${SCHEMA_VERSION} SET completed = NOT completed WHERE id = ?`, [id]);
} else if (formData.get("action") === "delete") {
const id = formData.get("id");
await sqlite.execute(`DELETE FROM ${KEY}_todos_${SCHEMA_VERSION} WHERE id = ?`, [id]);
}
// Redirect to clear the form submission
return new Response(null, { status: 302, headers: { Location: url.pathname }});
}
// Fetch todos from the database
const todos = await sqlite.execute(`SELECT * FROM ${KEY}_todos_${SCHEMA_VERSION} ORDER BY id DESC`);
// Custom SVG icons
const checkboxIcon = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="2" width="20" height="20" rx="5" stroke="currentColor" stroke-width="2"/>
<path d="M7 13L10 16L17 9" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>`;
const trashIcon = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 6H5H21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 6V4C8 3.46957 8.21071 2.96086 8.58579 2.58579C8.96086 2.21071 9.46957 2 10 2H14C14.5304 2 15.0391 2.21071 15.4142 2.58579C15.7893 2.96086 16 3.46957 16 4V6M19 6V20C19 20.5304 18.7893 21.0391 18.4142 21.4142C18.0391 21.7893 17.5304 22 17 22H
</svg>`;
// Generate HTML for the todo list
const todoListHtml = todos.rows.map(todo => `
<li class="${todo.completed ? 'completed' : ''}">
<span>${escapeHtml(todo.content)}</span>
<div class="actions">
<form method="POST">
<input type="hidden" name="action" value="toggle">
<input type="hidden" name="id" value="${todo.id}">
<button type="submit" class="icon-button toggle">${checkboxIcon}</button>
</form>
<form method="POST">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="id" value="${todo.id}">
<button type="submit" class="icon-button delete">${trashIcon}</button>
</form>
</div>
</li>
`).join('');
// Render the full HTML page
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>To Do List</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
background-color: #ffffff;
color: #000000;
}
h1 {
color: #000000;
text-align: center;
margin-bottom: 30px;
font-weight: 700;