diff --git a/internal/webserver/handler_bookmarks.go b/internal/webserver/handler_bookmarks.go new file mode 100644 index 0000000..d758448 --- /dev/null +++ b/internal/webserver/handler_bookmarks.go @@ -0,0 +1,36 @@ +package webserver + +import ( + "net/http" + "errors" + + "gitlab.com/offline-twitter/twitter_offline_engine/pkg/persistence" +) + +func (app *Application) Bookmarks(w http.ResponseWriter, r *http.Request) { + app.traceLog.Printf("'Bookmarks' handler (path: %q)", r.URL.Path) + + c := persistence.NewUserFeedBookmarksCursor(app.ActiveUser.Handle) + 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, app.ActiveUser.ID) + if err != nil && !errors.Is(err, persistence.ErrEndOfFeed) { + panic(err) + } + + if is_htmx(r) && c.CursorPosition == persistence.CURSOR_MIDDLE { + // It's a Show More request + app.buffered_render_htmx(w, "timeline", PageGlobalData{TweetTrove: feed.TweetTrove}, feed) + } else { + app.buffered_render_page( + w, + "tpl/bookmarks.tpl", + PageGlobalData{TweetTrove: feed.TweetTrove}, + TimelineData{Feed: feed}, + ) + } +} diff --git a/internal/webserver/server.go b/internal/webserver/server.go index a857039..96ba0f7 100644 --- a/internal/webserver/server.go +++ b/internal/webserver/server.go @@ -122,6 +122,8 @@ func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.StripPrefix("/search", http.HandlerFunc(app.Search)).ServeHTTP(w, r) case "lists": http.StripPrefix("/lists", http.HandlerFunc(app.Lists)).ServeHTTP(w, r) + case "bookmarks": + app.Bookmarks(w, r) case "messages": http.StripPrefix("/messages", http.HandlerFunc(app.Messages)).ServeHTTP(w, r) case "nav-sidebar-poll-updates": diff --git a/internal/webserver/server_test.go b/internal/webserver/server_test.go index 5074305..e8df2dd 100644 --- a/internal/webserver/server_test.go +++ b/internal/webserver/server_test.go @@ -180,6 +180,37 @@ func TestUserFeedLikesTab(t *testing.T) { assert.Len(tweets, 4) } +func TestBookmarksTab(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + // Boilerplate for setting an active user + app := webserver.NewApp(profile) + app.IsScrapingDisabled = true + app.ActiveUser = scraper.User{ID: 1488963321701171204, Handle: "Offline_Twatter"} // Simulate a login + + recorder := httptest.NewRecorder() + app.ServeHTTP(recorder, httptest.NewRequest("GET", "/bookmarks", nil)) + resp := recorder.Result() + require.Equal(resp.StatusCode, 200) + + root, err := html.Parse(resp.Body) + require.NoError(err) + tweets := cascadia.QueryAll(root, selector(".timeline > .tweet")) + assert.Len(tweets, 2) + + // Double check pagination works properly + recorder = httptest.NewRecorder() + app.ServeHTTP(recorder, httptest.NewRequest("GET", "/bookmarks?cursor=1800452344077464795", nil)) + resp = recorder.Result() + require.Equal(resp.StatusCode, 200) + + root, err = html.Parse(resp.Body) + require.NoError(err) + tweets = cascadia.QueryAll(root, selector(".timeline > .tweet")) + assert.Len(tweets, 1) +} + // Followers and followees // ----------------------- diff --git a/internal/webserver/static/styles.css b/internal/webserver/static/styles.css index 12eeb96..2780a27 100644 --- a/internal/webserver/static/styles.css +++ b/internal/webserver/static/styles.css @@ -1190,6 +1190,15 @@ main { } } + +/****************************************************** + * Bookmarks pages + ******************************************************/ + +.bookmarks-feed-header { + border-bottom: 1px solid var(--color-outline-gray); +} + .add-users-container { padding: 1em; text-align: center; diff --git a/internal/webserver/tpl/includes/nav_sidebar.tpl b/internal/webserver/tpl/includes/nav_sidebar.tpl index 1502f49..aa1f46c 100644 --- a/internal/webserver/tpl/includes/nav_sidebar.tpl +++ b/internal/webserver/tpl/includes/nav_sidebar.tpl @@ -42,7 +42,7 @@ - +
  • @@ -50,7 +50,7 @@
  • - +
  • diff --git a/sample_data/seed_data.sql b/sample_data/seed_data.sql index d705dcb..ba38b99 100644 --- a/sample_data/seed_data.sql +++ b/sample_data/seed_data.sql @@ -347,6 +347,8 @@ create table likes(rowid integer primary key, foreign key(user_id) references users(id) foreign key(tweet_id) references tweets(id) ); +create index if not exists index_likes_user_id on likes (user_id); +create index if not exists index_likes_tweet_id on likes (tweet_id); insert into likes values (1, 1, 1178839081222115328, 1413646595493568516), @@ -356,6 +358,22 @@ insert into likes values (5, 5, 1178839081222115328, 1698765208393576891); +create table bookmarks(rowid integer primary key, + sort_order integer not null, + user_id integer not null, + tweet_id integer not null, + unique(user_id, tweet_id) + foreign key(tweet_id) references tweets(id) + foreign key(user_id) references users(id) +); +create index if not exists index_bookmarks_user_id on bookmarks (user_id); +create index if not exists index_bookmarks_tweet_id on bookmarks (tweet_id); +insert into bookmarks values + (23,1800452344077464795,1488963321701171204,1413647919215906817), + (24,1800452337108289740,1488963321701171204,1439747634277740546); + + + create table chat_rooms (rowid integer primary key, id text unique not null, type text not null, @@ -514,6 +532,6 @@ insert into fake_user_sequence values(0x4000000000000000); create table database_version(rowid integer primary key, version_number integer not null unique ); -insert into database_version(version_number) values (29); +insert into database_version(version_number) values (30); COMMIT;