diff --git a/internal/webserver/handler_search.go b/internal/webserver/handler_search.go index cee7134..6a12ecb 100644 --- a/internal/webserver/handler_search.go +++ b/internal/webserver/handler_search.go @@ -9,8 +9,41 @@ import ( "strings" "gitlab.com/offline-twitter/twitter_offline_engine/pkg/persistence" + "gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper" ) +type SearchPageData struct { + persistence.Feed + SearchText string + SortOrder persistence.SortOrder + SortOrderOptions []string + // TODO: fill out the search text in the search bar as well (needs modifying the base template) +} + +func NewSearchPageData() SearchPageData { + ret := SearchPageData{SortOrderOptions: []string{}} + for i := 0; i < 4; i++ { // Don't include "Liked At" option which is #4 + ret.SortOrderOptions = append(ret.SortOrderOptions, persistence.SortOrder(i).String()) + } + return ret +} + +func (t SearchPageData) Tweet(id scraper.TweetID) scraper.Tweet { + return t.Tweets[id] +} +func (t SearchPageData) User(id scraper.UserID) scraper.User { + return t.Users[id] +} +func (t SearchPageData) Retweet(id scraper.TweetID) scraper.Retweet { + return t.Retweets[id] +} +func (t SearchPageData) Space(id scraper.SpaceID) scraper.Space { + return t.Spaces[id] +} +func (t SearchPageData) FocusedTweetID() scraper.TweetID { + return scraper.TweetID(0) +} + func (app *Application) Search(w http.ResponseWriter, r *http.Request) { app.traceLog.Printf("'Search' handler (path: %q)", r.URL.Path) @@ -67,6 +100,11 @@ func (app *Application) Search(w http.ResponseWriter, r *http.Request) { app.error_400_with_message(w, "invalid cursor (must be a number)") return } + var is_ok bool + c.SortOrder, is_ok = persistence.SortOrderFromString(r.URL.Query().Get("sort-order")) + if !is_ok && r.URL.Query().Get("sort-order") != "" { + app.error_400_with_message(w, "Invalid sort order") + } feed, err := app.Profile.NextPage(c, app.ActiveUser.ID) if err != nil { @@ -77,7 +115,10 @@ func (app *Application) Search(w http.ResponseWriter, r *http.Request) { } } - data := UserProfileData{Feed: feed} // TODO: wrong struct + data := NewSearchPageData() + data.Feed = feed + data.SearchText = search_text + data.SortOrder = c.SortOrder if r.Header.Get("HX-Request") == "true" && c.CursorPosition == persistence.CURSOR_MIDDLE { // It's a Show More request diff --git a/internal/webserver/response_helpers.go b/internal/webserver/response_helpers.go index fa3d2b2..7f474a7 100644 --- a/internal/webserver/response_helpers.go +++ b/internal/webserver/response_helpers.go @@ -7,6 +7,7 @@ import ( "io" "io/fs" "net/http" + "net/url" "path" "path/filepath" "regexp" @@ -15,6 +16,7 @@ import ( "github.com/Masterminds/sprig/v3" + "gitlab.com/offline-twitter/twitter_offline_engine/pkg/persistence" "gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper" ) @@ -86,14 +88,15 @@ func (app *Application) buffered_render_tweet_page(w http.ResponseWriter, tpl_fi r := renderer{ Funcs: func_map(template.FuncMap{ - "tweet": data.Tweet, - "user": data.User, - "retweet": data.Retweet, - "space": data.Space, - "active_user": app.get_active_user, - "focused_tweet_id": data.FocusedTweetID, - "get_entities": get_entities, - "get_tombstone_text": get_tombstone_text, + "tweet": data.Tweet, + "user": data.User, + "retweet": data.Retweet, + "space": data.Space, + "active_user": app.get_active_user, + "focused_tweet_id": data.FocusedTweetID, + "get_entities": get_entities, + "get_tombstone_text": get_tombstone_text, + "cursor_to_query_params": cursor_to_query_params, }), Filenames: append(partials, get_filepath(tpl_file)), TplName: "base", @@ -121,14 +124,15 @@ func (app *Application) buffered_render_tweet_htmx(w http.ResponseWriter, tpl_na r := renderer{ Funcs: func_map(template.FuncMap{ - "tweet": data.Tweet, - "user": data.User, - "retweet": data.Retweet, - "space": data.Space, - "active_user": app.get_active_user, - "focused_tweet_id": data.FocusedTweetID, - "get_entities": get_entities, - "get_tombstone_text": get_tombstone_text, + "tweet": data.Tweet, + "user": data.User, + "retweet": data.Retweet, + "space": data.Space, + "active_user": app.get_active_user, + "focused_tweet_id": data.FocusedTweetID, + "get_entities": get_entities, + "get_tombstone_text": get_tombstone_text, + "cursor_to_query_params": cursor_to_query_params, }), Filenames: partials, TplName: tpl_name, @@ -233,3 +237,10 @@ func (r renderer) BufferedRender(w io.Writer) { _, err = buf.WriteTo(w) panic_if(err) } + +func cursor_to_query_params(c persistence.Cursor) string { + result := url.Values{} + result.Set("cursor", fmt.Sprint(c.CursorValue)) + result.Set("sort-order", c.SortOrder.String()) + return result.Encode() +} diff --git a/internal/webserver/server_test.go b/internal/webserver/server_test.go index 7eda36a..e7840a9 100644 --- a/internal/webserver/server_test.go +++ b/internal/webserver/server_test.go @@ -270,6 +270,41 @@ func TestSearchWithCursor(t *testing.T) { assert.Len(cascadia.QueryAll(root, selector(".timeline > .tweet")), 2) } +func TestSearchWithSortOrder(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + resp := do_request(httptest.NewRequest("GET", "/search/think?sort-order=most%20likes", nil)) + require.Equal(resp.StatusCode, 200) + root, err := html.Parse(resp.Body) + require.NoError(err) + assert.Contains(cascadia.Query(root, selector("select[name='sort-order'] option[selected]")).FirstChild.Data, "most likes") + + tweets := cascadia.QueryAll(root, selector(".timeline > .tweet")) + txts := []string{ + "Morally nuanced and complicated discussion", + "a lot of y’all embarrass yourselves on this", + "this is why the \"think tank mindset\" is a dead end", + "At this point what can we expect I guess", + "Idk if this is relevant to your department", + } + for i, txt := range txts { + assert.Contains(cascadia.Query(tweets[i], selector("p.text")).FirstChild.Data, txt) + } + + resp = do_request(httptest.NewRequest("GET", "/search/think?sort-order=most%20likes&cursor=413", nil)) + require.Equal(resp.StatusCode, 200) + root, err = html.Parse(resp.Body) + require.NoError(err) + tweets = cascadia.QueryAll(root, selector(".timeline > .tweet")) + for i, txt := range txts[2:] { + assert.Contains(cascadia.Query(tweets[i], selector("p.text")).FirstChild.Data, txt) + } +} + +// Search bar pasted link redirects +// -------------------------------- + func TestSearchRedirectOnUserHandle(t *testing.T) { assert := assert.New(t) diff --git a/internal/webserver/tpl/search.tpl b/internal/webserver/tpl/search.tpl index 7cff985..2e195d8 100644 --- a/internal/webserver/tpl/search.tpl +++ b/internal/webserver/tpl/search.tpl @@ -1,6 +1,18 @@ {{define "title"}}Search{{end}} {{define "main"}} +
End of feed
{{else}} {{end}} diff --git a/pkg/persistence/compound_ssf_queries.go b/pkg/persistence/compound_ssf_queries.go index a7923fd..dcd33a9 100644 --- a/pkg/persistence/compound_ssf_queries.go +++ b/pkg/persistence/compound_ssf_queries.go @@ -19,6 +19,21 @@ const ( SORT_ORDER_LIKED_AT ) +func (o SortOrder) String() string { + return []string{"newest", "oldest", "most likes", "most retweets", "liked at"}[o] +} + +func SortOrderFromString(s string) (SortOrder, bool) { + result, is_ok := map[string]SortOrder{ + "newest": SORT_ORDER_NEWEST, + "oldest": SORT_ORDER_OLDEST, + "most likes": SORT_ORDER_MOST_LIKES, + "most retweets": SORT_ORDER_MOST_RETWEETS, + "liked at": SORT_ORDER_LIKED_AT, + }[s] + return result, is_ok // Have to store as temporary variable b/c otherwise it interprets it as single-value and compile fails +} + func (o SortOrder) OrderByClause() string { switch o { case SORT_ORDER_NEWEST: