Show unread notifications count bubble in web UI; add background scraping of notifications every 10s

This commit is contained in:
Alessio 2024-09-02 17:00:30 -07:00
parent 72b547f6aa
commit 14ea626014
8 changed files with 94 additions and 9 deletions

View File

@ -118,6 +118,13 @@ 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 := NotificationBubbles{NumMessageNotifications: len(app.Profile.GetUnreadConversations(app.ActiveUser.ID))} app.LastReadNotificationSortIndex = 0 // Clear unread notifications
go app.background_notifications_scrape() // Update notifications info in background (avoid latency when switching users)
data := NotificationBubbles{
NumMessageNotifications: len(app.Profile.GetUnreadConversations(app.ActiveUser.ID)),
}
if app.LastReadNotificationSortIndex != 0 {
data.NumRegularNotifications = app.Profile.GetUnreadNotificationsCount(app.ActiveUser.ID, app.LastReadNotificationSortIndex)
}
app.buffered_render_htmx(w, "nav-sidebar", PageGlobalData{}, data) app.buffered_render_htmx(w, "nav-sidebar", PageGlobalData{}, data)
} }

View File

@ -13,6 +13,11 @@ func (app *Application) NavSidebarPollUpdates(w http.ResponseWriter, r *http.Req
return return
} }
data := NotificationBubbles{NumMessageNotifications: len(app.Profile.GetUnreadConversations(app.ActiveUser.ID))} data := NotificationBubbles{
NumMessageNotifications: len(app.Profile.GetUnreadConversations(app.ActiveUser.ID)),
}
if app.LastReadNotificationSortIndex != 0 {
data.NumRegularNotifications = app.Profile.GetUnreadNotificationsCount(app.ActiveUser.ID, app.LastReadNotificationSortIndex)
}
app.buffered_render_htmx(w, "nav-sidebar", PageGlobalData{}, data) app.buffered_render_htmx(w, "nav-sidebar", PageGlobalData{}, data)
} }

View File

@ -17,6 +17,7 @@ import (
type NotificationBubbles struct { type NotificationBubbles struct {
NumMessageNotifications int NumMessageNotifications int
NumRegularNotifications int
} }
// TODO: this name sucks // TODO: this name sucks
@ -99,6 +100,12 @@ func (app *Application) buffered_render_page(w http.ResponseWriter, tpl_file str
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.NotificationBubbles.NumMessageNotifications = len(app.Profile.GetUnreadConversations(app.ActiveUser.ID)) global_data.NotificationBubbles.NumMessageNotifications = len(app.Profile.GetUnreadConversations(app.ActiveUser.ID))
if app.LastReadNotificationSortIndex != 0 {
global_data.NotificationBubbles.NumRegularNotifications = app.Profile.GetUnreadNotificationsCount(
app.ActiveUser.ID,
app.LastReadNotificationSortIndex,
)
}
r := renderer{ r := renderer{
Funcs: app.make_funcmap(global_data), Funcs: app.make_funcmap(global_data),

View File

@ -28,10 +28,11 @@ type Application struct {
Middlewares []Middleware Middlewares []Middleware
Profile persistence.Profile Profile persistence.Profile
ActiveUser scraper.User ActiveUser scraper.User
IsScrapingDisabled bool IsScrapingDisabled bool
API scraper.API API scraper.API
LastReadNotificationSortIndex int64
} }
func NewApp(profile persistence.Profile) Application { func NewApp(profile persistence.Profile) Application {

View File

@ -945,8 +945,12 @@ main {
* Notifications * Notifications
******************************************************/ ******************************************************/
.notifications-header {
border-bottom: 1px solid var(--color-outline-gray);
}
/** /**
* Notifications module * Notification module
*/ */
.notification { .notification {
.notification__users { .notification__users {

View File

@ -118,6 +118,41 @@ func (app *Application) background_dm_polling_scrape() {
fmt.Println("Scraping DMs succeeded.") fmt.Println("Scraping DMs succeeded.")
} }
func (app *Application) background_notifications_scrape() {
// Avoid crashing the thread if a scrape fails
defer func() {
if r := recover(); r != nil {
// TODO
fmt.Println("Background notifications thread: panicked!")
if err, ok := r.(error); ok {
fmt.Println(err.Error())
} else {
fmt.Println(r)
}
}
}()
fmt.Println("Starting notifications scrape...")
// Do nothing if scraping is currently disabled
if app.IsScrapingDisabled {
fmt.Println("Skipping notifications scrape!")
return
}
fmt.Println("Scraping user notifications...")
trove, last_unread_notification_sort_index, err := app.API.GetNotifications(1) // Just 1 page
if err != nil {
panic(err)
}
// Jot down the unread notifs info in the application object (to render notification count bubble)
app.LastReadNotificationSortIndex = last_unread_notification_sort_index
fmt.Println("Saving notification results...")
app.Profile.SaveTweetTrove(trove, false, &app.API)
go app.Profile.SaveTweetTrove(trove, true, &app.API)
fmt.Println("Scraping notification succeeded.")
}
func (app *Application) start_background() { func (app *Application) start_background() {
fmt.Println("Starting background") fmt.Println("Starting background")
@ -163,4 +198,16 @@ func (app *Application) start_background() {
app.background_dm_polling_scrape() app.background_dm_polling_scrape()
} }
}() }()
// Scrape notifications every 10 seconds
go func() {
app.background_notifications_scrape()
interval := 10 * time.Second
timer := time.NewTicker(interval)
defer timer.Stop()
for range timer.C {
app.background_notifications_scrape()
}
}()
} }

View File

@ -21,8 +21,11 @@
</a> </a>
{{if (not (eq (active_user).Handle "[nobody]"))}} {{if (not (eq (active_user).Handle "[nobody]"))}}
<a href="/notifications"> <a href="/notifications">
<li class="button labelled-icon"> <li class="nav-sidebar__notifications 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" />
{{if .NumRegularNotifications}}
<span class="nav-sidebar__notifications-count">{{.NumRegularNotifications}}</span>
{{end}}
<label class="nav-sidebar__button-label">Notifications</label> <label class="nav-sidebar__button-label">Notifications</label>
</li> </li>
</a> </a>

View File

@ -2,7 +2,18 @@
{{define "main"}} {{define "main"}}
<div class="notifications-header"> <div class="notifications-header">
<h2>Notifications</h2> <div class="row row--spread">
<div class="dummy"></div> {{/* Extra div to take up a slot in the `row` */}}
<h1>Notifications</h1>
<div class="row">
<a class="button" target="_blank" href="https://twitter.com/notifications" title="Open on twitter.com">
<img class="svg-icon" src="/static/icons/external-link.svg" width="24" height="24" />
</a>
<a class="button" hx-get="?scrape" hx-target="body" hx-indicator=".search-header" title="Refresh">
<img class="svg-icon" src="/static/icons/refresh.svg" width="24" height="24" />
</a>
</div>
</div>
</div> </div>
<div class="timeline"> <div class="timeline">