Tag autocomplete shows suggestions when typing #partial in capture bar. Tab/enter accepts, up/down navigates, esc dismisses. Query composition extends ? search with date filters (@today, @week, @month, <7d, >30d), card type filters (^snippet), all composable with existing text and tag filters.
This commit is contained in:
+53
-12
@@ -4,19 +4,23 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
Body string
|
||||
Glyph string
|
||||
Title *string
|
||||
Description *string
|
||||
TimeAnchor *string
|
||||
Tags []string
|
||||
FilterTags []string
|
||||
CardSuffix *string
|
||||
Pin bool
|
||||
Query bool
|
||||
Body string
|
||||
Glyph string
|
||||
Title *string
|
||||
Description *string
|
||||
TimeAnchor *string
|
||||
Tags []string
|
||||
FilterTags []string
|
||||
CardSuffix *string
|
||||
Pin bool
|
||||
Query bool
|
||||
QueryDateFrom *string
|
||||
QueryDateTo *string
|
||||
QueryCardType *string
|
||||
}
|
||||
|
||||
var validCardTypes = map[string]string{
|
||||
@@ -66,11 +70,48 @@ func Parse(input string) (*Result, error) {
|
||||
r.Glyph = ""
|
||||
tokens := strings.Fields(remaining)
|
||||
var bodyParts []string
|
||||
now := time.Now()
|
||||
for _, tok := range tokens {
|
||||
if strings.HasPrefix(tok, "#") && len(tok) > 1 && !strings.HasPrefix(tok, "##") {
|
||||
switch {
|
||||
case strings.HasPrefix(tok, "#") && len(tok) > 1 && !strings.HasPrefix(tok, "##"):
|
||||
tag := strings.ToLower(tok[1:])
|
||||
r.FilterTags = append(r.FilterTags, tag)
|
||||
} else {
|
||||
case tok == "@today":
|
||||
d := now.Format("2006-01-02")
|
||||
r.QueryDateFrom = &d
|
||||
r.QueryDateTo = &d
|
||||
case tok == "@yesterday":
|
||||
d := now.AddDate(0, 0, -1).Format("2006-01-02")
|
||||
r.QueryDateFrom = &d
|
||||
r.QueryDateTo = &d
|
||||
case tok == "@week":
|
||||
d := now.AddDate(0, 0, -7).Format("2006-01-02")
|
||||
r.QueryDateFrom = &d
|
||||
case tok == "@month":
|
||||
d := now.AddDate(0, -1, 0).Format("2006-01-02")
|
||||
r.QueryDateFrom = &d
|
||||
case strings.HasPrefix(tok, ">") && strings.HasSuffix(tok, "d"):
|
||||
if n, err := strconv.Atoi(tok[1 : len(tok)-1]); err == nil && n > 0 {
|
||||
d := now.AddDate(0, 0, -n).Format("2006-01-02")
|
||||
r.QueryDateTo = &d
|
||||
} else {
|
||||
bodyParts = append(bodyParts, tok)
|
||||
}
|
||||
case strings.HasPrefix(tok, "<") && strings.HasSuffix(tok, "d"):
|
||||
if n, err := strconv.Atoi(tok[1 : len(tok)-1]); err == nil && n > 0 {
|
||||
d := now.AddDate(0, 0, -n).Format("2006-01-02")
|
||||
r.QueryDateFrom = &d
|
||||
} else {
|
||||
bodyParts = append(bodyParts, tok)
|
||||
}
|
||||
case strings.HasPrefix(tok, "^") && len(tok) > 1:
|
||||
suffix := tok[1:]
|
||||
if ct, ok := validCardTypes[suffix]; ok {
|
||||
r.QueryCardType = &ct
|
||||
} else {
|
||||
bodyParts = append(bodyParts, tok)
|
||||
}
|
||||
default:
|
||||
bodyParts = append(bodyParts, tok)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +158,67 @@ func TestParse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseQueryComposition(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantBody string
|
||||
wantTags []string
|
||||
wantDateFrom bool
|
||||
wantDateTo bool
|
||||
wantCardType *string
|
||||
}{
|
||||
{"today", "?@today", "", nil, true, true, nil},
|
||||
{"yesterday", "?@yesterday", "", nil, true, true, nil},
|
||||
{"week", "?@week", "", nil, true, false, nil},
|
||||
{"month", "?@month", "", nil, true, false, nil},
|
||||
{"newer than", "?<7d", "", nil, true, false, nil},
|
||||
{"older than", "?>30d", "", nil, false, true, nil},
|
||||
{"card type snippet", "?^snippet", "", nil, false, false, sp("snippet")},
|
||||
{"card type shorthand", "?^c", "", nil, false, false, sp("snippet")},
|
||||
{"card type checklist", "?^checklist", "", nil, false, false, sp("checklist")},
|
||||
{"invalid card type stays as body", "?^bogus", "^bogus", nil, false, false, nil},
|
||||
{"combined text and date", "?deploy @today", "deploy", nil, true, true, nil},
|
||||
{"combined tags and date", "?#ops @week", "", []string{"ops"}, true, false, nil},
|
||||
{"combined all", "?deploy #ops @week ^snippet", "deploy", []string{"ops"}, true, false, sp("snippet")},
|
||||
{"invalid age stays as body", "?>abcd", ">abcd", nil, false, false, nil},
|
||||
{"zero days stays as body", "?>0d", ">0d", nil, false, false, nil},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := Parse(tt.input)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !got.Query {
|
||||
t.Fatal("expected Query=true")
|
||||
}
|
||||
if got.Body != tt.wantBody {
|
||||
t.Errorf("body: got %q, want %q", got.Body, tt.wantBody)
|
||||
}
|
||||
if !tagsEq(got.FilterTags, tt.wantTags) {
|
||||
t.Errorf("tags: got %v, want %v", got.FilterTags, tt.wantTags)
|
||||
}
|
||||
if tt.wantDateFrom && got.QueryDateFrom == nil {
|
||||
t.Error("expected QueryDateFrom to be set")
|
||||
}
|
||||
if !tt.wantDateFrom && got.QueryDateFrom != nil {
|
||||
t.Errorf("expected QueryDateFrom nil, got %v", *got.QueryDateFrom)
|
||||
}
|
||||
if tt.wantDateTo && got.QueryDateTo == nil {
|
||||
t.Error("expected QueryDateTo to be set")
|
||||
}
|
||||
if !tt.wantDateTo && got.QueryDateTo != nil {
|
||||
t.Errorf("expected QueryDateTo nil, got %v", *got.QueryDateTo)
|
||||
}
|
||||
if !ptrEq(got.QueryCardType, tt.wantCardType) {
|
||||
t.Errorf("card type: got %v, want %v", strPtr(got.QueryCardType), strPtr(tt.wantCardType))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ptrEq(a, b *string) bool {
|
||||
if a == nil && b == nil {
|
||||
return true
|
||||
|
||||
Reference in New Issue
Block a user