diff --git a/internal/webserver/handler_login.go b/internal/webserver/handler_login.go index 04e15a3..f75e160 100644 --- a/internal/webserver/handler_login.go +++ b/internal/webserver/handler_login.go @@ -118,6 +118,6 @@ func (app *Application) ChangeSession(w http.ResponseWriter, r *http.Request) { app.error_400_with_message(w, r, fmt.Sprintf("User not in database: %s", form.AccountName)) return } - data := Notifications{NumMessageNotifications: len(app.Profile.GetUnreadConversations(app.ActiveUser.ID))} + data := NotificationBubbles{NumMessageNotifications: len(app.Profile.GetUnreadConversations(app.ActiveUser.ID))} app.buffered_render_htmx(w, "nav-sidebar", PageGlobalData{}, data) } diff --git a/internal/webserver/handler_notifications.go b/internal/webserver/handler_notifications.go new file mode 100644 index 0000000..b36e7cf --- /dev/null +++ b/internal/webserver/handler_notifications.go @@ -0,0 +1,11 @@ +package webserver + +import ( + "net/http" +) + +func (app *Application) Notifications(w http.ResponseWriter, r *http.Request) { + feed := app.Profile.GetNotificationsForUser(app.ActiveUser.ID, 0) + + app.buffered_render_page(w, "tpl/notifications.tpl", PageGlobalData{TweetTrove: feed.TweetTrove}, feed) +} diff --git a/internal/webserver/handler_sidebar.go b/internal/webserver/handler_sidebar.go index b02a8d9..81d10a5 100644 --- a/internal/webserver/handler_sidebar.go +++ b/internal/webserver/handler_sidebar.go @@ -13,6 +13,6 @@ func (app *Application) NavSidebarPollUpdates(w http.ResponseWriter, r *http.Req return } - data := Notifications{NumMessageNotifications: len(app.Profile.GetUnreadConversations(app.ActiveUser.ID))} + data := NotificationBubbles{NumMessageNotifications: len(app.Profile.GetUnreadConversations(app.ActiveUser.ID))} app.buffered_render_htmx(w, "nav-sidebar", PageGlobalData{}, data) } diff --git a/internal/webserver/renderer_helpers.go b/internal/webserver/renderer_helpers.go index 6351ead..3c3d4a8 100644 --- a/internal/webserver/renderer_helpers.go +++ b/internal/webserver/renderer_helpers.go @@ -15,7 +15,7 @@ import ( "gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper" ) -type Notifications struct { +type NotificationBubbles struct { NumMessageNotifications int } @@ -24,8 +24,8 @@ type PageGlobalData struct { scraper.TweetTrove SearchText string FocusedTweetID scraper.TweetID - Notifications - Toasts []Toast + Toasts []Toast + NotificationBubbles } func (d PageGlobalData) Tweet(id scraper.TweetID) scraper.Tweet { @@ -40,6 +40,9 @@ func (d PageGlobalData) Retweet(id scraper.TweetID) scraper.Retweet { func (d PageGlobalData) Space(id scraper.SpaceID) scraper.Space { return d.Spaces[id] } +func (d PageGlobalData) Notification(id scraper.NotificationID) scraper.Notification { + return d.Notifications[id] +} func (d PageGlobalData) Message(id scraper.DMMessageID) scraper.DMMessage { return d.Messages[id] } @@ -95,7 +98,7 @@ func (r renderer) BufferedRender(w io.Writer) { func (app *Application) buffered_render_page(w http.ResponseWriter, tpl_file string, global_data PageGlobalData, tpl_data interface{}) { partials := append(glob("tpl/includes/*.tpl"), glob("tpl/tweet_page_includes/*.tpl")...) - global_data.Notifications.NumMessageNotifications = len(app.Profile.GetUnreadConversations(app.ActiveUser.ID)) + global_data.NotificationBubbles.NumMessageNotifications = len(app.Profile.GetUnreadConversations(app.ActiveUser.ID)) r := renderer{ Funcs: app.make_funcmap(global_data), @@ -127,6 +130,7 @@ func (app *Application) make_funcmap(global_data PageGlobalData) template.FuncMa "user": global_data.User, "retweet": global_data.Retweet, "space": global_data.Space, + "notification": global_data.Notification, "dm_message": global_data.Message, "chat_room": global_data.ChatRoom, "focused_tweet_id": global_data.GetFocusedTweetID, diff --git a/internal/webserver/server.go b/internal/webserver/server.go index 239c4a5..c36761c 100644 --- a/internal/webserver/server.go +++ b/internal/webserver/server.go @@ -132,6 +132,8 @@ func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.StripPrefix("/lists", http.HandlerFunc(app.Lists)).ServeHTTP(w, r) case "bookmarks": app.Bookmarks(w, r) + case "notifications": + app.Notifications(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 cbefe64..f86f6e1 100644 --- a/internal/webserver/server_test.go +++ b/internal/webserver/server_test.go @@ -888,3 +888,22 @@ func TestMessagesPaginate(t *testing.T) { require.NoError(err) assert.Len(cascadia.QueryAll(root, selector(".dm-message")), 2) } + +func TestNotifications(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 + + // Notifications page + recorder := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/notifications", nil) + app.ServeHTTP(recorder, req) + resp := recorder.Result() + root, err := html.Parse(resp.Body) + require.NoError(err) + assert.Len(cascadia.QueryAll(root, selector(".notification")), 6) +} diff --git a/internal/webserver/static/styles.css b/internal/webserver/static/styles.css index 3f35432..4339211 100644 --- a/internal/webserver/static/styles.css +++ b/internal/webserver/static/styles.css @@ -543,7 +543,7 @@ main { /** * Tweet module */ -.tweet { +.tweet, .notification { padding: 0 1.5em; position: relative; z-index: 0; /* Dunno why, but without it, hovering a tweet with a Poll hides the poll fill bars */ @@ -940,6 +940,21 @@ main { } } + +/****************************************************** + * Notifications + ******************************************************/ + +/** + * Notifications module + */ +.notification { + .notification__users { + font-size: 0.8em; /* Make the profile images smaller */ + } +} + + /****************************************************** * Navigation and base page ******************************************************/ diff --git a/internal/webserver/tpl/includes/base.tpl b/internal/webserver/tpl/includes/base.tpl index 83e9771..3f0c50d 100644 --- a/internal/webserver/tpl/includes/base.tpl +++ b/internal/webserver/tpl/includes/base.tpl @@ -35,7 +35,7 @@ /> - {{template "nav-sidebar" (global_data).Notifications}} + {{template "nav-sidebar" (global_data).NotificationBubbles}}
{{template "main" .}}
diff --git a/internal/webserver/tpl/includes/nav_sidebar.tpl b/internal/webserver/tpl/includes/nav_sidebar.tpl index 5b94f0a..bd7804d 100644 --- a/internal/webserver/tpl/includes/nav_sidebar.tpl +++ b/internal/webserver/tpl/includes/nav_sidebar.tpl @@ -20,7 +20,7 @@ {{if (not (eq (active_user).Handle "[nobody]"))}} - +
  • diff --git a/internal/webserver/tpl/notifications.tpl b/internal/webserver/tpl/notifications.tpl new file mode 100644 index 0000000..c462ac8 --- /dev/null +++ b/internal/webserver/tpl/notifications.tpl @@ -0,0 +1,11 @@ +{{define "title"}}Notifications{{end}} + +{{define "main"}} +
    +

    Notifications

    +
    + +
    + {{template "timeline" .}} +
    +{{end}} diff --git a/internal/webserver/tpl/tweet_page_includes/notification.tpl b/internal/webserver/tpl/tweet_page_includes/notification.tpl new file mode 100644 index 0000000..780e9e2 --- /dev/null +++ b/internal/webserver/tpl/tweet_page_includes/notification.tpl @@ -0,0 +1,60 @@ +{{define "notification"}} + {{$notification := (notification .NotificationID)}} + +
    +
    + {{if (not (eq $notification.ActionUserID 0))}} +
    + {{template "circle-profile-img" (user $notification.ActionUserID)}} + {{/*template "author-info" (user $notification.ActionUserID)*/}} + {{if (gt (len $notification.UserIDs) 1)}} + {{$max_display_users := 10}} + {{range $i, $user_id := $notification.UserIDs}} + {{if (ne $user_id $notification.ActionUserID)}} {{/* don't duplicate main user */}} + {{/* Only render the first 10-ish users */}} + {{if (lt $i $max_display_users)}} + {{template "circle-profile-img" (user $user_id)}} + {{end}} + {{end}} + {{end}} + {{if (gt (len $notification.UserIDs) (add $max_display_users 1))}} + ... + {{end}} + {{end}} +
    + {{end}} + +
    + {{if (eq $notification.Type 1)}} {{/* LIKE */}} + {{(user $notification.ActionUserID).DisplayName}} liked your tweet + {{else if (eq $notification.Type 2)}} {{/* RETWEET */}} + {{(user $notification.ActionUserID).DisplayName}} retweeted you + {{else if (eq $notification.Type 3)}} {{/* QUOTE_TWEET */}} + {{(user $notification.ActionUserID).DisplayName}} quote-tweeted you + {{else if (eq $notification.Type 4)}} {{/* REPLY */}} + {{(user $notification.ActionUserID).DisplayName}} replied to you + {{else if (eq $notification.Type 5)}} {{/* FOLLOW */}} + {{(user $notification.ActionUserID).DisplayName}} followed you! + {{else if (eq $notification.Type 6)}} {{/* MENTION */}} + {{(user $notification.ActionUserID).DisplayName}} mentioned you + {{else if (eq $notification.Type 7)}} {{/* USER_IS_LIVE */}} + {{(user $notification.ActionUserID).DisplayName}} is live + {{else if (eq $notification.Type 8)}} {{/* POLL_ENDED */}} + Poll ended. + {{else if (eq $notification.Type 9)}} {{/* LOGIN */}} + New login on your account. + {{else if (eq $notification.Type 10)}} {{/* COMMUNITY_PINNED_POST */}} + {{(user $notification.ActionUserID).DisplayName}} posted in community + {{else if (eq $notification.Type 11)}} {{/* RECOMMENDED_POST */}} + You've been recommended a post from {{(user $notification.ActionUserID).DisplayName}} + {{else}} + {{"<>: "}}{{$notification.Type}} + {{end}} +
    +
    + + {{if (ne .TweetID 0)}} + {{template "tweet" .}} + {{end}} +
    +{{end}} diff --git a/internal/webserver/tpl/tweet_page_includes/timeline.tpl b/internal/webserver/tpl/tweet_page_includes/timeline.tpl index e6daf7e..6aedae1 100644 --- a/internal/webserver/tpl/tweet_page_includes/timeline.tpl +++ b/internal/webserver/tpl/tweet_page_includes/timeline.tpl @@ -1,6 +1,10 @@ {{define "timeline"}} {{range .Items}} - {{template "tweet" .}} + {{if .NotificationID}} + {{template "notification" .}} + {{else}} + {{template "tweet" .}} + {{end}} {{end}}