From 28111585efaf6e4f22b52c48e6d7906e3a361b17 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Wed, 28 Feb 2024 20:49:16 -0800 Subject: [PATCH] Notification Filters --- pkg/controller/account/dashboard.go | 6 +- pkg/models/notification.go | 12 ++- pkg/models/notification_filters.go | 92 ++++++++++++++++++ web/static/js/bulma.js | 5 +- web/templates/account/dashboard.html | 140 +++++++++++++++++++++++++-- 5 files changed, 243 insertions(+), 12 deletions(-) create mode 100644 pkg/models/notification_filters.go diff --git a/pkg/controller/account/dashboard.go b/pkg/controller/account/dashboard.go index ffb81e5..68e8869 100644 --- a/pkg/controller/account/dashboard.go +++ b/pkg/controller/account/dashboard.go @@ -43,6 +43,9 @@ func Dashboard() http.HandlerFunc { return } + // Parse notification filters. + nf := models.NewNotificationFilterFromForm(r) + // Get our notifications. pager := &models.Pagination{ Page: 1, @@ -50,7 +53,7 @@ func Dashboard() http.HandlerFunc { Sort: "created_at desc", } pager.ParsePage(r) - notifs, err := models.PaginateNotifications(currentUser, pager) + notifs, err := models.PaginateNotifications(currentUser, nf, pager) if err != nil { session.FlashError(w, r, "Couldn't get your notifications: %s", err) } @@ -86,6 +89,7 @@ func Dashboard() http.HandlerFunc { var vars = map[string]interface{}{ "Notifications": notifs, "NotifMap": notifMap, + "Filters": nf, "Pager": pager, // Show a warning to 'restricted' profiles who are especially private. diff --git a/pkg/models/notification.go b/pkg/models/notification.go index 1fa7f4b..56e54e4 100644 --- a/pkg/models/notification.go +++ b/pkg/models/notification.go @@ -41,13 +41,13 @@ const ( NotificationAlsoPosted NotificationType = "also_posted" // forum replies NotificationCertRejected NotificationType = "cert_rejected" NotificationCertApproved NotificationType = "cert_approved" - NotificationPrivatePhoto NotificationType = "private_photo" + NotificationPrivatePhoto NotificationType = "private_photo" // private photo grants NotificationNewPhoto NotificationType = "new_photo" NotificationInnerCircle NotificationType = "inner_circle" NotificationCustom NotificationType = "custom" // custom message pushed ) -// CreateNotification +// CreateNotification inserts a new notification into the database. func CreateNotification(n *Notification) error { // Insert via raw SQL query, reasoning: // the AboutUser relationship has gorm do way too much work: @@ -204,7 +204,7 @@ func CountUnreadNotifications(user *User) (int64, error) { } // PaginateNotifications returns the user's notifications. -func PaginateNotifications(user *User, pager *Pagination) ([]*Notification, error) { +func PaginateNotifications(user *User, filters NotificationFilter, pager *Pagination) ([]*Notification, error) { var ( ns = []*Notification{} blockedUserIDs = BlockedUserIDs(user) @@ -232,6 +232,12 @@ func PaginateNotifications(user *User, pager *Pagination) ([]*Notification, erro ) `) + // Mix in notification type filters? + if w, ph, ok := filters.Query(); ok { + where = append(where, w) + placeholders = append(placeholders, ph) + } + query := (&Notification{}).Preload().Where( strings.Join(where, " AND "), placeholders..., diff --git a/pkg/models/notification_filters.go b/pkg/models/notification_filters.go new file mode 100644 index 0000000..ded89b8 --- /dev/null +++ b/pkg/models/notification_filters.go @@ -0,0 +1,92 @@ +package models + +import ( + "net/http" +) + +// NotificationFilter handles users filtering their notification list by category. It is populated +// from front-end checkboxes and translates to SQL filters for PaginateNotifications. +type NotificationFilter struct { + Likes bool `json:"likes"` // form field name + Comments bool `json:"comments"` + NewPhotos bool `json:"photos"` + AlsoCommented bool `json:"replies"` // also_comment and also_posted + PrivatePhoto bool `json:"private"` + Misc bool `json:"misc"` // friendship_approved, cert_approved, cert_rejected, inner_circle, custom +} + +var defaultNotificationFilter = NotificationFilter{ + Likes: true, + Comments: true, + NewPhotos: true, + AlsoCommented: true, + PrivatePhoto: true, + Misc: true, +} + +// NewNotificationFilterFromForm creates a NotificationFilter struct parsed from an HTTP form. +func NewNotificationFilterFromForm(r *http.Request) NotificationFilter { + // Are these boxes checked in a frontend post? + var ( + nf = NotificationFilter{ + Likes: r.FormValue("likes") == "true", + Comments: r.FormValue("comments") == "true", + NewPhotos: r.FormValue("photos") == "true", + AlsoCommented: r.FormValue("replies") == "true", + PrivatePhoto: r.FormValue("private") == "true", + Misc: r.FormValue("misc") == "true", + } + ) + + // Default view or when no checkboxes were sent, all are true. + if nf.IsZero() { + return defaultNotificationFilter + } + return nf +} + +// IsZero checks for an empty filter. +func (nf NotificationFilter) IsZero() bool { + return nf == NotificationFilter{} +} + +// IsAll checks if all filters are checked. +func (nf NotificationFilter) IsAll() bool { + return nf == defaultNotificationFilter +} + +// Query returns the SQL "WHERE" clause that applies the filters to the Notifications query. +// +// If no filters should be added, ok returns false. +func (nf NotificationFilter) Query() (where string, placeholders []interface{}, ok bool) { + if nf.IsAll() { + return "", nil, false + } + + var ( + // Notification types to include. + types = []interface{}{} + ) + + // Translate + if nf.Likes { + types = append(types, NotificationLike) + } + if nf.Comments { + types = append(types, NotificationComment) + } + if nf.NewPhotos { + types = append(types, NotificationNewPhoto) + } + if nf.AlsoCommented { + types = append(types, NotificationAlsoCommented, NotificationAlsoPosted) + } + if nf.PrivatePhoto { + types = append(types, NotificationPrivatePhoto) + } + if nf.Misc { + types = append(types, NotificationFriendApproved, NotificationCertApproved, NotificationCertRejected, NotificationCustom) + } + + return "type IN ?", types, true +} diff --git a/web/static/js/bulma.js b/web/static/js/bulma.js index b954066..9296c08 100644 --- a/web/static/js/bulma.js +++ b/web/static/js/bulma.js @@ -97,14 +97,15 @@ document.addEventListener('DOMContentLoaded', () => { (document.querySelectorAll(".card.nonshy-collapsible-mobile") || []).forEach(node => { const header = node.querySelector(".card-header"), body = node.querySelector(".card-content"), - icon = header.querySelector("button.card-header-icon > .icon > i"); + icon = header.querySelector("button.card-header-icon > .icon > i"), + always = node.classList.contains("nonshy-collapsible-always"); // Icon classes. const iconExpanded = "fa-angle-up", iconContracted = "fa-angle-down"; // If we are already on mobile, hide the body now. - if (screen.width <= 768) { + if (screen.width <= 768 || always) { body.style.display = "none"; if (icon !== null) { icon.classList.remove(iconExpanded); diff --git a/web/templates/account/dashboard.html b/web/templates/account/dashboard.html index b61b5bc..603f32f 100644 --- a/web/templates/account/dashboard.html +++ b/web/templates/account/dashboard.html @@ -321,12 +321,140 @@
-

- - - Manage notification settings - -

+ +
+
+ +
+ +
+ +

+ + + Manage notification settings + +

+ +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ +
+ + Reset + + +
+ +
+