Add notifications page

This commit is contained in:
Alessio 2024-08-31 23:23:22 -07:00
parent 1f392f5240
commit 665e6a31dd
12 changed files with 136 additions and 10 deletions

View File

@ -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)) app.error_400_with_message(w, r, fmt.Sprintf("User not in database: %s", form.AccountName))
return 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) app.buffered_render_htmx(w, "nav-sidebar", PageGlobalData{}, data)
} }

View File

@ -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)
}

View File

@ -13,6 +13,6 @@ func (app *Application) NavSidebarPollUpdates(w http.ResponseWriter, r *http.Req
return 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) app.buffered_render_htmx(w, "nav-sidebar", PageGlobalData{}, data)
} }

View File

@ -15,7 +15,7 @@ import (
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper" "gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
) )
type Notifications struct { type NotificationBubbles struct {
NumMessageNotifications int NumMessageNotifications int
} }
@ -24,8 +24,8 @@ type PageGlobalData struct {
scraper.TweetTrove scraper.TweetTrove
SearchText string SearchText string
FocusedTweetID scraper.TweetID FocusedTweetID scraper.TweetID
Notifications
Toasts []Toast Toasts []Toast
NotificationBubbles
} }
func (d PageGlobalData) Tweet(id scraper.TweetID) scraper.Tweet { 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 { func (d PageGlobalData) Space(id scraper.SpaceID) scraper.Space {
return d.Spaces[id] 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 { func (d PageGlobalData) Message(id scraper.DMMessageID) scraper.DMMessage {
return d.Messages[id] 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{}) { 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")...) 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{ r := renderer{
Funcs: app.make_funcmap(global_data), Funcs: app.make_funcmap(global_data),
@ -127,6 +130,7 @@ func (app *Application) make_funcmap(global_data PageGlobalData) template.FuncMa
"user": global_data.User, "user": global_data.User,
"retweet": global_data.Retweet, "retweet": global_data.Retweet,
"space": global_data.Space, "space": global_data.Space,
"notification": global_data.Notification,
"dm_message": global_data.Message, "dm_message": global_data.Message,
"chat_room": global_data.ChatRoom, "chat_room": global_data.ChatRoom,
"focused_tweet_id": global_data.GetFocusedTweetID, "focused_tweet_id": global_data.GetFocusedTweetID,

View File

@ -132,6 +132,8 @@ func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
http.StripPrefix("/lists", http.HandlerFunc(app.Lists)).ServeHTTP(w, r) http.StripPrefix("/lists", http.HandlerFunc(app.Lists)).ServeHTTP(w, r)
case "bookmarks": case "bookmarks":
app.Bookmarks(w, r) app.Bookmarks(w, r)
case "notifications":
app.Notifications(w, r)
case "messages": case "messages":
http.StripPrefix("/messages", http.HandlerFunc(app.Messages)).ServeHTTP(w, r) http.StripPrefix("/messages", http.HandlerFunc(app.Messages)).ServeHTTP(w, r)
case "nav-sidebar-poll-updates": case "nav-sidebar-poll-updates":

View File

@ -888,3 +888,22 @@ func TestMessagesPaginate(t *testing.T) {
require.NoError(err) require.NoError(err)
assert.Len(cascadia.QueryAll(root, selector(".dm-message")), 2) 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)
}

View File

@ -543,7 +543,7 @@ main {
/** /**
* Tweet module * Tweet module
*/ */
.tweet { .tweet, .notification {
padding: 0 1.5em; padding: 0 1.5em;
position: relative; position: relative;
z-index: 0; /* Dunno why, but without it, hovering a tweet with a Poll hides the poll fill bars */ 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 * Navigation and base page
******************************************************/ ******************************************************/

View File

@ -35,7 +35,7 @@
/> />
</form> </form>
</header> </header>
{{template "nav-sidebar" (global_data).Notifications}} {{template "nav-sidebar" (global_data).NotificationBubbles}}
<main> <main>
{{template "main" .}} {{template "main" .}}
</main> </main>

View File

@ -20,7 +20,7 @@
</li> </li>
</a> </a>
{{if (not (eq (active_user).Handle "[nobody]"))}} {{if (not (eq (active_user).Handle "[nobody]"))}}
<a href="#"> <a href="/notifications">
<li class="button labelled-icon"> <li class="button labelled-icon">
<img class="svg-icon" src="/static/icons/notifications.svg" width="24" height="24" /> <img class="svg-icon" src="/static/icons/notifications.svg" width="24" height="24" />
<label class="nav-sidebar__button-label">Notifications</label> <label class="nav-sidebar__button-label">Notifications</label>

View File

@ -0,0 +1,11 @@
{{define "title"}}Notifications{{end}}
{{define "main"}}
<div class="notifications-header">
<h2>Notifications</h2>
</div>
<div class="timeline">
{{template "timeline" .}}
</div>
{{end}}

View File

@ -0,0 +1,60 @@
{{define "notification"}}
{{$notification := (notification .NotificationID)}}
<div class="notification">
<div class="notification__header">
{{if (not (eq $notification.ActionUserID 0))}}
<div class="notification__users">
{{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))}}
<span class="ellipsis">...</span>
{{end}}
{{end}}
</div>
{{end}}
<div class="notification__text">
{{if (eq $notification.Type 1)}} {{/* LIKE */}}
<b>{{(user $notification.ActionUserID).DisplayName}} liked your tweet</b>
{{else if (eq $notification.Type 2)}} {{/* RETWEET */}}
<b>{{(user $notification.ActionUserID).DisplayName}} retweeted you</b>
{{else if (eq $notification.Type 3)}} {{/* QUOTE_TWEET */}}
<b>{{(user $notification.ActionUserID).DisplayName}} quote-tweeted you</b>
{{else if (eq $notification.Type 4)}} {{/* REPLY */}}
<b>{{(user $notification.ActionUserID).DisplayName}} replied to you</b>
{{else if (eq $notification.Type 5)}} {{/* FOLLOW */}}
<b>{{(user $notification.ActionUserID).DisplayName}} followed you!</b>
{{else if (eq $notification.Type 6)}} {{/* MENTION */}}
<b>{{(user $notification.ActionUserID).DisplayName}} mentioned you</b>
{{else if (eq $notification.Type 7)}} {{/* USER_IS_LIVE */}}
<b>{{(user $notification.ActionUserID).DisplayName}} is live</b>
{{else if (eq $notification.Type 8)}} {{/* POLL_ENDED */}}
<b>Poll ended.</b>
{{else if (eq $notification.Type 9)}} {{/* LOGIN */}}
<b>New login on your account.</b>
{{else if (eq $notification.Type 10)}} {{/* COMMUNITY_PINNED_POST */}}
<b>{{(user $notification.ActionUserID).DisplayName}} posted in community</b>
{{else if (eq $notification.Type 11)}} {{/* RECOMMENDED_POST */}}
<b>You've been recommended a post from {{(user $notification.ActionUserID).DisplayName}}</b>
{{else}}
<b>{{"<<UNKNOWN ID>>: "}}{{$notification.Type}}</b>
{{end}}
</div>
</div>
{{if (ne .TweetID 0)}}
{{template "tweet" .}}
{{end}}
</div>
{{end}}

View File

@ -1,7 +1,11 @@
{{define "timeline"}} {{define "timeline"}}
{{range .Items}} {{range .Items}}
{{if .NotificationID}}
{{template "notification" .}}
{{else}}
{{template "tweet" .}} {{template "tweet" .}}
{{end}} {{end}}
{{end}}
<div class="show-more"> <div class="show-more">
{{if .CursorBottom.CursorPosition.IsEnd}} {{if .CursorBottom.CursorPosition.IsEnd}}