Mute Users from the Site Gallery
This commit is contained in:
parent
3416d647fc
commit
9db4dbd1e7
|
@ -12,6 +12,7 @@ var (
|
||||||
PageSizeMemberSearch = 60
|
PageSizeMemberSearch = 60
|
||||||
PageSizeFriends = 12
|
PageSizeFriends = 12
|
||||||
PageSizeBlockList = 12
|
PageSizeBlockList = 12
|
||||||
|
PageSizeMuteList = PageSizeBlockList
|
||||||
PageSizePrivatePhotoGrantees = 12
|
PageSizePrivatePhotoGrantees = 12
|
||||||
PageSizeAdminCertification = 20
|
PageSizeAdminCertification = 20
|
||||||
PageSizeAdminFeedback = 20
|
PageSizeAdminFeedback = 20
|
||||||
|
|
166
pkg/controller/mutelist/mute.go
Normal file
166
pkg/controller/mutelist/mute.go
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
package mutelist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/config"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/models"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/session"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Muted User list: view the list of muted accounts.
|
||||||
|
func MuteList() http.HandlerFunc {
|
||||||
|
tmpl := templates.Must("account/mute_list.html")
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
currentUser, err := session.CurrentUser(r)
|
||||||
|
if err != nil {
|
||||||
|
session.FlashError(w, r, "Unexpected error: could not get currentUser.")
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get our mutelist.
|
||||||
|
pager := &models.Pagination{
|
||||||
|
PerPage: config.PageSizeMuteList,
|
||||||
|
Sort: "updated_at desc",
|
||||||
|
}
|
||||||
|
pager.ParsePage(r)
|
||||||
|
muted, err := models.PaginateMuteList(currentUser, pager)
|
||||||
|
if err != nil {
|
||||||
|
session.FlashError(w, r, "Couldn't paginate mute list: %s", err)
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var vars = map[string]interface{}{
|
||||||
|
"MutedUsers": muted,
|
||||||
|
"Pager": pager,
|
||||||
|
}
|
||||||
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddUser to manually add someone to your mute list.
|
||||||
|
func AddUser() http.HandlerFunc {
|
||||||
|
tmpl := templates.Must("account/mute_list_add.html")
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Query parameters.
|
||||||
|
var (
|
||||||
|
username = strings.ToLower(r.FormValue("username"))
|
||||||
|
next = r.FormValue("next")
|
||||||
|
context = models.MutedUserContext(r.FormValue("context"))
|
||||||
|
listName = "Site Gallery" // TODO: more as contexts are added
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validate the Next URL.
|
||||||
|
if !strings.HasPrefix(next, "/") {
|
||||||
|
next = "/users/muted"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate acceptable contexts.
|
||||||
|
if !models.IsValidMuteUserContext(context) {
|
||||||
|
session.FlashError(w, r, "Unsupported mute context.")
|
||||||
|
templates.Redirect(w, next)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the target user.
|
||||||
|
user, err := models.FindUser(username)
|
||||||
|
if err != nil {
|
||||||
|
session.FlashError(w, r, "User Not Found")
|
||||||
|
templates.Redirect(w, next)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vars := map[string]interface{}{
|
||||||
|
"User": user,
|
||||||
|
"Next": next,
|
||||||
|
"Context": context,
|
||||||
|
"MuteListName": listName,
|
||||||
|
}
|
||||||
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// MuteUser controller: POST endpoint to add a mute.
|
||||||
|
func MuteUser() http.HandlerFunc {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Form fields
|
||||||
|
var (
|
||||||
|
username = strings.ToLower(r.PostFormValue("username"))
|
||||||
|
next = r.PostFormValue("next")
|
||||||
|
context = models.MutedUserContext(r.PostFormValue("context"))
|
||||||
|
listName = "Site Gallery" // TODO: more as contexts are added
|
||||||
|
unmute = r.PostFormValue("unmute") == "true"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validate the Next URL.
|
||||||
|
if !strings.HasPrefix(next, "/") {
|
||||||
|
next = "/users/muted"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate acceptable contexts.
|
||||||
|
if !models.IsValidMuteUserContext(context) {
|
||||||
|
session.FlashError(w, r, "Unsupported mute context.")
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current user.
|
||||||
|
currentUser, err := session.CurrentUser(r)
|
||||||
|
if err != nil {
|
||||||
|
session.FlashError(w, r, "Couldn't get CurrentUser: %s", err)
|
||||||
|
templates.Redirect(w, "/")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the target user.
|
||||||
|
user, err := models.FindUser(username)
|
||||||
|
if err != nil {
|
||||||
|
session.FlashError(w, r, "User Not Found")
|
||||||
|
templates.Redirect(w, next)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmuting?
|
||||||
|
if unmute {
|
||||||
|
if err := models.RemoveMutedUser(currentUser.ID, user.ID, context); err != nil {
|
||||||
|
session.FlashError(w, r, "Couldn't unmute this user: %s.", err)
|
||||||
|
} else {
|
||||||
|
session.Flash(w, r, "You have removed %s from your %s mute list.", user.Username, listName)
|
||||||
|
|
||||||
|
// Log the change.
|
||||||
|
models.LogDeleted(currentUser, nil, "muted_users", user.ID, "Unmuted user "+user.Username+" from "+listName+".", nil)
|
||||||
|
}
|
||||||
|
templates.Redirect(w, next)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can't mute yourself.
|
||||||
|
if currentUser.ID == user.ID {
|
||||||
|
session.FlashError(w, r, "You can't mute yourself!")
|
||||||
|
templates.Redirect(w, next)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mute the target user.
|
||||||
|
if err := models.AddMutedUser(currentUser.ID, user.ID, context); err != nil {
|
||||||
|
session.FlashError(w, r, "Couldn't mute this user: %s.", err)
|
||||||
|
} else {
|
||||||
|
session.Flash(w, r, "You have added %s to your %s mute list.", user.Username, listName)
|
||||||
|
|
||||||
|
// Log the change.
|
||||||
|
models.LogCreated(currentUser, "muted_users", user.ID, "Mutes user "+user.Username+" on list "+listName+".")
|
||||||
|
}
|
||||||
|
|
||||||
|
templates.Redirect(w, next)
|
||||||
|
})
|
||||||
|
}
|
|
@ -51,6 +51,7 @@ func DeleteUser(user *models.User) error {
|
||||||
{"Messages", DeleteUserMessages},
|
{"Messages", DeleteUserMessages},
|
||||||
{"Friends", DeleteFriends},
|
{"Friends", DeleteFriends},
|
||||||
{"Blocks", DeleteBlocks},
|
{"Blocks", DeleteBlocks},
|
||||||
|
{"MutedUsers", DeleteMutedUsers},
|
||||||
{"Feedbacks", DeleteFeedbacks},
|
{"Feedbacks", DeleteFeedbacks},
|
||||||
{"Two Factor", DeleteTwoFactor},
|
{"Two Factor", DeleteTwoFactor},
|
||||||
{"Profile Fields", DeleteProfile},
|
{"Profile Fields", DeleteProfile},
|
||||||
|
@ -227,6 +228,16 @@ func DeleteBlocks(userID uint64) error {
|
||||||
return result.Error
|
return result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteMutedUsers scrubs data for deleting a user.
|
||||||
|
func DeleteMutedUsers(userID uint64) error {
|
||||||
|
log.Error("DeleteUser: DeleteMutedUsers(%d)", userID)
|
||||||
|
result := models.DB.Where(
|
||||||
|
"source_user_id = ? OR target_user_id = ?",
|
||||||
|
userID, userID,
|
||||||
|
).Delete(&models.MutedUser{})
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteFeedbacks scrubs data for deleting a user.
|
// DeleteFeedbacks scrubs data for deleting a user.
|
||||||
func DeleteFeedbacks(userID uint64) error {
|
func DeleteFeedbacks(userID uint64) error {
|
||||||
log.Error("DeleteUser: DeleteFeedbacks(%d)", userID)
|
log.Error("DeleteUser: DeleteFeedbacks(%d)", userID)
|
||||||
|
|
|
@ -32,6 +32,7 @@ func ExportModels(zw *zip.Writer, user *models.User) error {
|
||||||
{"IPAddress", ExportIPAddressTable},
|
{"IPAddress", ExportIPAddressTable},
|
||||||
{"Like", ExportLikeTable},
|
{"Like", ExportLikeTable},
|
||||||
{"Message", ExportMessageTable},
|
{"Message", ExportMessageTable},
|
||||||
|
{"MutedUser", ExportMutedUserTable},
|
||||||
{"Notification", ExportNotificationTable},
|
{"Notification", ExportNotificationTable},
|
||||||
{"ProfileField", ExportProfileFieldTable},
|
{"ProfileField", ExportProfileFieldTable},
|
||||||
{"Photo", ExportPhotoTable},
|
{"Photo", ExportPhotoTable},
|
||||||
|
@ -189,6 +190,21 @@ func ExportBlockTable(zw *zip.Writer, user *models.User) error {
|
||||||
return ZipJson(zw, "blocks.json", items)
|
return ZipJson(zw, "blocks.json", items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExportMutedUserTable(zw *zip.Writer, user *models.User) error {
|
||||||
|
var (
|
||||||
|
items = []*models.MutedUser{}
|
||||||
|
query = models.DB.Model(&models.MutedUser{}).Where(
|
||||||
|
"source_user_id = ?",
|
||||||
|
user.ID,
|
||||||
|
).Find(&items)
|
||||||
|
)
|
||||||
|
if query.Error != nil {
|
||||||
|
return query.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
return ZipJson(zw, "muted_users.json", items)
|
||||||
|
}
|
||||||
|
|
||||||
func ExportFeedbackTable(zw *zip.Writer, user *models.User) error {
|
func ExportFeedbackTable(zw *zip.Writer, user *models.User) error {
|
||||||
var (
|
var (
|
||||||
items = []*models.Feedback{}
|
items = []*models.Feedback{}
|
||||||
|
|
|
@ -24,6 +24,7 @@ func AutoMigrate() {
|
||||||
&IPAddress{}, // ✔
|
&IPAddress{}, // ✔
|
||||||
&Like{}, // ✔
|
&Like{}, // ✔
|
||||||
&Message{}, // ✔
|
&Message{}, // ✔
|
||||||
|
&MutedUser{}, // ✔
|
||||||
&Notification{}, // ✔
|
&Notification{}, // ✔
|
||||||
&ProfileField{}, // ✔
|
&ProfileField{}, // ✔
|
||||||
&Photo{}, // ✔
|
&Photo{}, // ✔
|
||||||
|
|
109
pkg/models/muted_users.go
Normal file
109
pkg/models/muted_users.go
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MutedUser table, for users to mute one another's Site Gallery photos and similar.
|
||||||
|
type MutedUser struct {
|
||||||
|
ID uint64 `gorm:"primaryKey"`
|
||||||
|
SourceUserID uint64 `gorm:"uniqueIndex:idx_muted_user"`
|
||||||
|
TargetUserID uint64 `gorm:"uniqueIndex:idx_muted_user"`
|
||||||
|
Context MutedUserContext `gorm:"uniqueIndex:idx_muted_user"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type MutedUserContext string
|
||||||
|
|
||||||
|
// Context options for MutedUser to specify what is being muted.
|
||||||
|
const (
|
||||||
|
MutedUserContextSiteGallery MutedUserContext = "site_gallery" // hide a user's photos from the Site Gallery.
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsValidMuteUserContext validates acceptable options for muting users.
|
||||||
|
func IsValidMuteUserContext(ctx MutedUserContext) bool {
|
||||||
|
return ctx == MutedUserContextSiteGallery
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddMutedUser is sourceUserId adding targetUserId to their mute list under the given context.
|
||||||
|
func AddMutedUser(sourceUserID, targetUserID uint64, ctx MutedUserContext) error {
|
||||||
|
m := &MutedUser{
|
||||||
|
SourceUserID: sourceUserID,
|
||||||
|
TargetUserID: targetUserID,
|
||||||
|
Context: ctx,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upsert the mute.
|
||||||
|
res := DB.Model(&MutedUser{}).Clauses(
|
||||||
|
clause.OnConflict{
|
||||||
|
Columns: []clause.Column{
|
||||||
|
{Name: "source_user_id"},
|
||||||
|
{Name: "target_user_id"},
|
||||||
|
{Name: "context"},
|
||||||
|
},
|
||||||
|
DoNothing: true,
|
||||||
|
},
|
||||||
|
).Create(m)
|
||||||
|
return res.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// MutedUserIDs returns all user IDs Muted by the user.
|
||||||
|
func MutedUserIDs(user *User, context MutedUserContext) []uint64 {
|
||||||
|
var (
|
||||||
|
ms = []*MutedUser{}
|
||||||
|
userIDs = []uint64{}
|
||||||
|
)
|
||||||
|
DB.Where("source_user_id = ? AND context = ?", user.ID, context).Find(&ms)
|
||||||
|
for _, row := range ms {
|
||||||
|
userIDs = append(userIDs, row.TargetUserID)
|
||||||
|
}
|
||||||
|
return userIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
// PaginateMuteList views a user's mute lists.
|
||||||
|
func PaginateMuteList(user *User, pager *Pagination) ([]*User, error) {
|
||||||
|
// We paginate over the MutedUser table.
|
||||||
|
var (
|
||||||
|
ms = []*MutedUser{}
|
||||||
|
userIDs = []uint64{}
|
||||||
|
query *gorm.DB
|
||||||
|
)
|
||||||
|
|
||||||
|
query = DB.Where(
|
||||||
|
"source_user_id = ?",
|
||||||
|
user.ID,
|
||||||
|
)
|
||||||
|
|
||||||
|
query = query.Order(pager.Sort)
|
||||||
|
query.Model(&MutedUser{}).Count(&pager.Total)
|
||||||
|
result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&ms)
|
||||||
|
if result.Error != nil {
|
||||||
|
return nil, result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now of these friends get their User objects.
|
||||||
|
for _, b := range ms {
|
||||||
|
userIDs = append(userIDs, b.TargetUserID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetUsers(user, userIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveMutedUser clears the muted user row.
|
||||||
|
func RemoveMutedUser(sourceUserID, targetUserID uint64, ctx MutedUserContext) error {
|
||||||
|
result := DB.Where(
|
||||||
|
"source_user_id = ? AND target_user_id = ? AND context = ?",
|
||||||
|
sourceUserID, targetUserID, ctx,
|
||||||
|
).Delete(&MutedUser{})
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save photo.
|
||||||
|
func (m *MutedUser) Save() error {
|
||||||
|
result := DB.Save(m)
|
||||||
|
return result.Error
|
||||||
|
}
|
|
@ -682,6 +682,7 @@ func PaginateGalleryPhotos(user *User, conf Gallery, pager *Pagination) ([]*Phot
|
||||||
explicitOK = user.Explicit // User opted-in for explicit content
|
explicitOK = user.Explicit // User opted-in for explicit content
|
||||||
|
|
||||||
blocklist = BlockedUserIDs(user)
|
blocklist = BlockedUserIDs(user)
|
||||||
|
mutelist = MutedUserIDs(user, MutedUserContextSiteGallery)
|
||||||
privateUserIDs = PrivateGrantedUserIDs(userID)
|
privateUserIDs = PrivateGrantedUserIDs(userID)
|
||||||
privateUserIDsAreFriends = PrivateGrantedUserIDsAreFriends(user)
|
privateUserIDsAreFriends = PrivateGrantedUserIDsAreFriends(user)
|
||||||
wheres = []string{}
|
wheres = []string{}
|
||||||
|
@ -790,6 +791,12 @@ func PaginateGalleryPhotos(user *User, conf Gallery, pager *Pagination) ([]*Phot
|
||||||
placeholders = append(placeholders, blocklist)
|
placeholders = append(placeholders, blocklist)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter Site Gallery muted users.
|
||||||
|
if len(mutelist) > 0 {
|
||||||
|
wheres = append(wheres, "photos.user_id NOT IN ?")
|
||||||
|
placeholders = append(placeholders, mutelist)
|
||||||
|
}
|
||||||
|
|
||||||
// Non-explicit pics unless the user opted in. Allow explicit filter setting to override.
|
// Non-explicit pics unless the user opted in. Allow explicit filter setting to override.
|
||||||
if filterExplicit != "" {
|
if filterExplicit != "" {
|
||||||
wheres = append(wheres, "photos.explicit = ?")
|
wheres = append(wheres, "photos.explicit = ?")
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"code.nonshy.com/nonshy/website/pkg/controller/htmx"
|
"code.nonshy.com/nonshy/website/pkg/controller/htmx"
|
||||||
"code.nonshy.com/nonshy/website/pkg/controller/inbox"
|
"code.nonshy.com/nonshy/website/pkg/controller/inbox"
|
||||||
"code.nonshy.com/nonshy/website/pkg/controller/index"
|
"code.nonshy.com/nonshy/website/pkg/controller/index"
|
||||||
|
"code.nonshy.com/nonshy/website/pkg/controller/mutelist"
|
||||||
"code.nonshy.com/nonshy/website/pkg/controller/photo"
|
"code.nonshy.com/nonshy/website/pkg/controller/photo"
|
||||||
"code.nonshy.com/nonshy/website/pkg/controller/poll"
|
"code.nonshy.com/nonshy/website/pkg/controller/poll"
|
||||||
"code.nonshy.com/nonshy/website/pkg/middleware"
|
"code.nonshy.com/nonshy/website/pkg/middleware"
|
||||||
|
@ -78,6 +79,9 @@ func New() http.Handler {
|
||||||
mux.Handle("POST /users/block", middleware.LoginRequired(block.BlockUser()))
|
mux.Handle("POST /users/block", middleware.LoginRequired(block.BlockUser()))
|
||||||
mux.Handle("GET /users/blocked", middleware.LoginRequired(block.Blocked()))
|
mux.Handle("GET /users/blocked", middleware.LoginRequired(block.Blocked()))
|
||||||
mux.Handle("GET /users/blocklist/add", middleware.LoginRequired(block.AddUser()))
|
mux.Handle("GET /users/blocklist/add", middleware.LoginRequired(block.AddUser()))
|
||||||
|
mux.Handle("GET /users/muted", middleware.LoginRequired(mutelist.MuteList()))
|
||||||
|
mux.Handle("GET /users/mutelist/add", middleware.LoginRequired(mutelist.AddUser()))
|
||||||
|
mux.Handle("POST /users/mutelist/add", middleware.LoginRequired(mutelist.MuteUser()))
|
||||||
mux.Handle("/comments", middleware.LoginRequired(comment.PostComment()))
|
mux.Handle("/comments", middleware.LoginRequired(comment.PostComment()))
|
||||||
mux.Handle("GET /comments/subscription", middleware.LoginRequired(comment.Subscription()))
|
mux.Handle("GET /comments/subscription", middleware.LoginRequired(comment.Subscription()))
|
||||||
mux.Handle("GET /admin/unimpersonate", middleware.LoginRequired(admin.Unimpersonate()))
|
mux.Handle("GET /admin/unimpersonate", middleware.LoginRequired(admin.Unimpersonate()))
|
||||||
|
|
|
@ -35,6 +35,7 @@ func TemplateFuncs(r *http.Request) template.FuncMap {
|
||||||
"FormatNumberCommas": FormatNumberCommas(),
|
"FormatNumberCommas": FormatNumberCommas(),
|
||||||
"ComputeAge": utility.Age,
|
"ComputeAge": utility.Age,
|
||||||
"Split": strings.Split,
|
"Split": strings.Split,
|
||||||
|
"NewHashMap": NewHashMap,
|
||||||
"ToMarkdown": ToMarkdown,
|
"ToMarkdown": ToMarkdown,
|
||||||
"DeMarkify": markdown.DeMarkify,
|
"DeMarkify": markdown.DeMarkify,
|
||||||
"ToJSON": ToJSON,
|
"ToJSON": ToJSON,
|
||||||
|
@ -264,6 +265,30 @@ func SubtractInt64(a, b int64) int64 {
|
||||||
return a - b
|
return a - b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewHashMap creates a key/value dict on the fly for Go templates.
|
||||||
|
//
|
||||||
|
// Use it like: {{$Vars := NewHashMap "username" .CurrentUser.Username "photoID" .Photo.ID}}
|
||||||
|
//
|
||||||
|
// It is useful for calling Go subtemplates that need custom parameters, e.g. a
|
||||||
|
// mixin from current scope with other variables.
|
||||||
|
func NewHashMap(upsert ...interface{}) map[string]interface{} {
|
||||||
|
// Map the positional arguments into a dictionary.
|
||||||
|
var params = map[string]interface{}{}
|
||||||
|
for i := 0; i < len(upsert); i += 2 {
|
||||||
|
var (
|
||||||
|
key = fmt.Sprintf("%v", upsert[i])
|
||||||
|
value interface{}
|
||||||
|
)
|
||||||
|
if len(upsert) > i {
|
||||||
|
value = upsert[i+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
params[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
// UrlEncode escapes a series of values (joined with no delimiter)
|
// UrlEncode escapes a series of values (joined with no delimiter)
|
||||||
func UrlEncode(values ...interface{}) string {
|
func UrlEncode(values ...interface{}) string {
|
||||||
var result string
|
var result string
|
||||||
|
|
|
@ -5,7 +5,10 @@
|
||||||
<section class="hero is-link is-bold">
|
<section class="hero is-link is-bold">
|
||||||
<div class="hero-body">
|
<div class="hero-body">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="title">Blocked Users</h1>
|
<h1 class="title">
|
||||||
|
<i class="fa fa-hand mr-2"></i>
|
||||||
|
Blocked Users
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -234,6 +234,12 @@
|
||||||
Blocked Users
|
Blocked Users
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/users/muted">
|
||||||
|
<span class="icon"><i class="fa fa-eye-slash"></i></span>
|
||||||
|
Muted Users
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/logout">
|
<a href="/logout">
|
||||||
<span class="icon"><i class="fa fa-arrow-right-from-bracket"></i></span>
|
<span class="icon"><i class="fa fa-arrow-right-from-bracket"></i></span>
|
||||||
|
|
74
web/templates/account/mute_list.html
Normal file
74
web/templates/account/mute_list.html
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
{{define "title"}}Muted Users{{end}}
|
||||||
|
{{define "content"}}
|
||||||
|
<div class="container">
|
||||||
|
{{$Root := .}}
|
||||||
|
<section class="hero is-link is-bold">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">
|
||||||
|
<i class="fa fa-eye-slash mr-2"></i>
|
||||||
|
Muted Users
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="p-4">
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
This page lists members of {{PrettyTitle}} who you have "muted" so that their content
|
||||||
|
will not appear to you in certain areas of the site. Currently, only <strong>Site Gallery</strong>
|
||||||
|
mutes exist: so you can hide somebody's pictures from ever appearing to you on the Site Gallery.
|
||||||
|
In the future, more kinds of mute lists may be available to manage from this page.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
You have muted {{.Pager.Total}} user{{Pluralize64 .Pager.Total}}
|
||||||
|
(page {{.Pager.Page}} of {{.Pager.Pages}}).
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
{{SimplePager .Pager}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
|
||||||
|
{{range .MutedUsers}}
|
||||||
|
<div class="column is-half-tablet is-one-third-desktop">
|
||||||
|
|
||||||
|
<form action="/users/mutelist/add" method="POST">
|
||||||
|
{{InputCSRF}}
|
||||||
|
<input type="hidden" name="username" value="{{.Username}}">
|
||||||
|
<input type="hidden" name="context" value="site_gallery">
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="media block">
|
||||||
|
<div class="media-left">
|
||||||
|
{{template "avatar-64x64" .}}
|
||||||
|
</div>
|
||||||
|
<div class="media-content">
|
||||||
|
<p class="title is-4">{{.NameOrUsername}}</p>
|
||||||
|
<p class="subtitle is-6">
|
||||||
|
<span class="icon"><i class="fa fa-user"></i></span>
|
||||||
|
<a href="/u/{{.Username}}">{{.Username}}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer class="card-footer">
|
||||||
|
<button type="submit" name="unmute" value="true" class="card-footer-item button is-danger">
|
||||||
|
<span class="icon"><i class="fa fa-xmark"></i></span>
|
||||||
|
<span>Unmute User</span>
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{end}}<!-- range .MutedUsers -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
77
web/templates/account/mute_list_add.html
Normal file
77
web/templates/account/mute_list_add.html
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
{{define "title"}}Add to {{.MuteListName}} Mute List{{end}}
|
||||||
|
{{define "content"}}
|
||||||
|
<div class="container">
|
||||||
|
<section class="hero is-link is-bold">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="title">
|
||||||
|
<i class="fa fa-eye-slash mr-2"></i>
|
||||||
|
Add to {{.MuteListName}} Mute List
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="block p-4">
|
||||||
|
<div class="columns is-centered">
|
||||||
|
<div class="column is-half">
|
||||||
|
|
||||||
|
<div class="card" style="width: 100%; max-width: 640px">
|
||||||
|
<header class="card-header has-background-link">
|
||||||
|
<p class="card-header-title has-text-light">
|
||||||
|
<span class="icon mr-2"><i class="fa fa-eye-slash"></i></span>
|
||||||
|
<span>Add to {{.MuteListName}} Mute List</span>
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
Confirm that you wish to add <strong>{{.User.Username}}</strong>
|
||||||
|
to your <strong>{{.MuteListName}}</strong> mute list by clicking the button below.
|
||||||
|
</div>
|
||||||
|
<div class="media block">
|
||||||
|
<div class="media-left">
|
||||||
|
{{template "avatar-64x64" .User}}
|
||||||
|
</div>
|
||||||
|
<div class="media-content">
|
||||||
|
<p class="title is-4">{{.User.NameOrUsername}}</p>
|
||||||
|
<p class="subtitle is-6">
|
||||||
|
<span class="icon"><i class="fa fa-user"></i></span>
|
||||||
|
<a href="/u/{{.User.Username}}" target="_blank">{{.User.Username}}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="/users/mutelist/add" method="POST">
|
||||||
|
{{InputCSRF}}
|
||||||
|
<input type="hidden" name="username" value="{{.User.Username}}">
|
||||||
|
<input type="hidden" name="context" value="{{.Context}}">
|
||||||
|
<input type="hidden" name="next" value="{{.Next}}">
|
||||||
|
|
||||||
|
<!-- Explain what will happen -->
|
||||||
|
{{if eq .Context "site_gallery"}}
|
||||||
|
<div class="block">
|
||||||
|
By continuing, <strong>you will no longer see <a href="/u/{{.User.Username}}">{{.User.Username}}</a>'s photos appear on the site-wide Photo Gallery.</strong>
|
||||||
|
You may still see their photos when looking at their own Photo Gallery directly, from their profile page.
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<div class="field has-text-centered">
|
||||||
|
<button type="submit" class="button is-success">
|
||||||
|
Continue
|
||||||
|
</button>
|
||||||
|
<a href="{{.Next}}" class="button">
|
||||||
|
Cancel
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{end}}
|
|
@ -131,6 +131,15 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li>
|
||||||
|
<a href="/users/muted">
|
||||||
|
<strong><i class="fa fa-eye-slash mr-1"></i> Muted Users</strong>
|
||||||
|
<p class="help">
|
||||||
|
View and manage your mute lists.
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<a href="/notes/me">
|
<a href="/notes/me">
|
||||||
<strong><i class="fa fa-pen-to-square mr-1"></i> My User Notes</strong>
|
<strong><i class="fa fa-pen-to-square mr-1"></i> My User Notes</strong>
|
||||||
|
|
|
@ -88,6 +88,19 @@
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
<!-- Reusable "mute site gallery" component -->
|
||||||
|
<!-- Parameter: the photo owner User object -->
|
||||||
|
{{define "mute-site-gallery"}}
|
||||||
|
<div class="columns is-centered is-mobile">
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<a href="/users/mutelist/add?username={{.User.Username}}&context=site_gallery&next={{.Request.URL.String}}" class="has-text-grey is-size-7">
|
||||||
|
<i class="fa fa-eye-slash"></i>
|
||||||
|
Don't show {{.User.Username}}'s photos
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<!-- Main content template -->
|
<!-- Main content template -->
|
||||||
{{define "content"}}
|
{{define "content"}}
|
||||||
{{if not .IsSiteGallery}}
|
{{if not .IsSiteGallery}}
|
||||||
|
@ -615,6 +628,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
<!-- Mute this user from Site Gallery -->
|
||||||
|
{{if and $Root.IsSiteGallery (ne $Root.CurrentUser.ID .UserID) ($Root.UserMap.Has .UserID)}}
|
||||||
|
{{$Owner := $Root.UserMap.Get .UserID}}
|
||||||
|
{{$SubVariables := NewHashMap "User" $Owner "Request" $Root.Request}}
|
||||||
|
{{template "mute-site-gallery" $SubVariables}}
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="card-footer">
|
<footer class="card-footer">
|
||||||
|
@ -740,6 +760,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
<!-- Mute this user from Site Gallery -->
|
||||||
|
{{if and $Root.IsSiteGallery (ne $Root.CurrentUser.ID .UserID) ($Root.UserMap.Has .UserID)}}
|
||||||
|
{{$Owner := $Root.UserMap.Get .UserID}}
|
||||||
|
{{$SubVariables := NewHashMap "User" $Owner "Request" $Root.Request}}
|
||||||
|
{{template "mute-site-gallery" $SubVariables}}
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="card-footer">
|
<footer class="card-footer">
|
||||||
|
|
Loading…
Reference in New Issue
Block a user