Patch the day list in place instead of navigating
Kirjaa now works like the catalog: opening a day, picking a dish, saving, deleting, cancelling and "Näytä lisää" all patch the list where it stands. Nothing loads a page, so the scroll position never moves. One builder serves all three paths. buildLog takes what the screen should show — the day, an open dish, whether the entry is being changed or a delete confirmed — and the page render, the patch and the post-write response all go through it. After a save it is called with only the date, so the day comes back closed rather than reopening the sides step it was just submitted from. Links stay links and forms stay forms, with data-on:click__prevent and data-on:submit__prevent layered over them, so it all still works with JavaScript off. Each response patches a single element, so plain text/html is enough here; the SSE writer is only needed by the catalog, where the list and both forms have to move together. The anchors added in the previous attempt are gone. They could never have worked: the browser positions an anchor without knowing where the page was scrolled, so it jumped regardless.
This commit is contained in:
+64
-32
@@ -98,17 +98,57 @@ type logView struct {
|
|||||||
Confirming bool
|
Confirming bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logOptions is what the Kirjaa screen is being asked to show. Pulled out of
|
||||||
|
// the request for a page load or a patch, and set directly after a write,
|
||||||
|
// where the answer is simply "that day, nothing else open".
|
||||||
|
type logOptions struct {
|
||||||
|
Date time.Time
|
||||||
|
Dish string // ?ruoka=, opening the sides step
|
||||||
|
Changing bool // ?muuta=, swapping the dish on a logged day
|
||||||
|
Confirming bool // ?poista=, asking before deleting the entry
|
||||||
|
Search string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) logOptionsFrom(r *http.Request) logOptions {
|
||||||
|
q := r.URL.Query()
|
||||||
|
return logOptions{
|
||||||
|
Date: a.date(r),
|
||||||
|
Dish: q.Get("ruoka"),
|
||||||
|
Changing: q.Get("muuta") != "",
|
||||||
|
Confirming: q.Get("poista") != "",
|
||||||
|
Search: strings.TrimSpace(q.Get("haku")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *app) index(w http.ResponseWriter, r *http.Request) {
|
func (a *app) index(w http.ResponseWriter, r *http.Request) {
|
||||||
date := a.date(r)
|
render(w, r, logPage(a.buildLog(r, a.logOptionsFrom(r))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// day patches the list in place. Every link in it calls here rather than
|
||||||
|
// loading a page, so opening a day leaves the scroll position alone.
|
||||||
|
func (a *app) day(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fragment(w, r, dayList(a.buildLog(r, a.logOptionsFrom(r))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// finishDay answers a write: a patch for Datastar, a redirect otherwise.
|
||||||
|
func (a *app) finishDay(w http.ResponseWriter, r *http.Request, date time.Time) {
|
||||||
|
if isDatastar(r) {
|
||||||
|
fragment(w, r, dayList(a.buildLog(r, logOptions{Date: date})))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
a.redirectToDay(w, r, date)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *app) buildLog(r *http.Request, o logOptions) logView {
|
||||||
|
date := o.Date
|
||||||
v := logView{
|
v := logView{
|
||||||
Date: date,
|
Date: date,
|
||||||
Today: today(a.loc),
|
Today: today(a.loc),
|
||||||
Search: strings.TrimSpace(r.URL.Query().Get("haku")),
|
Search: o.Search,
|
||||||
Checked: map[int64]bool{},
|
Checked: map[int64]bool{},
|
||||||
|
Confirming: o.Confirming,
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Confirming = r.URL.Query().Get("poista") != ""
|
|
||||||
|
|
||||||
entry, err := entryFor(a.db, date)
|
entry, err := entryFor(a.db, date)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("entry for %s: %v", date.Format(dateLayout), err)
|
log.Printf("entry for %s: %v", date.Format(dateLayout), err)
|
||||||
@@ -118,8 +158,8 @@ func (a *app) index(w http.ResponseWriter, r *http.Request) {
|
|||||||
// ?ruoka= opens the sides step for that dish. When it is the dish already
|
// ?ruoka= opens the sides step for that dish. When it is the dish already
|
||||||
// logged, the existing sides come back ticked, which makes editing an
|
// logged, the existing sides come back ticked, which makes editing an
|
||||||
// entry the same screen as creating one.
|
// entry the same screen as creating one.
|
||||||
if raw := r.URL.Query().Get("ruoka"); raw != "" {
|
if o.Dish != "" {
|
||||||
if id, err := strconv.ParseInt(raw, 10, 64); err == nil {
|
if id, err := strconv.ParseInt(o.Dish, 10, 64); err == nil {
|
||||||
if dish, err := dishByID(a.db, id); err == nil {
|
if dish, err := dishByID(a.db, id); err == nil {
|
||||||
v.Chosen = dish
|
v.Chosen = dish
|
||||||
if entry != nil && entry.Main.ID == id {
|
if entry != nil && entry.Main.ID == id {
|
||||||
@@ -134,7 +174,7 @@ func (a *app) index(w http.ResponseWriter, r *http.Request) {
|
|||||||
// The board shows when there is nothing logged yet, or when the entry is
|
// The board shows when there is nothing logged yet, or when the entry is
|
||||||
// being changed. "Muokkaa" on a logged day sets ?muuta=1 and lands here,
|
// being changed. "Muokkaa" on a logged day sets ?muuta=1 and lands here,
|
||||||
// so swapping the dish and picking one for the first time are one path.
|
// so swapping the dish and picking one for the first time are one path.
|
||||||
changing := r.URL.Query().Get("muuta") != "" || v.Search != ""
|
changing := o.Changing || v.Search != ""
|
||||||
if v.Chosen == nil && (v.Entry == nil || changing) {
|
if v.Chosen == nil && (v.Entry == nil || changing) {
|
||||||
v.ShowBoard = true
|
v.ShowBoard = true
|
||||||
if v.Dishes, err = listDishes(a.db, v.Search); err != nil {
|
if v.Dishes, err = listDishes(a.db, v.Search); err != nil {
|
||||||
@@ -157,7 +197,7 @@ func (a *app) index(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a.loadDays(r, &v)
|
a.loadDays(r, &v)
|
||||||
render(w, r, logPage(v))
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadDays fills the day list. The selected day expands inside it rather than
|
// loadDays fills the day list. The selected day expands inside it rather than
|
||||||
@@ -344,6 +384,12 @@ func (a *app) quickAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Printf("quick add: %v", err)
|
log.Printf("quick add: %v", err)
|
||||||
form.Err = "Tallennus epäonnistui."
|
form.Err = "Tallennus epäonnistui."
|
||||||
default:
|
default:
|
||||||
|
// Created: straight on to its sides step.
|
||||||
|
opts := logOptions{Date: date, Dish: strconv.FormatInt(id, 10)}
|
||||||
|
if isDatastar(r) {
|
||||||
|
fragment(w, r, dayList(a.buildLog(r, opts)))
|
||||||
|
return
|
||||||
|
}
|
||||||
a.redirectToPick(w, r, date, id)
|
a.redirectToPick(w, r, date, id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -351,23 +397,12 @@ func (a *app) quickAdd(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Rejected: back to the board with the form filled in and the search
|
// Rejected: back to the board with the form filled in and the search
|
||||||
// still narrowed, so the add card stays on screen.
|
// still narrowed, so the add card stays on screen.
|
||||||
v := logView{
|
v := a.buildLog(r, logOptions{Date: date, Search: form.Name})
|
||||||
Date: date,
|
v.New = form
|
||||||
Today: today(a.loc),
|
if isDatastar(r) {
|
||||||
Search: form.Name,
|
fragment(w, r, dayList(v))
|
||||||
Checked: map[int64]bool{},
|
return
|
||||||
New: form,
|
|
||||||
ShowBoard: true,
|
|
||||||
}
|
}
|
||||||
var err error
|
|
||||||
if v.Dishes, err = listDishes(a.db, v.Search); err != nil {
|
|
||||||
log.Printf("list dishes: %v", err)
|
|
||||||
}
|
|
||||||
v.Groups = groupDishes(v.Dishes)
|
|
||||||
if v.Special, err = listSpecial(a.db, v.Search); err != nil {
|
|
||||||
log.Printf("list special: %v", err)
|
|
||||||
}
|
|
||||||
a.loadDays(r, &v)
|
|
||||||
render(w, r, logPage(v))
|
render(w, r, logPage(v))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,9 +412,7 @@ func (a *app) redirectToPick(w http.ResponseWriter, r *http.Request, date time.T
|
|||||||
if strings.Contains(target, "?") {
|
if strings.Contains(target, "?") {
|
||||||
sep = "&"
|
sep = "&"
|
||||||
}
|
}
|
||||||
http.Redirect(w, r,
|
http.Redirect(w, r, target+sep+"ruoka="+strconv.FormatInt(id, 10), http.StatusSeeOther)
|
||||||
target+sep+"ruoka="+strconv.FormatInt(id, 10)+"#"+dayAnchor(date),
|
|
||||||
http.StatusSeeOther)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// save records the meal and redirects, so a refresh cannot double-post.
|
// save records the meal and redirects, so a refresh cannot double-post.
|
||||||
@@ -404,7 +437,7 @@ func (a *app) save(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "tallennus epäonnistui", http.StatusInternalServerError)
|
http.Error(w, "tallennus epäonnistui", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.redirectToDay(w, r, date)
|
a.finishDay(w, r, date)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) delete(w http.ResponseWriter, r *http.Request) {
|
func (a *app) delete(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -414,13 +447,12 @@ func (a *app) delete(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.Error(w, "poisto epäonnistui", http.StatusInternalServerError)
|
http.Error(w, "poisto epäonnistui", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a.redirectToDay(w, r, date)
|
a.finishDay(w, r, date)
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirectToDay returns to the day in the list, anchor included, so saving or
|
// redirectToDay is the no-JavaScript path back after a write.
|
||||||
// deleting leaves the viewport where the work was happening.
|
|
||||||
func (a *app) redirectToDay(w http.ResponseWriter, r *http.Request, date time.Time) {
|
func (a *app) redirectToDay(w http.ResponseWriter, r *http.Request, date time.Time) {
|
||||||
http.Redirect(w, r, dayLink(date, today(a.loc)), http.StatusSeeOther)
|
http.Redirect(w, r, dayURL("/", date, today(a.loc)), http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mainForm and sideForm carry what the user typed, so a rejected submission
|
// mainForm and sideForm carry what the user typed, so a rejected submission
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ func routes(db *sql.DB, loc *time.Location, password string) http.Handler {
|
|||||||
mux.HandleFunc("POST /kirjaa", a.save)
|
mux.HandleFunc("POST /kirjaa", a.save)
|
||||||
mux.HandleFunc("POST /lisaa", a.quickAdd)
|
mux.HandleFunc("POST /lisaa", a.quickAdd)
|
||||||
mux.HandleFunc("GET /etsi", a.searchBoard)
|
mux.HandleFunc("GET /etsi", a.searchBoard)
|
||||||
|
mux.HandleFunc("GET /paiva", a.day)
|
||||||
mux.HandleFunc("POST /poista", a.delete)
|
mux.HandleFunc("POST /poista", a.delete)
|
||||||
mux.HandleFunc("GET /ruuat", a.catalog)
|
mux.HandleFunc("GET /ruuat", a.catalog)
|
||||||
mux.HandleFunc("GET /ruuat/etsi", a.searchCatalog)
|
mux.HandleFunc("GET /ruuat/etsi", a.searchCatalog)
|
||||||
|
|||||||
+53
-22
@@ -49,16 +49,15 @@ func showURL(pageURL string) string {
|
|||||||
return strings.Replace(pageURL, "/ruuat?", "/ruuat/nayta?", 1)
|
return strings.Replace(pageURL, "/ruuat?", "/ruuat/nayta?", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// dayAnchor names the element a day expands into.
|
// dayPatch is the endpoint behind every link in the day list. The href beside
|
||||||
func dayAnchor(d time.Time) string {
|
// it stays a real page URL for anyone without JavaScript; Datastar calls this
|
||||||
return "paiva-" + isoDate(d)
|
// instead and swaps the list where it stands.
|
||||||
|
func dayPatch(d time.Time, param string) string {
|
||||||
|
url := "/paiva?pvm=" + isoDate(d)
|
||||||
|
if param != "" {
|
||||||
|
url += "&" + param
|
||||||
}
|
}
|
||||||
|
return url
|
||||||
// dayLink opens a day and lands the viewport on it. Without the fragment the
|
|
||||||
// browser would jump to the top of a page whose selected day might be far
|
|
||||||
// down the list.
|
|
||||||
func dayLink(d, now time.Time) string {
|
|
||||||
return dayURL("/", d, now) + "#" + dayAnchor(d)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// pickSeparator joins a dish onto a day URL, which already carries ?pvm= for
|
// pickSeparator joins a dish onto a day URL, which already carries ?pvm= for
|
||||||
@@ -70,15 +69,14 @@ func pickSeparator(v logView) string {
|
|||||||
return "&"
|
return "&"
|
||||||
}
|
}
|
||||||
|
|
||||||
// stepURL is any link inside the open day. It keeps the anchor, so picking a
|
// stepURL is the page URL for a link inside the open day: the fallback when
|
||||||
// dish or cancelling stays where the day is instead of throwing the viewport
|
// there is no JavaScript to intercept it.
|
||||||
// back to the top of the list.
|
|
||||||
func stepURL(v logView, param string) string {
|
func stepURL(v logView, param string) string {
|
||||||
url := dayURL("/", v.Date, v.Today)
|
url := dayURL("/", v.Date, v.Today)
|
||||||
if param != "" {
|
if param != "" {
|
||||||
url += pickSeparator(v) + param
|
url += pickSeparator(v) + param
|
||||||
}
|
}
|
||||||
return url + "#" + dayAnchor(v.Date)
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
// jsString renders a Go string as a JavaScript literal, for the data-signals
|
// jsString renders a Go string as a JavaScript literal, for the data-signals
|
||||||
@@ -320,13 +318,13 @@ templ logPage(v logView) {
|
|||||||
// above the list and drop that day out of it, so the rows below jumped up
|
// above the list and drop that day out of it, so the rows below jumped up
|
||||||
// under the tap. Now nothing moves — the row grows.
|
// under the tap. Now nothing moves — the row grows.
|
||||||
templ dayList(v logView) {
|
templ dayList(v logView) {
|
||||||
<section class="history">
|
<section class="history" id="paivat">
|
||||||
for i, row := range v.History.Rows {
|
for i, row := range v.History.Rows {
|
||||||
if i == 0 || v.History.Rows[i-1].Date.Month() != row.Date.Month() {
|
if i == 0 || v.History.Rows[i-1].Date.Month() != row.Date.Month() {
|
||||||
<p class="monthrule">{ monthFI(row.Date) }</p>
|
<p class="monthrule">{ monthFI(row.Date) }</p>
|
||||||
}
|
}
|
||||||
if row.Date.Equal(v.Date) {
|
if row.Date.Equal(v.Date) {
|
||||||
<div class="open" id={ dayAnchor(row.Date) }>
|
<div class="open">
|
||||||
<p class="openday">
|
<p class="openday">
|
||||||
if row.Date.Equal(v.Today) {
|
if row.Date.Equal(v.Today) {
|
||||||
Tänään
|
Tänään
|
||||||
@@ -344,7 +342,11 @@ templ dayList(v logView) {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
} else if row.Entry != nil {
|
} else if row.Entry != nil {
|
||||||
<a class="entry" href={ templ.SafeURL(dayLink(row.Date, v.Today)) }>
|
<a
|
||||||
|
class="entry"
|
||||||
|
href={ templ.SafeURL(dayURL("/", row.Date, v.Today)) }
|
||||||
|
data-on:click__prevent={ "@get('" + dayPatch(row.Date, "") + "')" }
|
||||||
|
>
|
||||||
<time>{ dayLabelFI(row.Date) }</time>
|
<time>{ dayLabelFI(row.Date) }</time>
|
||||||
<div>
|
<div>
|
||||||
<div class="nm">
|
<div class="nm">
|
||||||
@@ -356,7 +358,11 @@ templ dayList(v logView) {
|
|||||||
<span class="chev">›</span>
|
<span class="chev">›</span>
|
||||||
</a>
|
</a>
|
||||||
} else {
|
} else {
|
||||||
<a class="gapline" href={ templ.SafeURL(dayLink(row.Date, v.Today)) }>
|
<a
|
||||||
|
class="gapline"
|
||||||
|
href={ templ.SafeURL(dayURL("/", row.Date, v.Today)) }
|
||||||
|
data-on:click__prevent={ "@get('" + dayPatch(row.Date, "") + "')" }
|
||||||
|
>
|
||||||
<time>{ dayLabelFI(row.Date) }</time>
|
<time>{ dayLabelFI(row.Date) }</time>
|
||||||
<span>Ei merkintää</span>
|
<span>Ei merkintää</span>
|
||||||
<span class="act">Merkitse</span>
|
<span class="act">Merkitse</span>
|
||||||
@@ -367,6 +373,7 @@ templ dayList(v logView) {
|
|||||||
<a
|
<a
|
||||||
class="more"
|
class="more"
|
||||||
href={ templ.SafeURL(dayURL("/", v.Date, v.Today) + pickSeparator(v) + "paivat=" + strconv.Itoa(v.HistoryMore)) }
|
href={ templ.SafeURL(dayURL("/", v.Date, v.Today) + pickSeparator(v) + "paivat=" + strconv.Itoa(v.HistoryMore)) }
|
||||||
|
data-on:click__prevent={ "@get('" + dayPatch(v.Date, "paivat="+strconv.Itoa(v.HistoryMore)) + "')" }
|
||||||
>Näytä lisää</a>
|
>Näytä lisää</a>
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
@@ -442,6 +449,7 @@ templ boardList(v logView) {
|
|||||||
<a
|
<a
|
||||||
class="pill plain"
|
class="pill plain"
|
||||||
href={ templ.SafeURL(stepURL(v, "ruoka="+strconv.FormatInt(d.ID, 10))) }
|
href={ templ.SafeURL(stepURL(v, "ruoka="+strconv.FormatInt(d.ID, 10))) }
|
||||||
|
data-on:click__prevent={ "@get('" + dayPatch(v.Date, "ruoka="+strconv.FormatInt(d.ID, 10)) + "')" }
|
||||||
>
|
>
|
||||||
@categoryIcon(d.CategoryKey())
|
@categoryIcon(d.CategoryKey())
|
||||||
{ d.Name }
|
{ d.Name }
|
||||||
@@ -477,7 +485,11 @@ templ quickAddCard(v logView) {
|
|||||||
if v.New.Err != "" {
|
if v.New.Err != "" {
|
||||||
<p class="formerr">{ v.New.Err }</p>
|
<p class="formerr">{ v.New.Err }</p>
|
||||||
}
|
}
|
||||||
<form method="post" action="/lisaa">
|
<form
|
||||||
|
method="post"
|
||||||
|
action="/lisaa"
|
||||||
|
data-on:submit__prevent="@post('/lisaa', {contentType: 'form'})"
|
||||||
|
>
|
||||||
<input type="hidden" name="pvm" value={ isoDate(v.Date) }/>
|
<input type="hidden" name="pvm" value={ isoDate(v.Date) }/>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Nimi</span>
|
<span>Nimi</span>
|
||||||
@@ -509,6 +521,7 @@ templ dishPill(d Dish, v logView) {
|
|||||||
<a
|
<a
|
||||||
class={ "pill", d.Size() }
|
class={ "pill", d.Size() }
|
||||||
href={ templ.SafeURL(stepURL(v, "ruoka="+strconv.FormatInt(d.ID, 10))) }
|
href={ templ.SafeURL(stepURL(v, "ruoka="+strconv.FormatInt(d.ID, 10))) }
|
||||||
|
data-on:click__prevent={ "@get('" + dayPatch(v.Date, "ruoka="+strconv.FormatInt(d.ID, 10)) + "')" }
|
||||||
>
|
>
|
||||||
@categoryIcon(d.CategoryKey())
|
@categoryIcon(d.CategoryKey())
|
||||||
{ d.Name }
|
{ d.Name }
|
||||||
@@ -524,7 +537,11 @@ templ sidesStep(v logView) {
|
|||||||
@categoryIcon(v.Chosen.CategoryKey())
|
@categoryIcon(v.Chosen.CategoryKey())
|
||||||
{ v.Chosen.Name }
|
{ v.Chosen.Name }
|
||||||
</h3>
|
</h3>
|
||||||
<form method="post" action="/kirjaa">
|
<form
|
||||||
|
method="post"
|
||||||
|
action="/kirjaa"
|
||||||
|
data-on:submit__prevent="@post('/kirjaa', {contentType: 'form'})"
|
||||||
|
>
|
||||||
<input type="hidden" name="pvm" value={ isoDate(v.Date) }/>
|
<input type="hidden" name="pvm" value={ isoDate(v.Date) }/>
|
||||||
<input type="hidden" name="ruoka" value={ strconv.FormatInt(v.Chosen.ID, 10) }/>
|
<input type="hidden" name="ruoka" value={ strconv.FormatInt(v.Chosen.ID, 10) }/>
|
||||||
if v.Chosen.HasSides && len(v.Sides) > 0 {
|
if v.Chosen.HasSides && len(v.Sides) > 0 {
|
||||||
@@ -546,7 +563,11 @@ templ sidesStep(v logView) {
|
|||||||
}
|
}
|
||||||
<button class="primary" type="submit">Tallenna</button>
|
<button class="primary" type="submit">Tallenna</button>
|
||||||
</form>
|
</form>
|
||||||
<a class="ghost" href={ templ.SafeURL(stepURL(v, "")) }>Peruuta</a>
|
<a
|
||||||
|
class="ghost"
|
||||||
|
href={ templ.SafeURL(stepURL(v, "")) }
|
||||||
|
data-on:click__prevent={ "@get('" + dayPatch(v.Date, "") + "')" }
|
||||||
|
>Peruuta</a>
|
||||||
</section>
|
</section>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,21 +588,31 @@ templ loggedCard(v logView) {
|
|||||||
if v.Confirming {
|
if v.Confirming {
|
||||||
<p class="q">Poistetaanko merkintä?</p>
|
<p class="q">Poistetaanko merkintä?</p>
|
||||||
<div class="pair">
|
<div class="pair">
|
||||||
<form method="post" action="/poista">
|
<form
|
||||||
|
method="post"
|
||||||
|
action="/poista"
|
||||||
|
data-on:submit__prevent="@post('/poista', {contentType: 'form'})"
|
||||||
|
>
|
||||||
<input type="hidden" name="pvm" value={ isoDate(v.Date) }/>
|
<input type="hidden" name="pvm" value={ isoDate(v.Date) }/>
|
||||||
<button class="btn del" type="submit">Kyllä, poista</button>
|
<button class="btn del" type="submit">Kyllä, poista</button>
|
||||||
</form>
|
</form>
|
||||||
<a class="btn" href={ templ.SafeURL(stepURL(v, "")) }>Peruuta</a>
|
<a
|
||||||
|
class="btn"
|
||||||
|
href={ templ.SafeURL(stepURL(v, "")) }
|
||||||
|
data-on:click__prevent={ "@get('" + dayPatch(v.Date, "") + "')" }
|
||||||
|
>Peruuta</a>
|
||||||
</div>
|
</div>
|
||||||
} else {
|
} else {
|
||||||
<div class="pair">
|
<div class="pair">
|
||||||
<a
|
<a
|
||||||
class="btn"
|
class="btn"
|
||||||
href={ templ.SafeURL(stepURL(v, "muuta=1")) }
|
href={ templ.SafeURL(stepURL(v, "muuta=1")) }
|
||||||
|
data-on:click__prevent={ "@get('" + dayPatch(v.Date, "muuta=1") + "')" }
|
||||||
>Muokkaa</a>
|
>Muokkaa</a>
|
||||||
<a
|
<a
|
||||||
class="btn del"
|
class="btn del"
|
||||||
href={ templ.SafeURL(stepURL(v, "poista=1")) }
|
href={ templ.SafeURL(stepURL(v, "poista=1")) }
|
||||||
|
data-on:click__prevent={ "@get('" + dayPatch(v.Date, "poista=1") + "')" }
|
||||||
>Poista</a>
|
>Poista</a>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-4
@@ -140,13 +140,31 @@ check "the saved day shows what was eaten" \
|
|||||||
# The selected day expands inside the list rather than in a panel above it,
|
# The selected day expands inside the list rather than in a panel above it,
|
||||||
# so the rows below do not shift when one is tapped.
|
# so the rows below do not shift when one is tapped.
|
||||||
day=$(curl -s -u ":$pass" "http://$addr/?pvm=2026-09-05")
|
day=$(curl -s -u ":$pass" "http://$addr/?pvm=2026-09-05")
|
||||||
check "the selected day expands in place" "$day" 'id="paiva-2026-09-05"'
|
check "the selected day expands in place" "$day" 'class="open"'
|
||||||
check "and stays in the list rather than being lifted out" "$day" "kirjattu"
|
check "and stays in the list rather than being lifted out" "$day" "kirjattu"
|
||||||
check "links inside it keep the anchor" "$day" "#paiva-2026-09-05"
|
|
||||||
|
|
||||||
check "saving returns to the day, not the top" \
|
# ---- the day list patches in place instead of navigating ----------------
|
||||||
|
|
||||||
|
dayp=$(curl -s -u ":$pass" -H 'Datastar-Request: true' "http://$addr/paiva?pvm=2026-09-05")
|
||||||
|
check "opening a day patches the list" "$dayp" 'id="paivat"'
|
||||||
|
refute "and returns a fragment, not a page" "$dayp" "<html"
|
||||||
|
|
||||||
|
check "picking a dish patches to the sides step" \
|
||||||
|
"$(curl -s -u ":$pass" -H 'Datastar-Request: true' \
|
||||||
|
"http://$addr/paiva?pvm=2026-09-05&ruoka=$ruoka")" "Tallenna"
|
||||||
|
|
||||||
|
check "saving from Datastar patches back" \
|
||||||
|
"$(curl -s -u ":$pass" -H 'Datastar-Request: true' \
|
||||||
|
-d "pvm=2026-09-04&ruoka=$ruoka" "http://$addr/kirjaa")" 'id="paivat"'
|
||||||
|
|
||||||
|
check "deleting from Datastar patches back" \
|
||||||
|
"$(curl -s -u ":$pass" -H 'Datastar-Request: true' \
|
||||||
|
-d "pvm=2026-09-04" "http://$addr/poista")" 'id="paivat"'
|
||||||
|
|
||||||
|
# Without the header it must still redirect, for no JavaScript.
|
||||||
|
check "a plain save still redirects to the day" \
|
||||||
"$(curl -s -o /dev/null -w '%{redirect_url}' -u ":$pass" \
|
"$(curl -s -o /dev/null -w '%{redirect_url}' -u ":$pass" \
|
||||||
-d "pvm=2026-09-04&ruoka=$ruoka" "http://$addr/kirjaa")" "#paiva-2026-09-04"
|
-d "pvm=2026-09-04&ruoka=$ruoka" "http://$addr/kirjaa")" "pvm=2026-09-04"
|
||||||
|
|
||||||
# Deleting a logged meal drops the row outright, so it asks first.
|
# Deleting a logged meal drops the row outright, so it asks first.
|
||||||
saved=$(curl -s -u ":$pass" "http://$addr/?pvm=2026-09-05&poista=1")
|
saved=$(curl -s -u ":$pass" "http://$addr/?pvm=2026-09-05&poista=1")
|
||||||
|
|||||||
Reference in New Issue
Block a user