package web // The search page (novox/hq ADR 0440 ยง4, issue 1265): the form's own field names are exactly mail-store.search's // own argument names (mesh-mail tools.go searchArgs, issue 1232), so a filled field maps straight onto the // seat's own argument of the same name; an empty field is left out of the call entirely, never sent as "". // Paged by date exactly as the folder page (issue 1264): 50 a page, the next link asking again with before the // last shown mail's own date. import ( "context" "encoding/json" "html/template" "net/http" "strings" "time" ) var searchTemplate = template.Must(template.New("search").Parse(` Search mail

Search

{{if .Error}}

{{.Error}}

{{else}} {{if .NextBefore}}

Older

{{end}} {{end}} `)) type searchTemplateData struct { Query, From, To, OnOrAfter, Before, Error, NextBefore, NextLink string HasAttachment bool Mails []searchResult } // searchArgsFromForm builds mail-store.search's own arguments from the request's query parameters: a field // left empty is left out of the call entirely (issue 1265/AC1) โ€” never sent empty. "from" is split into a list // on whitespace or commas, mail-store.search's own array argument. limit is always MostFolderResults (AC2), // mechanical paging the form itself never carries. func searchArgsFromForm(q map[string][]string) map[string]any { get := func(name string) string { if v, ok := q[name]; ok && len(v) > 0 { return strings.TrimSpace(v[0]) } return "" } args := map[string]any{"limit": float64(MostFolderResults)} if v := get("query"); v != "" { args["query"] = v } if v := get("from"); v != "" { var list []string for _, part := range strings.FieldsFunc(v, func(r rune) bool { return r == ',' || r == ' ' }) { if part != "" { list = append(list, part) } } if len(list) > 0 { args["from"] = list } } if v := get("to"); v != "" { args["to"] = v } if v := get("on-or-after"); v != "" { args["on-or-after"] = v } if v := get("before"); v != "" { args["before"] = v } if v := get("has-attachment"); v != "" { args["has-attachment"] = true } return args } // searchPage is issue 1265/AC1-AC4. func (b *Bundle) searchPage(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() data := searchTemplateData{ Query: q.Get("query"), From: q.Get("from"), To: q.Get("to"), OnOrAfter: q.Get("on-or-after"), Before: q.Get("before"), HasAttachment: q.Get("has-attachment") != "", } args := searchArgsFromForm(q) ctx, cancel := context.WithTimeout(r.Context(), 20*time.Second) defer cancel() raw, err := b.searchAsk(ctx, args) if err != nil { data.Error = "the search could not be answered" renderPage(w, searchTemplate, data) return } var answer searchAnswer if err := json.Unmarshal(raw, &answer); err != nil { data.Error = "the search could not be answered" renderPage(w, searchTemplate, data) return } data.Mails = answer.Found if len(answer.Found) >= MostFolderResults { last := answer.Found[len(answer.Found)-1].Date data.NextBefore = last next := r.URL.Query() next.Set("before", last) data.NextLink = "/search?" + next.Encode() } renderPage(w, searchTemplate, data) }