Clear/Delete Notifications, Private Photo Fixes
Added the ability to delete or clear notifications. * A "Clear all" button deletes them all (with confirmation) * A "Remove" button on individual notifications (one confirmation per page load, so you can remove several without too much tedium) Fix some things regarding private photo notifications: * When notifying your existing grants about a new upload, only users who opt-in for Explicit are notified about Explicit private pictures. * When revoking private grants, clean up the "has uploaded a new private photo" notifications for all of your pics from their notification feeds.
This commit is contained in:
parent
47aaf15078
commit
fdc410c9f1
|
@ -20,10 +20,25 @@ func Dashboard() http.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark all notifications read?
|
// Mark all notifications read?
|
||||||
if r.FormValue("intent") == "read-notifications" {
|
if r.Method == http.MethodPost {
|
||||||
models.MarkNotificationsRead(currentUser)
|
switch r.FormValue("intent") {
|
||||||
session.Flash(w, r, "All of your notifications have been marked as 'read!'")
|
case "read-notifications":
|
||||||
templates.Redirect(w, "/me")
|
if err := models.MarkNotificationsRead(currentUser); err != nil {
|
||||||
|
session.FlashError(w, r, "Error marking your notifications as read: %s", err)
|
||||||
|
} else {
|
||||||
|
session.Flash(w, r, "All of your notifications have been marked as 'read!'")
|
||||||
|
}
|
||||||
|
case "clear-all":
|
||||||
|
if err := models.ClearAllNotifications(currentUser); err != nil {
|
||||||
|
session.FlashError(w, r, "Error clearing your notifications: %s", err)
|
||||||
|
} else {
|
||||||
|
session.Flash(w, r, "All of your notifications have been cleared!")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
session.FlashError(w, r, "Unknown intent.")
|
||||||
|
}
|
||||||
|
|
||||||
|
templates.Redirect(w, r.URL.Path)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,3 +74,69 @@ func ReadNotification() http.HandlerFunc {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearNotification API to delete a single notification for the user.
|
||||||
|
func ClearNotification() http.HandlerFunc {
|
||||||
|
// Request JSON schema.
|
||||||
|
type Request struct {
|
||||||
|
ID uint64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response JSON schema.
|
||||||
|
type Response struct {
|
||||||
|
OK bool `json:"OK"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
SendJSON(w, http.StatusNotAcceptable, Response{
|
||||||
|
Error: "POST method only",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current user.
|
||||||
|
currentUser, err := session.CurrentUser(r)
|
||||||
|
if err != nil {
|
||||||
|
SendJSON(w, http.StatusBadRequest, Response{
|
||||||
|
Error: "Couldn't get current user!",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse request payload.
|
||||||
|
var req Request
|
||||||
|
if err := ParseJSON(r, &req); err != nil {
|
||||||
|
SendJSON(w, http.StatusBadRequest, Response{
|
||||||
|
Error: fmt.Sprintf("Error with request payload: %s", err),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get this notification.
|
||||||
|
notif, err := models.GetNotification(req.ID)
|
||||||
|
if err != nil {
|
||||||
|
SendJSON(w, http.StatusInternalServerError, Response{
|
||||||
|
Error: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure it's ours to read.
|
||||||
|
if notif.UserID != currentUser.ID {
|
||||||
|
SendJSON(w, http.StatusForbidden, Response{
|
||||||
|
Error: "That is not your notification.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete it.
|
||||||
|
notif.Delete()
|
||||||
|
|
||||||
|
// Send success response.
|
||||||
|
SendJSON(w, http.StatusOK, Response{
|
||||||
|
OK: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -85,6 +85,11 @@ func Share() http.HandlerFunc {
|
||||||
|
|
||||||
// Are we revoking our privates from ALL USERS?
|
// Are we revoking our privates from ALL USERS?
|
||||||
if isRevokeAll {
|
if isRevokeAll {
|
||||||
|
// Revoke any "has uploaded a new private photo" notifications from all users' lists.
|
||||||
|
if err := models.RevokePrivatePhotoNotifications(currentUser, nil); err != nil {
|
||||||
|
log.Error("RevokePrivatePhotoNotifications(%s): %s", currentUser.Username, err)
|
||||||
|
}
|
||||||
|
|
||||||
models.RevokePrivatePhotosAll(currentUser.ID)
|
models.RevokePrivatePhotosAll(currentUser.ID)
|
||||||
session.Flash(w, r, "Your private photos have been locked from ALL users.")
|
session.Flash(w, r, "Your private photos have been locked from ALL users.")
|
||||||
templates.Redirect(w, "/photo/private")
|
templates.Redirect(w, "/photo/private")
|
||||||
|
@ -140,6 +145,11 @@ func Share() http.HandlerFunc {
|
||||||
|
|
||||||
// Remove any notification we created when the grant was given.
|
// Remove any notification we created when the grant was given.
|
||||||
models.RemoveSpecificNotification(user.ID, models.NotificationPrivatePhoto, "__private_photos", currentUser.ID)
|
models.RemoveSpecificNotification(user.ID, models.NotificationPrivatePhoto, "__private_photos", currentUser.ID)
|
||||||
|
|
||||||
|
// Revoke any "has uploaded a new private photo" notifications in this user's list.
|
||||||
|
if err := models.RevokePrivatePhotoNotifications(currentUser, &user.ID); err != nil {
|
||||||
|
log.Error("RevokePrivatePhotoNotifications(%s): %s", currentUser.Username, err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -172,8 +172,13 @@ func notifyFriendsNewPhoto(photo *models.Photo, currentUser *models.User) {
|
||||||
// Who to notify?
|
// Who to notify?
|
||||||
if photo.Visibility == models.PhotoPrivate {
|
if photo.Visibility == models.PhotoPrivate {
|
||||||
// Private grantees
|
// Private grantees
|
||||||
friendIDs = models.PrivateGranteeUserIDs(currentUser.ID)
|
if photo.Explicit {
|
||||||
log.Info("Notify %d private grantees about the new photo by %s", len(friendIDs), currentUser.Username)
|
friendIDs = models.PrivateGranteeAreExplicitUserIDs(currentUser.ID)
|
||||||
|
log.Info("Notify %d EXPLICIT private grantees about the new photo by %s", len(friendIDs), currentUser.Username)
|
||||||
|
} else {
|
||||||
|
friendIDs = models.PrivateGranteeUserIDs(currentUser.ID)
|
||||||
|
log.Info("Notify %d private grantees about the new photo by %s", len(friendIDs), currentUser.Username)
|
||||||
|
}
|
||||||
} else if photo.Visibility == models.PhotoInnerCircle {
|
} else if photo.Visibility == models.PhotoInnerCircle {
|
||||||
// Inner circle members. If the pic is also Explicit, further narrow to explicit friend IDs.
|
// Inner circle members. If the pic is also Explicit, further narrow to explicit friend IDs.
|
||||||
if photo.Explicit {
|
if photo.Explicit {
|
||||||
|
|
|
@ -93,6 +93,16 @@ func RemoveNotification(tableName string, tableID uint64) error {
|
||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveNotificationBulk about several table IDs, e.g. when bulk removing private photo upload
|
||||||
|
// notifications for everybody on the site.
|
||||||
|
func RemoveNotificationBulk(tableName string, tableIDs []uint64) error {
|
||||||
|
result := DB.Where(
|
||||||
|
"table_name = ? AND table_id IN ?",
|
||||||
|
tableName, tableIDs,
|
||||||
|
).Delete(&Notification{})
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveSpecificNotification to remove more specialized notifications where just removing by
|
// RemoveSpecificNotification to remove more specialized notifications where just removing by
|
||||||
// table name+ID is not adequate, e.g. for Private Photo Unlocks.
|
// table name+ID is not adequate, e.g. for Private Photo Unlocks.
|
||||||
func RemoveSpecificNotification(userID uint64, t NotificationType, tableName string, tableID uint64) error {
|
func RemoveSpecificNotification(userID uint64, t NotificationType, tableName string, tableID uint64) error {
|
||||||
|
@ -103,6 +113,16 @@ func RemoveSpecificNotification(userID uint64, t NotificationType, tableName str
|
||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RemoveSpecificNotificationBulk can remove notifications about several TableIDs of the same type,
|
||||||
|
// e.g. to bulk remove new private photo upload notifications.
|
||||||
|
func RemoveSpecificNotificationBulk(userID uint64, t NotificationType, tableName string, tableIDs []uint64) error {
|
||||||
|
result := DB.Where(
|
||||||
|
"user_id = ? AND type = ? AND table_name = ? AND table_id IN ?",
|
||||||
|
userID, t, tableName, tableIDs,
|
||||||
|
).Delete(&Notification{})
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
// MarkNotificationsRead sets all a user's notifications to read.
|
// MarkNotificationsRead sets all a user's notifications to read.
|
||||||
func MarkNotificationsRead(user *User) error {
|
func MarkNotificationsRead(user *User) error {
|
||||||
return DB.Model(&Notification{}).Where(
|
return DB.Model(&Notification{}).Where(
|
||||||
|
@ -111,6 +131,13 @@ func MarkNotificationsRead(user *User) error {
|
||||||
).Update("read", true).Error
|
).Update("read", true).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearAllNotifications removes a user's entire notification table.
|
||||||
|
func ClearAllNotifications(user *User) error {
|
||||||
|
return DB.Where(
|
||||||
|
"user_id = ?", user.ID,
|
||||||
|
).Delete(&Notification{}).Error
|
||||||
|
}
|
||||||
|
|
||||||
// CountUnreadNotifications gets the count of unread Notifications for a user.
|
// CountUnreadNotifications gets the count of unread Notifications for a user.
|
||||||
func CountUnreadNotifications(userID uint64) (int64, error) {
|
func CountUnreadNotifications(userID uint64) (int64, error) {
|
||||||
query := DB.Where(
|
query := DB.Where(
|
||||||
|
@ -145,6 +172,11 @@ func (n *Notification) Save() error {
|
||||||
return DB.Save(n).Error
|
return DB.Save(n).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete a notification.
|
||||||
|
func (n *Notification) Delete() error {
|
||||||
|
return DB.Delete(n).Error
|
||||||
|
}
|
||||||
|
|
||||||
// NotificationBody can store remote tables mapped.
|
// NotificationBody can store remote tables mapped.
|
||||||
type NotificationBody struct {
|
type NotificationBody struct {
|
||||||
PhotoID uint64
|
PhotoID uint64
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/log"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -56,6 +58,48 @@ func RevokePrivatePhotosAll(sourceUserID uint64) error {
|
||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RevokePrivatePhotoNotifications removes notifications about newly uploaded private photos
|
||||||
|
// that were sent to one (or multiple) members when the user revokes their access later. Pass
|
||||||
|
// a nil fromUserID to revoke the photo upload notifications from ALL users.
|
||||||
|
func RevokePrivatePhotoNotifications(currentUser *User, fromUserID *uint64) error {
|
||||||
|
// Gather the IDs of all our private photos to nuke notifications for.
|
||||||
|
photoIDs, err := currentUser.AllPrivatePhotoIDs()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if len(photoIDs) == 0 {
|
||||||
|
// Nothing to do.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Who to clear the notifications for?
|
||||||
|
if fromUserID == nil {
|
||||||
|
log.Info("RevokePrivatePhotoNotifications(%s): forget about private photo uploads for EVERYBODY on photo IDs: %v", currentUser.Username, photoIDs)
|
||||||
|
return RemoveNotificationBulk("photos", photoIDs)
|
||||||
|
} else {
|
||||||
|
log.Info("RevokePrivatePhotoNotifications(%s): forget about private photo uploads for user %d on photo IDs: %v", currentUser.Username, *fromUserID, photoIDs)
|
||||||
|
return RemoveSpecificNotificationBulk(*fromUserID, NotificationNewPhoto, "photos", photoIDs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllPrivatePhotoIDs returns the listing of all IDs of the user's private photos.
|
||||||
|
func (u *User) AllPrivatePhotoIDs() ([]uint64, error) {
|
||||||
|
var photoIDs = []uint64{}
|
||||||
|
err := DB.Table(
|
||||||
|
"photos",
|
||||||
|
).Select(
|
||||||
|
"photos.id AS id",
|
||||||
|
).Where(
|
||||||
|
"user_id = ? AND visibility = ?",
|
||||||
|
u.ID, PhotoPrivate,
|
||||||
|
).Scan(&photoIDs)
|
||||||
|
|
||||||
|
if err.Error != nil {
|
||||||
|
return photoIDs, fmt.Errorf("AllPrivatePhotoIDs(%s): %s", u.Username, err.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return photoIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
// IsPrivateUnlocked quickly sees if sourceUserID has unlocked private photos for targetUserID to see.
|
// IsPrivateUnlocked quickly sees if sourceUserID has unlocked private photos for targetUserID to see.
|
||||||
func IsPrivateUnlocked(sourceUserID, targetUserID uint64) bool {
|
func IsPrivateUnlocked(sourceUserID, targetUserID uint64) bool {
|
||||||
pb := &PrivatePhoto{}
|
pb := &PrivatePhoto{}
|
||||||
|
@ -102,6 +146,30 @@ func PrivateGranteeUserIDs(userId uint64) []uint64 {
|
||||||
return userIDs
|
return userIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrivateGranteeAreExplicitUserIDs gets your private photo grantees who have opted-in to see explicit content.
|
||||||
|
func PrivateGranteeAreExplicitUserIDs(userId uint64) []uint64 {
|
||||||
|
var (
|
||||||
|
userIDs = []uint64{}
|
||||||
|
)
|
||||||
|
|
||||||
|
err := DB.Table(
|
||||||
|
"private_photos",
|
||||||
|
).Joins(
|
||||||
|
"JOIN users ON (users.id = private_photos.target_user_id)",
|
||||||
|
).Select(
|
||||||
|
"private_photos.target_user_id AS user_id",
|
||||||
|
).Where(
|
||||||
|
"source_user_id = ? AND users.explicit IS TRUE",
|
||||||
|
userId,
|
||||||
|
).Scan(&userIDs)
|
||||||
|
|
||||||
|
if err.Error != nil {
|
||||||
|
log.Error("PrivateGranteeAreExplicitUserIDs: %s", err.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return userIDs
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
PaginatePrivatePhotoList views a user's list of private photo grants.
|
PaginatePrivatePhotoList views a user's list of private photo grants.
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,7 @@ func New() http.Handler {
|
||||||
mux.HandleFunc("/v1/users/me", api.LoginOK())
|
mux.HandleFunc("/v1/users/me", api.LoginOK())
|
||||||
mux.Handle("/v1/likes", middleware.LoginRequired(api.Likes()))
|
mux.Handle("/v1/likes", middleware.LoginRequired(api.Likes()))
|
||||||
mux.Handle("/v1/notifications/read", middleware.LoginRequired(api.ReadNotification()))
|
mux.Handle("/v1/notifications/read", middleware.LoginRequired(api.ReadNotification()))
|
||||||
|
mux.Handle("/v1/notifications/delete", middleware.LoginRequired(api.ClearNotification()))
|
||||||
mux.Handle("/v1/comment-photos/remove-orphaned", api.RemoveOrphanedCommentPhotos())
|
mux.Handle("/v1/comment-photos/remove-orphaned", api.RemoveOrphanedCommentPhotos())
|
||||||
|
|
||||||
// Static files.
|
// Static files.
|
||||||
|
|
|
@ -224,22 +224,63 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="columns">
|
<!-- Notifications header row: tablets on upwards -->
|
||||||
<div class="column">
|
<div class="is-hidden-mobile">
|
||||||
|
<form method="POST" action="{{.Request.URL.Path}}">
|
||||||
|
{{InputCSRF}}
|
||||||
|
<div class="columns mb-2">
|
||||||
|
<div class="column">
|
||||||
|
{{if gt .NavUnreadNotifications 0}}
|
||||||
|
{{.NavUnreadNotifications}} unread notification{{Pluralize64 .NavUnreadNotifications}}.
|
||||||
|
{{else}}
|
||||||
|
No unread notifications.
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="column is-narrow has-text-right">
|
||||||
|
<button type="submit" name="intent" value="clear-all"
|
||||||
|
class="button is-danger is-light is-small"
|
||||||
|
onclick="return window.confirm('Are you sure you want to REMOVE all notifications?')">
|
||||||
|
<i class="fa fa-xmark mr-1"></i>
|
||||||
|
Clear all
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="submit" name="intent" value="read-notifications"
|
||||||
|
class="button is-link is-light is-small">
|
||||||
|
<i class="fa fa-check mr-1"></i>
|
||||||
|
Mark all as read
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Notifications header: for mobiles only version -->
|
||||||
|
<div class="is-hidden-tablet">
|
||||||
|
<p>
|
||||||
{{if gt .NavUnreadNotifications 0}}
|
{{if gt .NavUnreadNotifications 0}}
|
||||||
{{.NavUnreadNotifications}} unread notification{{Pluralize64 .NavUnreadNotifications}}.
|
{{.NavUnreadNotifications}} unread notification{{Pluralize64 .NavUnreadNotifications}}.
|
||||||
{{else}}
|
{{else}}
|
||||||
No unread notifications.
|
No unread notifications.
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</p>
|
||||||
<div class="column is-narrow">
|
<form method="POST" action="{{.Request.URL.Path}}">
|
||||||
<a href="/me?intent=read-notifications" class="button is-link is-light is-small">
|
{{InputCSRF}}
|
||||||
<span class="icon-text">
|
<div class="my-2 has-text-right">
|
||||||
<span class="icon"><i class="fa fa-check"></i></span>
|
<button type="submit" name="intent" value="clear-all"
|
||||||
<span>Mark all as read</span>
|
class="button is-danger is-light is-small"
|
||||||
</span>
|
onclick="return window.confirm('Are you sure you want to REMOVE all notifications?')">
|
||||||
</a>
|
<i class="fa fa-xmark mr-1"></i>
|
||||||
</div>
|
Clear all
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button type="submit" name="intent" value="read-notifications"
|
||||||
|
class="button is-link is-light is-small">
|
||||||
|
<i class="fa fa-check mr-1"></i>
|
||||||
|
Mark all as read
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table is-striped is-fullwidth is-hoverable">
|
<table class="table is-striped is-fullwidth is-hoverable">
|
||||||
|
@ -404,7 +445,14 @@
|
||||||
|
|
||||||
<hr class="has-background-light mb-1">
|
<hr class="has-background-light mb-1">
|
||||||
<small title="{{.CreatedAt.Format "2006-01-02 15:04:05"}}">
|
<small title="{{.CreatedAt.Format "2006-01-02 15:04:05"}}">
|
||||||
{{SincePrettyCoarse .CreatedAt}} ago
|
{{SincePrettyCoarse .CreatedAt}} ago.
|
||||||
|
|
||||||
|
<!-- Delete button for just this notification -->
|
||||||
|
<button type="button"
|
||||||
|
class="button is-danger is-light is-small nonshy-notif-delete-button"
|
||||||
|
data-notification-id="{{.ID}}">
|
||||||
|
<i class="fa fa-xmark mr-1"></i> Remove
|
||||||
|
</button>
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -477,11 +525,56 @@
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
let busy = false;
|
let busy = false;
|
||||||
|
|
||||||
|
// For delete buttons: if they click thru the first confirm, do not ask every single time
|
||||||
|
// for the rest of the current page load.
|
||||||
|
let dontAskAgain = false;
|
||||||
|
|
||||||
// Bind to the notification table rows.
|
// Bind to the notification table rows.
|
||||||
(document.querySelectorAll(".nonshy-notification-row") || []).forEach(node => {
|
(document.querySelectorAll(".nonshy-notification-row") || []).forEach(node => {
|
||||||
let $newBadge = node.querySelector(".nonshy-notification-new"),
|
let $newBadge = node.querySelector(".nonshy-notification-new"),
|
||||||
|
$deleteButton = node.querySelector(".nonshy-notif-delete-button"),
|
||||||
ID = node.dataset.notificationId;
|
ID = node.dataset.notificationId;
|
||||||
|
|
||||||
|
// Delete buttons for individual notifications.
|
||||||
|
$deleteButton.addEventListener("click", (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
if (!dontAskAgain) {
|
||||||
|
if (!window.confirm(
|
||||||
|
"Do you want to DELETE this notification?\n\nNote: If you click Ok, you will not be asked "+
|
||||||
|
"the next time you want to delete another notification until your next page reload."
|
||||||
|
)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dontAskAgain = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
busy = true;
|
||||||
|
return fetch("/v1/notifications/delete", {
|
||||||
|
method: "POST",
|
||||||
|
mode: "same-origin",
|
||||||
|
cache: "no-cache",
|
||||||
|
credentials: "same-origin",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
"id": parseInt(ID),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
console.log(data);
|
||||||
|
|
||||||
|
// Hide the notification row immediately.
|
||||||
|
node.style.display = 'none';
|
||||||
|
}).catch(resp => {
|
||||||
|
window.alert(resp);
|
||||||
|
}).finally(() => {
|
||||||
|
busy = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// If the notification doesn't have a "NEW!" badge, no action needed.
|
// If the notification doesn't have a "NEW!" badge, no action needed.
|
||||||
if ($newBadge === null) return;
|
if ($newBadge === null) return;
|
||||||
|
|
||||||
|
@ -530,7 +623,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user