diff --git a/internal/webserver/server.go b/internal/webserver/server.go index 7db6117..161f395 100644 --- a/internal/webserver/server.go +++ b/internal/webserver/server.go @@ -119,6 +119,8 @@ func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) { app.Login(w, r) case "change-session": app.ChangeSession(w, r) + case "timeline": + app.Timeline(w, r) default: app.UserFeed(w, r) } @@ -308,6 +310,36 @@ func (app *Application) UserFeed(w http.ResponseWriter, r *http.Request) { } } +func (app *Application) Timeline(w http.ResponseWriter, r *http.Request) { + app.traceLog.Printf("'Timeline' handler (path: %q)", r.URL.Path) + + c := persistence.NewTimelineCursor() + err := parse_cursor_value(&c, r) + if err != nil { + app.error_400_with_message(w, "invalid cursor (must be a number)") + return + } + + feed, err := app.Profile.NextPage(c) + if err != nil { + if errors.Is(err, persistence.ErrEndOfFeed) { + // TODO + } else { + panic(err) + } + } + + data := UserProfileData{Feed: feed} // TODO: wrong struct + app.InfoLog.Printf(to_json(data)) + + if r.Header.Get("HX-Request") == "true" && c.CursorPosition == persistence.CURSOR_MIDDLE { + // It's a Show More request + app.buffered_render_tweet_htmx(w, "timeline", data) + } else { + app.buffered_render_tweet_page(w, "tpl/offline_timeline.tpl", data) + } +} + type FormErrors map[string]string type LoginForm struct { diff --git a/internal/webserver/server_test.go b/internal/webserver/server_test.go index e66fe29..066cc13 100644 --- a/internal/webserver/server_test.go +++ b/internal/webserver/server_test.go @@ -116,6 +116,50 @@ func TestUserFeedWithCursorBadNumber(t *testing.T) { require.Equal(resp.StatusCode, 400) } +// Timeline page +// ------------- + +func TestTimeline(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + resp := do_request(httptest.NewRequest("GET", "/timeline", nil)) + require.Equal(resp.StatusCode, 200) + + root, err := html.Parse(resp.Body) + require.NoError(err) + title_node := cascadia.Query(root, selector("title")) + assert.Equal(title_node.FirstChild.Data, "Offline Twitter | Timeline") + + tweet_nodes := cascadia.QueryAll(root, selector(".tweet")) + assert.Len(tweet_nodes, 18) +} + +func TestTimelineWithCursor(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + resp := do_request(httptest.NewRequest("GET", "/timeline?cursor=1631935701", nil)) + require.Equal(resp.StatusCode, 200) + + root, err := html.Parse(resp.Body) + require.NoError(err) + title_node := cascadia.Query(root, selector("title")) + assert.Equal(title_node.FirstChild.Data, "Offline Twitter | Timeline") + + tweet_nodes := cascadia.QueryAll(root, selector(".tweet")) + assert.Len(tweet_nodes, 10) +} + +func TestTimelineWithCursorBadNumber(t *testing.T) { + require := require.New(t) + + // With a cursor but it sucks + resp := do_request(httptest.NewRequest("GET", "/timeline?cursor=asdf", nil)) + require.Equal(resp.StatusCode, 400) +} + + // Tweet Detail page // ----------------- diff --git a/internal/webserver/static/styles.css b/internal/webserver/static/styles.css index 1a00d37..183613a 100644 --- a/internal/webserver/static/styles.css +++ b/internal/webserver/static/styles.css @@ -285,7 +285,7 @@ ul.quick-links { margin-bottom: 0.1em; border-bottom: 1px solid var(--color-outline-gray); } -.user-feed-tweets .tweet { +.timeline .tweet { border-bottom: 1px solid var(--color-twitter-off-white-dark); padding-top: 0.8em; padding-bottom: 0.8em; @@ -304,6 +304,7 @@ ul.quick-links { width: 40%; left: 30%; box-sizing: border-box; + z-index: 1; } .back-button { diff --git a/internal/webserver/tpl/includes/nav_sidebar.tpl b/internal/webserver/tpl/includes/nav_sidebar.tpl index a46b612..62ac505 100644 --- a/internal/webserver/tpl/includes/nav_sidebar.tpl +++ b/internal/webserver/tpl/includes/nav_sidebar.tpl @@ -7,7 +7,7 @@