Sort elements as they are added

This commit is contained in:
Cameron Reed 2024-08-19 16:02:45 -06:00
parent ab4226a4b2
commit 45ecbcbe77
7 changed files with 341 additions and 114 deletions

View File

@ -12,6 +12,7 @@ import (
)
func main() {
db_path := flag.String("db", "./test.db", "Path to the sqlite3 database")
bind_port := flag.Int("p", 8080, "Port to bind to")
bind_addr := flag.String("a", "0.0.0.0", "Address to bind to")
noFront := flag.Bool("no-frontend", false, "Disable the frontend endpoints")
@ -34,7 +35,7 @@ func main() {
addBackendEndpoints(mux)
}
db.Open("test.db")
db.Open(*db_path)
defer db.Close()
addr := fmt.Sprintf("%s:%d", *bind_addr, *bind_port)

View File

@ -1,14 +1,10 @@
package pages
import (
"fmt"
"net/http"
"strconv"
"time"
"github.com/Cameron-Reed1/todo-web/db"
"github.com/Cameron-Reed1/todo-web/pages/templates"
"github.com/Cameron-Reed1/todo-web/types"
)
func OverdueFragment(w http.ResponseWriter, r *http.Request) {
@ -18,7 +14,7 @@ func OverdueFragment(w http.ResponseWriter, r *http.Request) {
return
}
templates.TodoList(items).Render(r.Context(), w)
templates.TodoList("No overdue items", items).Render(r.Context(), w)
}
func TodayFragment(w http.ResponseWriter, r *http.Request) {
@ -28,7 +24,7 @@ func TodayFragment(w http.ResponseWriter, r *http.Request) {
return
}
templates.TodoList(items).Render(r.Context(), w)
templates.TodoList("No items for today", items).Render(r.Context(), w)
}
func UpcomingFragment(w http.ResponseWriter, r *http.Request) {
@ -38,85 +34,5 @@ func UpcomingFragment(w http.ResponseWriter, r *http.Request) {
return
}
templates.TodoList(items).Render(r.Context(), w)
}
func CreateItem(w http.ResponseWriter, r *http.Request) {
var todo types.Todo
var err error
todo.Text = r.FormValue("name")
start := r.FormValue("start")
due := r.FormValue("due")
if start != "" {
start_time, err := time.Parse("2006-01-02T03:04", start)
if err != nil {
fmt.Printf("Bad start time: %s\n", start)
w.WriteHeader(http.StatusBadRequest)
return
}
todo.Start = start_time.Unix()
} else {
todo.Start = 0
}
if due != "" {
due_time, err := time.Parse("2006-01-02T15:04", due)
if err != nil {
fmt.Printf("Bad due time: %s\n", due)
w.WriteHeader(http.StatusBadRequest)
return
}
todo.Due = due_time.Unix()
} else {
todo.Due = 0
}
fmt.Printf("New item: %s: %d - %d\n", todo.Text, todo.Start, todo.Due)
err = db.AddTodo(&todo)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write([]byte{})
}
func DeleteItem(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if idStr == "" || err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
err = db.DeleteTodo(id)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write([]byte{})
}
func SetItemCompleted(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if idStr == "" || err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
completed := r.FormValue("completed") == "on"
if err = db.SetCompleted(id, completed); err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write([]byte{})
templates.TodoList("No upcoming items", items).Render(r.Context(), w)
}

View File

@ -16,6 +16,7 @@ templ RootPage() {
<link rel="stylesheet" href="/css/styles.css"/>
<script src="/js/script.js"></script>
<script src="/js/lib/htmx.min.js"></script>
<!-- Font Awesome -->
@ -38,18 +39,21 @@ templ RootPage() {
<div id="overdue-list" class="todo-list">
<div class="todo-list-title">Overdue</div>
<div class="todo-list-items" hx-get="/overdue" hx-trigger="load" hx-swap="outerHTML"></div>
<div class="new-item"></div>
</div>
<div id="today-list" class="todo-list">
<div class="todo-list-title">Today</div>
<div class="todo-list-items" hx-get="/today" hx-trigger="load" hx-swap="outerHTML"></div>
<div class="new-item"></div>
</div>
<div id="upcoming-list" class="todo-list">
<div class="todo-list-title">Upcoming</div>
<div class="todo-list-items" hx-get="/upcoming" hx-trigger="load" hx-swap="outerHTML"></div>
<div class="new-item"></div>
</div>
</div>
<form id="create-item" hx-post="/new" hx-swap="none" hx-push-url="#">
<form id="create-item" hx-post="/new" hx-swap="none">
<div id="create-item-title">Create new Todo</div>
<div id="create-item-form-container">
<div class="create-item-form-column">
@ -60,17 +64,17 @@ templ RootPage() {
<br/>
<label for="start">Start</label>
<br/>
<input name="start" type="datetime-local"/>
<input id="create-item-form-start" name="start" type="datetime-local"/>
<br/>
<label for="due">Due</label>
<br/>
<input name="due" type="datetime-local"/>
<input id="create-item-form-due" name="due" type="datetime-local"/>
</div>
</div>
<div class="create-item-form-column"></div>
</div>
<div id="create-item-button-container">
<button id="create-item-save-button" class="button" type="submit" onclick="setTimeout(() => window.location = window.location.origin, 100)">Save</button>
<button id="create-item-save-button" class="button" type="submit">Save</button>
<a id="create-item-close-button" class="button" href="#">Close</a>
</div>
</form>
@ -80,7 +84,7 @@ templ RootPage() {
}
templ TodoItem(item types.Todo) {
<div class="todo-item">
<div class="todo-item" data-start={ fmt.Sprintf("%d", item.Start) } data-due={ fmt.Sprintf("%d", item.Due) }>
<input type="checkbox" name="completed" checked?={ item.Completed } hx-patch={ string(templ.URL(fmt.Sprintf("/set/%d", item.Id))) }/>
<div class="todo-text">{ item.Text }</div>
<div class="todo-item-actions">
@ -90,8 +94,15 @@ templ TodoItem(item types.Todo) {
</div>
}
templ TodoList(items []types.Todo) {
<div class="todo-list-items">
templ OobTodoItem(targetSelector string, item types.Todo) {
<div hx-swap-oob={ fmt.Sprintf("%s:%s", "afterend", targetSelector) } >
@TodoItem(item)
</div>
}
templ TodoList(fillerText string, items []types.Todo) {
<div class="todo-list-items" data-item-count={ fmt.Sprintf("%d", len(items)) }>
<div class="filler-item">{ fillerText }</div>
for _, item := range items {
@TodoItem(item)
}

View File

@ -29,7 +29,7 @@ func RootPage() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype HTML><html lang=\"en-US\"><head><title>Todo</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link rel=\"stylesheet\" href=\"/css/styles.css\"><script src=\"/js/lib/htmx.min.js\"></script><!-- Font Awesome --><script src=\"https://kit.fontawesome.com/469cdddb31.js\" crossorigin=\"anonymous\"></script></head><body><nav><div id=\"nav-container\"><div id=\"nav-left\"><a id=\"new-button\" href=\"#create-item\">New</a></div><div id=\"nav-center\"></div><div id=\"nav-right\"></div></div></nav><div id=\"main-content\"><div id=\"lists\"><div id=\"overdue-list\" class=\"todo-list\"><div class=\"todo-list-title\">Overdue</div><div class=\"todo-list-items\" hx-get=\"/overdue\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div></div><div id=\"today-list\" class=\"todo-list\"><div class=\"todo-list-title\">Today</div><div class=\"todo-list-items\" hx-get=\"/today\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div></div><div id=\"upcoming-list\" class=\"todo-list\"><div class=\"todo-list-title\">Upcoming</div><div class=\"todo-list-items\" hx-get=\"/upcoming\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div></div></div><form id=\"create-item\" hx-post=\"/new\" hx-swap=\"none\" hx-push-url=\"#\"><div id=\"create-item-title\">Create new Todo</div><div id=\"create-item-form-container\"><div class=\"create-item-form-column\"><div class=\"create-item-form-column\"><label for=\"name\">Name</label><br><input type=\"text\" name=\"name\"><br><label for=\"start\">Start</label><br><input name=\"start\" type=\"datetime-local\"><br><label for=\"due\">Due</label><br><input name=\"due\" type=\"datetime-local\"></div></div><div class=\"create-item-form-column\"></div></div><div id=\"create-item-button-container\"><button id=\"create-item-save-button\" class=\"button\" type=\"submit\" onclick=\"setTimeout(() =&gt; window.location = window.location.origin, 100)\">Save</button> <a id=\"create-item-close-button\" class=\"button\" href=\"#\">Close</a></div></form></div></body></html>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype HTML><html lang=\"en-US\"><head><title>Todo</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link rel=\"stylesheet\" href=\"/css/styles.css\"><script src=\"/js/script.js\"></script><script src=\"/js/lib/htmx.min.js\"></script><!-- Font Awesome --><script src=\"https://kit.fontawesome.com/469cdddb31.js\" crossorigin=\"anonymous\"></script></head><body><nav><div id=\"nav-container\"><div id=\"nav-left\"><a id=\"new-button\" href=\"#create-item\">New</a></div><div id=\"nav-center\"></div><div id=\"nav-right\"></div></div></nav><div id=\"main-content\"><div id=\"lists\"><div id=\"overdue-list\" class=\"todo-list\"><div class=\"todo-list-title\">Overdue</div><div class=\"todo-list-items\" hx-get=\"/overdue\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div><div id=\"today-list\" class=\"todo-list\"><div class=\"todo-list-title\">Today</div><div class=\"todo-list-items\" hx-get=\"/today\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div><div id=\"upcoming-list\" class=\"todo-list\"><div class=\"todo-list-title\">Upcoming</div><div class=\"todo-list-items\" hx-get=\"/upcoming\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div></div><form id=\"create-item\" hx-post=\"/new\" hx-swap=\"none\"><div id=\"create-item-title\">Create new Todo</div><div id=\"create-item-form-container\"><div class=\"create-item-form-column\"><div class=\"create-item-form-column\"><label for=\"name\">Name</label><br><input type=\"text\" name=\"name\"><br><label for=\"start\">Start</label><br><input id=\"create-item-form-start\" name=\"start\" type=\"datetime-local\"><br><label for=\"due\">Due</label><br><input id=\"create-item-form-due\" name=\"due\" type=\"datetime-local\"></div></div><div class=\"create-item-form-column\"></div></div><div id=\"create-item-button-container\"><button id=\"create-item-save-button\" class=\"button\" type=\"submit\">Save</button> <a id=\"create-item-close-button\" class=\"button\" href=\"#\">Close</a></div></form></div></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -53,7 +53,33 @@ func TodoItem(item types.Todo) templ.Component {
templ_7745c5c3_Var2 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"todo-item\"><input type=\"checkbox\" name=\"completed\"")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"todo-item\" data-start=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.Start))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 87, Col: 69}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" data-due=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.Due))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 87, Col: 110}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><input type=\"checkbox\" name=\"completed\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -67,12 +93,12 @@ func TodoItem(item types.Todo) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(string(templ.URL(fmt.Sprintf("/set/%d", item.Id))))
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(string(templ.URL(fmt.Sprintf("/set/%d", item.Id))))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 84, Col: 137}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 88, Col: 137}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -80,12 +106,12 @@ func TodoItem(item types.Todo) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(item.Text)
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(item.Text)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 85, Col: 42}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 89, Col: 42}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -93,12 +119,12 @@ func TodoItem(item types.Todo) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/delete/%d", item.Id))
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/delete/%d", item.Id))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 88, Col: 101}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 92, Col: 101}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -113,7 +139,7 @@ func TodoItem(item types.Todo) templ.Component {
})
}
func TodoList(items []types.Todo) templ.Component {
func OobTodoItem(targetSelector string, item types.Todo) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
@ -121,12 +147,83 @@ func TodoList(items []types.Todo) templ.Component {
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var6 := templ.GetChildren(ctx)
if templ_7745c5c3_Var6 == nil {
templ_7745c5c3_Var6 = templ.NopComponent
templ_7745c5c3_Var8 := templ.GetChildren(ctx)
if templ_7745c5c3_Var8 == nil {
templ_7745c5c3_Var8 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"todo-list-items\">")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div hx-swap-oob=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s:%s", "afterend", targetSelector))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 98, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = TodoItem(item).Render(ctx, templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if !templ_7745c5c3_IsBuffer {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W)
}
return templ_7745c5c3_Err
})
}
func TodoList(fillerText string, items []types.Todo) templ.Component {
return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) {
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer)
if !templ_7745c5c3_IsBuffer {
templ_7745c5c3_Buffer = templ.GetBuffer()
defer templ.ReleaseBuffer(templ_7745c5c3_Buffer)
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
if templ_7745c5c3_Var10 == nil {
templ_7745c5c3_Var10 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"todo-list-items\" data-item-count=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(items)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 104, Col: 80}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><div class=\"filler-item\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fillerText)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 105, Col: 45}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

101
pages/webapi.go Normal file
View File

@ -0,0 +1,101 @@
package pages
import (
"fmt"
"net/http"
"strconv"
"time"
"github.com/Cameron-Reed1/todo-web/db"
"github.com/Cameron-Reed1/todo-web/pages/templates"
"github.com/Cameron-Reed1/todo-web/types"
)
func CreateItem(w http.ResponseWriter, r *http.Request) {
var todo types.Todo
var err error
todo.Text = r.FormValue("name")
start := r.FormValue("start")
due := r.FormValue("due")
fmt.Printf("Create item request: %s: %s - %s\n", todo.Text, start, due)
if start != "" {
todo.Start, err = strconv.ParseInt(start, 10, 64)
if err != nil {
fmt.Printf("Bad start time: %s\n", start)
w.WriteHeader(http.StatusBadRequest)
return
}
} else {
todo.Start = 0
}
if due != "" {
todo.Due, err = strconv.ParseInt(due, 10, 64)
if err != nil {
fmt.Printf("Bad due time: %s\n", due)
w.WriteHeader(http.StatusBadRequest)
return
}
} else {
todo.Due = 0
}
fmt.Printf("New item: %s: %d - %d\n", todo.Text, todo.Start, todo.Due)
err = db.AddTodo(&todo)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
now := time.Now().Unix()
var targetSelector = "#today-list > .new-item"
if todo.Due != 0 && todo.Due < now {
targetSelector = "#overdue-list > .new-item"
}
if todo.Start > now {
targetSelector = "#upcoming-list > .new-item"
}
templates.OobTodoItem(targetSelector, todo).Render(r.Context(), w)
}
func DeleteItem(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if idStr == "" || err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
err = db.DeleteTodo(id)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write([]byte{})
}
func SetItemCompleted(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if idStr == "" || err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}
completed := r.FormValue("completed") == "on"
if err = db.SetCompleted(id, completed); err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Write([]byte{})
}

View File

@ -212,3 +212,19 @@ nav {
color: blue;
border-color: blue;
}
.new-item, .filler-item {
display: none;
}
[data-item-count="0"]
.filler-item {
display: block;
width: calc(100% - 12px);
flex: 1;
text-align: center;
font-weight: bold;
font-size: 1.5rem;
line-height: 40px;
margin: 6px;
}

85
static/js/script.js Normal file
View File

@ -0,0 +1,85 @@
document.addEventListener("keyup", (event) => {
if (event.key === "Escape") {
window.location.hash = '';
}
});
function on_load() {
let start_input = document.getElementById("create-item-form-start");
let due_input = document.getElementById("create-item-form-due");
let create_form = document.getElementById("create-item");
create_form.addEventListener("htmx:configRequest", function(evt) {
evt.detail.parameters["start"] = start_input.value ? start_input.valueAsNumber / 1000 : 0;
evt.detail.parameters["due"] = due_input.value ? due_input.valueAsNumber / 1000 : 0;
});
create_form.addEventListener("htmx:afterRequest", function(evt) {
if (evt.detail.successful) {
window.location.hash = '';
evt.detail.elt.reset();
}
});
document.querySelector("#overdue-list > .new-item").addEventListener("htmx:oobBeforeSwap", function(evt) {
let overdue_items = document.querySelector("#overdue-list > .todo-list-items");
let due = parseInt(evt.detail.fragment.firstElementChild.getAttribute("data-due"));
let target = overdue_items.children[overdue_items.children.length - 1];
for (let i = 1; i < overdue_items.children.length; i++) {
if (parseInt(overdue_items.children[i].getAttribute("data-due")) > due) {
target = overdue_items.children[i - 1];
break;
}
}
evt.detail.target = target;
overdue_items.setAttribute("data-item-count", parseInt(overdue_items.getAttribute("data-item-count")) + 1);
});
document.querySelector("#today-list > .new-item").addEventListener("htmx:oobBeforeSwap", function(evt) {
let today_items = document.querySelector("#today-list > .todo-list-items");
let due = parseInt(evt.detail.fragment.firstElementChild.getAttribute("data-due"));
let target = today_items.children[today_items.children.length - 1];
if (due !== 0) {
for (let i = 1; i < today_items.children.length; i++) {
if (parseInt(today_items.children[i].getAttribute("data-due")) > due) {
target = today_items.children[i - 1];
break;
}
}
}
evt.detail.target = target;
today_items.setAttribute("data-item-count", parseInt(today_items.getAttribute("data-item-count")) + 1);
});
document.querySelector("#upcoming-list > .new-item").addEventListener("htmx:oobBeforeSwap", function(evt) {
let upcoming_items = document.querySelector("#upcoming-list > .todo-list-items");
let start = parseInt(evt.detail.fragment.firstElementChild.getAttribute("data-start"));
let target = upcoming_items.children[0];
for (let i = 1; i < upcoming_items.children.length; i++) {
if (parseInt(upcoming_items.children[i].getAttribute("data-start")) > start) {
target = upcoming_items.children[i - 1];
break;
}
}
evt.detail.target = target;
upcoming_items.setAttribute("data-item-count", parseInt(upcoming_items.getAttribute("data-item-count")) + 1);
});
}
if (document.readyState === "completed") {
on_load();
} else {
document.addEventListener("DOMContentLoaded", on_load);
}