Mute specific friends new photo upload notifications
This commit is contained in:
parent
01c38c5c21
commit
147a9162ba
|
@ -28,6 +28,19 @@ func Subscription() http.HandlerFunc {
|
||||||
templates.Redirect(w, "/")
|
templates.Redirect(w, "/")
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
|
// Is the table_id expected to be a username?
|
||||||
|
switch tableName {
|
||||||
|
case "friend.photos":
|
||||||
|
// Special "Friend uploaded a new photo" opt-out.
|
||||||
|
if user, err := models.FindUser(idStr); err != nil {
|
||||||
|
session.FlashError(w, r, "Username not found!")
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
tableID = user.ID
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Integer IDs in all other cases.
|
||||||
if idInt, err := strconv.Atoi(idStr); err != nil {
|
if idInt, err := strconv.Atoi(idStr); err != nil {
|
||||||
session.FlashError(w, r, "Comment table ID invalid.")
|
session.FlashError(w, r, "Comment table ID invalid.")
|
||||||
templates.Redirect(w, "/")
|
templates.Redirect(w, "/")
|
||||||
|
@ -36,6 +49,7 @@ func Subscription() http.HandlerFunc {
|
||||||
tableID = uint64(idInt)
|
tableID = uint64(idInt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Redirect URL must be relative.
|
// Redirect URL must be relative.
|
||||||
if !strings.HasPrefix(fromURL, "/") {
|
if !strings.HasPrefix(fromURL, "/") {
|
||||||
|
@ -47,7 +61,7 @@ func Subscription() http.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate everything else.
|
// Validate everything else.
|
||||||
if _, ok := models.CommentableTables[tableName]; !ok {
|
if _, ok := models.SubscribableTables[tableName]; !ok {
|
||||||
session.FlashError(w, r, "You can not comment on that.")
|
session.FlashError(w, r, "You can not comment on that.")
|
||||||
templates.Redirect(w, "/")
|
templates.Redirect(w, "/")
|
||||||
return
|
return
|
||||||
|
@ -61,6 +75,12 @@ func Subscription() http.HandlerFunc {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Language to use in the flash messages.
|
||||||
|
var kind = "comments"
|
||||||
|
if tableName == "friend.photos" {
|
||||||
|
kind = "new photo uploads"
|
||||||
|
}
|
||||||
|
|
||||||
// Get their subscription.
|
// Get their subscription.
|
||||||
sub, err := models.GetSubscription(currentUser, tableName, tableID)
|
sub, err := models.GetSubscription(currentUser, tableName, tableID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -69,7 +89,15 @@ func Subscription() http.HandlerFunc {
|
||||||
if _, err := models.SubscribeTo(currentUser, tableName, tableID); err != nil {
|
if _, err := models.SubscribeTo(currentUser, tableName, tableID); err != nil {
|
||||||
session.FlashError(w, r, "Couldn't create subscription: %s", err)
|
session.FlashError(w, r, "Couldn't create subscription: %s", err)
|
||||||
} else {
|
} else {
|
||||||
session.Flash(w, r, "You will now be notified about comments on this page.")
|
session.Flash(w, r, "You will now be notified about %s on this page.", kind)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// An explicit subscribe=false, may be a preemptive opt-out as in
|
||||||
|
// friend new photo notifications.
|
||||||
|
if _, err := models.UnsubscribeTo(currentUser, tableName, tableID); err != nil {
|
||||||
|
session.FlashError(w, r, "Couldn't create subscription: %s", err)
|
||||||
|
} else {
|
||||||
|
session.Flash(w, r, "You will no longer be notified about %s on this page.", kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -79,9 +107,9 @@ func Subscription() http.HandlerFunc {
|
||||||
session.FlashError(w, r, "Couldn't save your subscription settings: %s", err)
|
session.FlashError(w, r, "Couldn't save your subscription settings: %s", err)
|
||||||
} else {
|
} else {
|
||||||
if subscribe {
|
if subscribe {
|
||||||
session.Flash(w, r, "You will now be notified about comments on this page.")
|
session.Flash(w, r, "You will now be notified about %s on this page.", kind)
|
||||||
} else {
|
} else {
|
||||||
session.Flash(w, r, "You will no longer be notified about new comments on that thread.")
|
session.Flash(w, r, "You will no longer be notified about new %s on this page.", kind)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -195,6 +195,16 @@ func UserPhotos() http.HandlerFunc {
|
||||||
profilePictureHidden = visibility
|
profilePictureHidden = visibility
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Friend Photos Notification Opt-out:
|
||||||
|
// If your friend posts too many photos and you want to mute them.
|
||||||
|
// NOTE: notifications are "on by default" and only an explicit "false"
|
||||||
|
// stored in the database indicates an opt-out.
|
||||||
|
// New photo upload notification subscription status.
|
||||||
|
var areNotificationsMuted bool
|
||||||
|
if exists, v := models.IsSubscribed(currentUser, "friend.photos", user.ID); exists {
|
||||||
|
areNotificationsMuted = !v
|
||||||
|
}
|
||||||
|
|
||||||
var vars = map[string]interface{}{
|
var vars = map[string]interface{}{
|
||||||
"IsOwnPhotos": currentUser.ID == user.ID,
|
"IsOwnPhotos": currentUser.ID == user.ID,
|
||||||
"IsShyUser": isShy,
|
"IsShyUser": isShy,
|
||||||
|
@ -202,6 +212,7 @@ func UserPhotos() http.HandlerFunc {
|
||||||
"IsMyPrivateUnlockedFor": isGranted, // have WE granted THIS USER to see our private pics?
|
"IsMyPrivateUnlockedFor": isGranted, // have WE granted THIS USER to see our private pics?
|
||||||
"AreWeGrantedPrivate": isGrantee, // have THEY granted US private photo access.
|
"AreWeGrantedPrivate": isGrantee, // have THEY granted US private photo access.
|
||||||
"AreFriends": areFriends,
|
"AreFriends": areFriends,
|
||||||
|
"AreNotificationsMuted": areNotificationsMuted,
|
||||||
"ProfilePictureHiddenVisibility": profilePictureHidden,
|
"ProfilePictureHiddenVisibility": profilePictureHidden,
|
||||||
"User": user,
|
"User": user,
|
||||||
"Photos": photos,
|
"Photos": photos,
|
||||||
|
|
|
@ -29,6 +29,16 @@ var CommentableTables = map[string]interface{}{
|
||||||
"threads": nil,
|
"threads": nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubscribableTables are the set of table names that allow notification subscriptions.
|
||||||
|
var SubscribableTables = map[string]interface{}{
|
||||||
|
"photos": nil,
|
||||||
|
"threads": nil,
|
||||||
|
|
||||||
|
// Special case: new photo uploads from your friends. You can't comment on this,
|
||||||
|
// but you can (un)subscribe from it all the same.
|
||||||
|
"friend.photos": nil,
|
||||||
|
}
|
||||||
|
|
||||||
// Preload related tables for the forum (classmethod).
|
// Preload related tables for the forum (classmethod).
|
||||||
func (c *Comment) Preload() *gorm.DB {
|
func (c *Comment) Preload() *gorm.DB {
|
||||||
return DB.Preload("User.ProfilePhoto")
|
return DB.Preload("User.ProfilePhoto")
|
||||||
|
|
|
@ -288,8 +288,10 @@ func FilterPhotoUploadNotificationUserIDs(photo *Photo, userIDs []uint64) []uint
|
||||||
result = []uint64{}
|
result = []uint64{}
|
||||||
|
|
||||||
// Collect notification opt-out profile fields and map them by user ID for easy lookup.
|
// Collect notification opt-out profile fields and map them by user ID for easy lookup.
|
||||||
prefs = []*ProfileField{}
|
prefs = []*ProfileField{} // Global Notification preferences
|
||||||
|
mutes = []*Subscription{} // Individual "friend.photos" notification mutes
|
||||||
mapPrefs = map[uint64]map[string]bool{}
|
mapPrefs = map[uint64]map[string]bool{}
|
||||||
|
mapMutes = map[uint64]bool{}
|
||||||
)
|
)
|
||||||
if len(userIDs) == 0 {
|
if len(userIDs) == 0 {
|
||||||
return userIDs
|
return userIDs
|
||||||
|
@ -308,6 +310,15 @@ func FilterPhotoUploadNotificationUserIDs(photo *Photo, userIDs []uint64) []uint
|
||||||
log.Error("FilterPhotoUploadNotificationUserIDs: couldn't collect user preferences: %s", r.Error)
|
log.Error("FilterPhotoUploadNotificationUserIDs: couldn't collect user preferences: %s", r.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Collect any muted notification threads, e.g. the user doesn't want your new photo notifications.
|
||||||
|
r = DB.Model(&Subscription{}).Where(
|
||||||
|
"table_name = 'friend.photos' AND table_id = ? AND subscribed IS FALSE",
|
||||||
|
photo.UserID,
|
||||||
|
).Find(&mutes)
|
||||||
|
if r.Error != nil {
|
||||||
|
log.Error("FilterPhotoUploadNotificationUserIDs: couldn't collect user notification mutes: %s", r.Error)
|
||||||
|
}
|
||||||
|
|
||||||
// Map the preferences by user ID.
|
// Map the preferences by user ID.
|
||||||
for _, row := range prefs {
|
for _, row := range prefs {
|
||||||
if _, ok := mapPrefs[row.UserID]; !ok {
|
if _, ok := mapPrefs[row.UserID]; !ok {
|
||||||
|
@ -315,6 +326,9 @@ func FilterPhotoUploadNotificationUserIDs(photo *Photo, userIDs []uint64) []uint
|
||||||
}
|
}
|
||||||
mapPrefs[row.UserID][row.Name] = row.Value == "true"
|
mapPrefs[row.UserID][row.Name] = row.Value == "true"
|
||||||
}
|
}
|
||||||
|
for _, row := range mutes {
|
||||||
|
mapMutes[row.UserID] = true
|
||||||
|
}
|
||||||
|
|
||||||
// Narrow the notification recipients based on photo property and their preferences.
|
// Narrow the notification recipients based on photo property and their preferences.
|
||||||
for _, userID := range userIDs {
|
for _, userID := range userIDs {
|
||||||
|
@ -333,6 +347,11 @@ func FilterPhotoUploadNotificationUserIDs(photo *Photo, userIDs []uint64) []uint
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// They muted your friend "new photo" notifications?
|
||||||
|
if mapMutes[userID] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// They get the notification.
|
// They get the notification.
|
||||||
result = append(result, userID)
|
result = append(result, userID)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ type Subscription struct {
|
||||||
ID uint64 `gorm:"primaryKey"`
|
ID uint64 `gorm:"primaryKey"`
|
||||||
UserID uint64 `gorm:"index"` // who it belongs to
|
UserID uint64 `gorm:"index"` // who it belongs to
|
||||||
Subscribed bool `gorm:"index"`
|
Subscribed bool `gorm:"index"`
|
||||||
TableName string // on which of your tables (photos, comments, ...)
|
TableName string `gorm:"index"` // on which of your tables (photos, comments, ...)
|
||||||
TableID uint64
|
TableID uint64 `gorm:"index"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,9 @@ func IsSubscribed(user *User, tableName string, tableID uint64) (exists bool, no
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubscribeTo creates a subscription to a thing (comment thread) to be notified of future activity on.
|
// SubscribeTo creates a subscription to a thing (comment thread) to be notified of future activity on.
|
||||||
|
//
|
||||||
|
// If a Subscription row already exists, it is NOT modified. So if a user has expressly opted out of a
|
||||||
|
// comment thread, they do not get re-subscribed when they comment on it again.
|
||||||
func SubscribeTo(user *User, tableName string, tableID uint64) (*Subscription, error) {
|
func SubscribeTo(user *User, tableName string, tableID uint64) (*Subscription, error) {
|
||||||
// Is there already a subscription row?
|
// Is there already a subscription row?
|
||||||
if sub, err := GetSubscription(user, tableName, tableID); err == nil {
|
if sub, err := GetSubscription(user, tableName, tableID); err == nil {
|
||||||
|
@ -98,6 +101,26 @@ func SubscribeTo(user *User, tableName string, tableID uint64) (*Subscription, e
|
||||||
return sub, result.Error
|
return sub, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnsubscribeTo will create an explicit opt-out Subscription only if no subscription exists.
|
||||||
|
//
|
||||||
|
// It is the inverse of SubscribeTo.
|
||||||
|
func UnsubscribeTo(user *User, tableName string, tableID uint64) (*Subscription, error) {
|
||||||
|
// Is there already a subscription row?
|
||||||
|
if sub, err := GetSubscription(user, tableName, tableID); err == nil {
|
||||||
|
return sub, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the default subscription.
|
||||||
|
sub := &Subscription{
|
||||||
|
UserID: user.ID,
|
||||||
|
Subscribed: false,
|
||||||
|
TableName: tableName,
|
||||||
|
TableID: tableID,
|
||||||
|
}
|
||||||
|
result := DB.Create(sub)
|
||||||
|
return sub, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
// Save a subscription.
|
// Save a subscription.
|
||||||
func (n *Subscription) Save() error {
|
func (n *Subscription) Save() error {
|
||||||
return DB.Save(n).Error
|
return DB.Save(n).Error
|
||||||
|
|
|
@ -669,10 +669,19 @@
|
||||||
<a href="/comments/subscription?table_name={{.TableName}}&table_id={{.TableID}}&next={{UrlEncode $Root.Request.URL.String}}&subscribe=false"
|
<a href="/comments/subscription?table_name={{.TableName}}&table_id={{.TableID}}&next={{UrlEncode $Root.Request.URL.String}}&subscribe=false"
|
||||||
class="has-text-warning is-small"
|
class="has-text-warning is-small"
|
||||||
title="Turn off notifications about this thread"
|
title="Turn off notifications about this thread"
|
||||||
onclick="return confirm('Do you want to turn off notifications about this comment thread?')">
|
onclick="return confirm('Do you want to TURN OFF notifications about this comment thread?')">
|
||||||
<i class="fa fa-microphone-slash mr-1"></i> Mute this thread
|
<i class="fa fa-microphone-slash mr-1"></i> Mute this thread
|
||||||
</a>
|
</a>
|
||||||
</small>
|
</small>
|
||||||
|
{{else if eq .Type "new_photo"}}
|
||||||
|
<small>
|
||||||
|
<a href="/comments/subscription?table_name=friend.photos&table_id={{.AboutUser.Username}}&next={{UrlEncode $Root.Request.URL.String}}&subscribe=false"
|
||||||
|
class="has-text-warning is-small"
|
||||||
|
title="Turn off notifications about @{{.AboutUser.Username}}'s new photo uploads"
|
||||||
|
onclick="return confirm('Do you want to TURN OFF notifications about @{{.AboutUser.Username}}\'s new photo uploads?\n\nNote: to re-subscribe to their new photo notifications, see the link at the top of @{{.AboutUser.Username}}\'s Photo Gallery page.')">
|
||||||
|
<i class="fa fa-microphone-slash mr-1"></i> Mute these notifications
|
||||||
|
</a>
|
||||||
|
</small>
|
||||||
{{end}}
|
{{end}}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -255,7 +255,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<div class="level">
|
<div class="level mb-2">
|
||||||
<div class="level-left">
|
<div class="level-left">
|
||||||
<div class="level-item">
|
<div class="level-item">
|
||||||
{{if .Pager.Total}}
|
{{if .Pager.Total}}
|
||||||
|
@ -290,6 +290,23 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Show an "Unsubscribe to this user's new photo notifications" if you are Friends. -->
|
||||||
|
{{if .AreFriends}}
|
||||||
|
<p class="block">
|
||||||
|
<a href="/comments/subscription?table_name=friend.photos&table_id={{.User.Username}}&next={{UrlEncode .Request.URL.String}}&subscribe={{if .AreNotificationsMuted}}true{{else}}false{{end}}"
|
||||||
|
class="{{if .AreNotificationsMuted}}has-text-success{{else}}{{end}}">
|
||||||
|
<span class="icon"><i class="fa fa-bell{{if not .AreNotificationsMuted}}-slash{{end}}"></i></span>
|
||||||
|
<span>
|
||||||
|
{{if .AreNotificationsMuted}}
|
||||||
|
Enable notifications about <strong>{{.User.Username}}</strong>'s new photos
|
||||||
|
{{else}}
|
||||||
|
Mute notifications about <strong>{{.User.Username}}</strong>'s new photos
|
||||||
|
{{end}}
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<!-- If viewing our own profile, and we don't have a profile picture set, offer advice. -->
|
<!-- If viewing our own profile, and we don't have a profile picture set, offer advice. -->
|
||||||
{{if and (not .IsSiteGallery) (eq .CurrentUser.ProfilePhoto.ID 0) (eq .CurrentUser.ID .User.ID)}}
|
{{if and (not .IsSiteGallery) (eq .CurrentUser.ProfilePhoto.ID 0) (eq .CurrentUser.ID .User.ID)}}
|
||||||
<div class="notification is-success is-light content">
|
<div class="notification is-success is-light content">
|
||||||
|
|
Loading…
Reference in New Issue
Block a user