Notification Filters
This commit is contained in:
parent
dd24aa1987
commit
28111585ef
|
@ -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.
|
||||
|
|
|
@ -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...,
|
||||
|
|
92
pkg/models/notification_filters.go
Normal file
92
pkg/models/notification_filters.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -321,12 +321,140 @@
|
|||
<hr>
|
||||
</div>
|
||||
|
||||
<p class="block">
|
||||
<a href="/settings#notifications">
|
||||
<i class="fa fa-gear mr-1"></i>
|
||||
Manage notification settings
|
||||
</a>
|
||||
</p>
|
||||
<!-- Filters -->
|
||||
<div class="block">
|
||||
<form action="{{.Request.URL.Path}}" method="GET">
|
||||
|
||||
<div class="card nonshy-collapsible-mobile nonshy-collapsible-always mb-5">
|
||||
<header class="card-header has-background-link-light">
|
||||
<p class="card-header-title">
|
||||
<i class="fa fa-list mr-2"></i> Notification Types
|
||||
</p>
|
||||
<button class="card-header-icon" type="button">
|
||||
<span class="icon">
|
||||
<i class="fa fa-angle-up"></i>
|
||||
</span>
|
||||
</button>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
|
||||
<p class="block">
|
||||
<a href="/settings#notifications">
|
||||
<i class="fa fa-gear mr-1"></i>
|
||||
Manage notification settings
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<div class="columns is-multiline mb-0">
|
||||
|
||||
<div class="column is-half">
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="likes"
|
||||
value="true"
|
||||
{{if .Filters.Likes}}checked{{end}}
|
||||
>
|
||||
Likes
|
||||
<p class="help">
|
||||
on your photos, profile or comments
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-half">
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="comments"
|
||||
value="true"
|
||||
{{if .Filters.Comments}}checked{{end}}
|
||||
>
|
||||
Comments
|
||||
<p class="help">
|
||||
on your photos
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-half">
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="photos"
|
||||
value="true"
|
||||
{{if .Filters.NewPhotos}}checked{{end}}
|
||||
>
|
||||
New Photos
|
||||
<p class="help">
|
||||
of your friends
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-half">
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="replies"
|
||||
value="true"
|
||||
{{if .Filters.AlsoCommented}}checked{{end}}
|
||||
>
|
||||
Replies
|
||||
<p class="help">
|
||||
on comment threads you follow
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-half">
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="private"
|
||||
value="true"
|
||||
{{if .Filters.PrivatePhoto}}checked{{end}}
|
||||
>
|
||||
Private photos
|
||||
<p class="help">
|
||||
unlock notifications
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-half">
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="misc"
|
||||
value="true"
|
||||
{{if .Filters.Misc}}checked{{end}}
|
||||
>
|
||||
Miscellaneous
|
||||
<p class="help">
|
||||
new friends, certification photos, etc.
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block has-text-centered">
|
||||
<a href="{{.Request.URL.Path}}" class="button">
|
||||
Reset
|
||||
</a>
|
||||
<button type="submit" class="button is-success">
|
||||
Apply Filters
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table is-striped is-fullwidth is-hoverable">
|
||||
<tbody>
|
||||
|
|
Loading…
Reference in New Issue
Block a user