Block Lists
Implement block lists. They work like friend lists but are unidirectional, but take effect in both directions (blocker and blockee can not see one another on the site -- except admin users can always see all users). * Profile page says 404 * User gallery says 404 * User search page filters out blocked users * Compose endpoint blocks sending messages to blocked users (except admin) * Site Gallery filters photos by blocked (and uncertified) users * Inbox page hides chat list for blocked users (can still read the chat history if you have a link to the old thread)
This commit is contained in:
parent
e4406feb1e
commit
030fadcf8d
|
@ -4,6 +4,7 @@ package config
|
|||
var (
|
||||
PageSizeMemberSearch = 60
|
||||
PageSizeFriends = 12
|
||||
PageSizeBlockList = 12
|
||||
PageSizeAdminCertification = 20
|
||||
PageSizeSiteGallery = 18
|
||||
PageSizeUserGallery = 18
|
||||
|
|
|
@ -43,6 +43,12 @@ func Profile() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
// Is either one blocking?
|
||||
if models.IsBlocking(currentUser.ID, user.ID) && !currentUser.IsAdmin {
|
||||
templates.NotFoundPage(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
vars := map[string]interface{}{
|
||||
"User": user,
|
||||
"IsFriend": models.FriendStatus(currentUser.ID, user.ID),
|
||||
|
|
|
@ -42,6 +42,14 @@ func Search() http.HandlerFunc {
|
|||
ageMin, ageMax = ageMax, ageMin
|
||||
}
|
||||
|
||||
// Get current user.
|
||||
currentUser, err := session.CurrentUser(r)
|
||||
if err != nil {
|
||||
session.FlashError(w, r, "Couldn't get current user!")
|
||||
templates.Redirect(w, "/")
|
||||
return
|
||||
}
|
||||
|
||||
// Sort options.
|
||||
for _, v := range sortWhitelist {
|
||||
if sort == v {
|
||||
|
@ -64,7 +72,7 @@ func Search() http.HandlerFunc {
|
|||
}
|
||||
pager.ParsePage(r)
|
||||
|
||||
users, err := models.SearchUsers(&models.UserSearch{
|
||||
users, err := models.SearchUsers(currentUser.ID, &models.UserSearch{
|
||||
EmailOrUsername: username,
|
||||
Gender: gender,
|
||||
Orientation: orientation,
|
||||
|
|
114
pkg/controller/block/block.go
Normal file
114
pkg/controller/block/block.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
package block
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/apps/gosocial/pkg/config"
|
||||
"git.kirsle.net/apps/gosocial/pkg/models"
|
||||
"git.kirsle.net/apps/gosocial/pkg/session"
|
||||
"git.kirsle.net/apps/gosocial/pkg/templates"
|
||||
)
|
||||
|
||||
// Blocked list.
|
||||
func Blocked() http.HandlerFunc {
|
||||
tmpl := templates.Must("account/block_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 blocklist.
|
||||
pager := &models.Pagination{
|
||||
PerPage: config.PageSizeBlockList,
|
||||
Sort: "updated_at desc",
|
||||
}
|
||||
pager.ParsePage(r)
|
||||
blocked, err := models.PaginateBlockList(currentUser.ID, pager)
|
||||
if err != nil {
|
||||
session.FlashError(w, r, "Couldn't paginate block list: %s", err)
|
||||
templates.Redirect(w, "/")
|
||||
return
|
||||
}
|
||||
|
||||
var vars = map[string]interface{}{
|
||||
"BlockedUsers": blocked,
|
||||
"Pager": pager,
|
||||
}
|
||||
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BlockUser controller.
|
||||
func BlockUser() http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// POST only.
|
||||
if r.Method != http.MethodPost {
|
||||
session.FlashError(w, r, "Unacceptable Request Method")
|
||||
templates.Redirect(w, "/")
|
||||
return
|
||||
}
|
||||
|
||||
// Form fields
|
||||
var (
|
||||
username = strings.ToLower(r.PostFormValue("username"))
|
||||
unblock = r.PostFormValue("unblock") == "true"
|
||||
)
|
||||
|
||||
// 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, "/")
|
||||
return
|
||||
}
|
||||
|
||||
// Unblocking?
|
||||
if unblock {
|
||||
if err := models.UnblockUser(currentUser.ID, user.ID); err != nil {
|
||||
session.FlashError(w, r, "Couldn't unblock this user: %s.", err)
|
||||
} else {
|
||||
session.Flash(w, r, "You have removed %s from your block list.", user.Username)
|
||||
}
|
||||
templates.Redirect(w, "/users/blocked")
|
||||
return
|
||||
}
|
||||
|
||||
// Can't block yourself.
|
||||
if currentUser.ID == user.ID {
|
||||
session.FlashError(w, r, "You can't block yourself!")
|
||||
templates.Redirect(w, "/u/"+username)
|
||||
return
|
||||
}
|
||||
|
||||
// Can't block admins.
|
||||
if user.IsAdmin {
|
||||
session.FlashError(w, r, "You can not block site administrators.")
|
||||
templates.Redirect(w, "/u/"+username)
|
||||
return
|
||||
}
|
||||
|
||||
// Block the target user.
|
||||
if err := models.AddBlock(currentUser.ID, user.ID); err != nil {
|
||||
session.FlashError(w, r, "Couldn't block this user: %s.", err)
|
||||
} else {
|
||||
session.Flash(w, r, "You have added %s to your block list.", user.Username)
|
||||
}
|
||||
|
||||
templates.Redirect(w, "/users/blocked")
|
||||
})
|
||||
}
|
|
@ -34,6 +34,13 @@ func Compose() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
// Any blocking?
|
||||
if models.IsBlocking(currentUser.ID, user.ID) && !currentUser.IsAdmin {
|
||||
session.FlashError(w, r, "You are blocked from sending a message to this user.")
|
||||
templates.Redirect(w, "/messages")
|
||||
return
|
||||
}
|
||||
|
||||
// POSTing?
|
||||
if r.Method == http.MethodPost {
|
||||
var (
|
||||
|
|
|
@ -34,7 +34,7 @@ func SiteGallery() http.HandlerFunc {
|
|||
Sort: "created_at desc",
|
||||
}
|
||||
pager.ParsePage(r)
|
||||
photos, err := models.PaginateGalleryPhotos(currentUser.IsAdmin, currentUser.Explicit, pager)
|
||||
photos, err := models.PaginateGalleryPhotos(currentUser.ID, currentUser.IsAdmin, currentUser.Explicit, pager)
|
||||
|
||||
// Bulk load the users associated with these photos.
|
||||
var userIDs = []uint64{}
|
||||
|
|
|
@ -46,6 +46,12 @@ func UserPhotos() http.HandlerFunc {
|
|||
}
|
||||
var isOwnPhotos = currentUser.ID == user.ID
|
||||
|
||||
// Is either one blocking?
|
||||
if models.IsBlocking(currentUser.ID, user.ID) && !currentUser.IsAdmin {
|
||||
templates.NotFoundPage(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// What set of visibilities to query?
|
||||
visibility := []models.PhotoVisibility{models.PhotoPublic}
|
||||
if isOwnPhotos || currentUser.IsAdmin {
|
||||
|
|
124
pkg/models/blocklist.go
Normal file
124
pkg/models/blocklist.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Block table.
|
||||
type Block struct {
|
||||
ID uint64 `gorm:"primaryKey"`
|
||||
SourceUserID uint64 `gorm:"index"`
|
||||
TargetUserID uint64 `gorm:"index"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// AddBlock is sourceUserId adding targetUserId to their block list.
|
||||
func AddBlock(sourceUserID, targetUserID uint64) error {
|
||||
// Unfriend in the process.
|
||||
RemoveFriend(sourceUserID, targetUserID)
|
||||
|
||||
// Did we already block this user?
|
||||
var b *Block
|
||||
forward := DB.Where(
|
||||
"source_user_id = ? AND target_user_id = ?",
|
||||
sourceUserID, targetUserID,
|
||||
).First(&b).Error
|
||||
|
||||
// Update existing.
|
||||
if forward == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create the block.
|
||||
b = &Block{
|
||||
SourceUserID: sourceUserID,
|
||||
TargetUserID: targetUserID,
|
||||
}
|
||||
return DB.Create(b).Error
|
||||
}
|
||||
|
||||
// IsBlocking quickly sees if either user blocks the other.
|
||||
func IsBlocking(sourceUserID, targetUserID uint64) bool {
|
||||
b := &Block{}
|
||||
result := DB.Where(
|
||||
"(source_user_id = ? AND target_user_id = ?) OR "+
|
||||
"(target_user_id = ? AND source_user_id = ?)",
|
||||
sourceUserID, targetUserID,
|
||||
sourceUserID, targetUserID,
|
||||
).First(&b)
|
||||
return result.Error == nil
|
||||
}
|
||||
|
||||
// IsBlocked quickly checks if sourceUserID currently blocks targetUserID.
|
||||
func IsBlocked(sourceUserID, targetUserID uint64) bool {
|
||||
b := &Block{}
|
||||
result := DB.Where(
|
||||
"source_user_id = ? AND target_user_id = ?",
|
||||
sourceUserID, targetUserID,
|
||||
).First(&b)
|
||||
return result.Error == nil
|
||||
}
|
||||
|
||||
// PaginateBlockList views a user's blocklist.
|
||||
func PaginateBlockList(userID uint64, pager *Pagination) ([]*User, error) {
|
||||
// We paginate over the Block table.
|
||||
var (
|
||||
bs = []*Block{}
|
||||
userIDs = []uint64{}
|
||||
query *gorm.DB
|
||||
)
|
||||
|
||||
query = DB.Where(
|
||||
"source_user_id = ?",
|
||||
userID,
|
||||
)
|
||||
|
||||
query = query.Order(pager.Sort)
|
||||
query.Model(&Block{}).Count(&pager.Total)
|
||||
result := query.Offset(pager.GetOffset()).Limit(pager.PerPage).Find(&bs)
|
||||
if result.Error != nil {
|
||||
return nil, result.Error
|
||||
}
|
||||
|
||||
// Now of these friends get their User objects.
|
||||
for _, b := range bs {
|
||||
userIDs = append(userIDs, b.TargetUserID)
|
||||
}
|
||||
|
||||
return GetUsers(userIDs)
|
||||
}
|
||||
|
||||
// BlockedUserIDs returns all user IDs blocked by the user.
|
||||
func BlockedUserIDs(userId uint64) []uint64 {
|
||||
var (
|
||||
bs = []*Block{}
|
||||
userIDs = []uint64{}
|
||||
)
|
||||
DB.Where("source_user_id = ? OR target_user_id = ?", userId, userId).Find(&bs)
|
||||
for _, row := range bs {
|
||||
for _, uid := range []uint64{row.TargetUserID, row.SourceUserID} {
|
||||
if uid != userId {
|
||||
userIDs = append(userIDs, uid)
|
||||
}
|
||||
}
|
||||
}
|
||||
return userIDs
|
||||
}
|
||||
|
||||
// UnblockUser removes targetUserID from your blocklist.
|
||||
func UnblockUser(sourceUserID, targetUserID uint64) error {
|
||||
result := DB.Where(
|
||||
"source_user_id = ? AND target_user_id = ?",
|
||||
sourceUserID, targetUserID,
|
||||
).Delete(&Block{})
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// Save photo.
|
||||
func (b *Block) Save() error {
|
||||
result := DB.Save(b)
|
||||
return result.Error
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -25,15 +26,33 @@ func GetMessage(id uint64) (*Message, error) {
|
|||
// GetMessages for a user.
|
||||
func GetMessages(userID uint64, sent bool, pager *Pagination) ([]*Message, error) {
|
||||
var (
|
||||
m = []*Message{}
|
||||
where = "target_user_id = ?"
|
||||
m = []*Message{}
|
||||
blockedUserIDs = BlockedUserIDs(userID)
|
||||
where = []string{}
|
||||
placeholders = []interface{}{}
|
||||
)
|
||||
|
||||
if sent {
|
||||
where = "source_user_id"
|
||||
where = append(where, "source_user_id = ?")
|
||||
placeholders = append(placeholders, userID)
|
||||
|
||||
if len(blockedUserIDs) > 0 {
|
||||
where = append(where, "target_user_id NOT IN ?")
|
||||
placeholders = append(placeholders, blockedUserIDs)
|
||||
}
|
||||
} else {
|
||||
where = append(where, "target_user_id = ?")
|
||||
placeholders = append(placeholders, userID)
|
||||
|
||||
if len(blockedUserIDs) > 0 {
|
||||
where = append(where, "source_user_id NOT IN ?")
|
||||
placeholders = append(placeholders, blockedUserIDs)
|
||||
}
|
||||
}
|
||||
|
||||
query := DB.Where(
|
||||
where, userID,
|
||||
strings.Join(where, " AND "),
|
||||
placeholders...,
|
||||
).Order(pager.Sort)
|
||||
|
||||
query.Model(&Message{}).Count(&pager.Total)
|
||||
|
|
|
@ -14,4 +14,5 @@ func AutoMigrate() {
|
|||
DB.AutoMigrate(&CertificationPhoto{})
|
||||
DB.AutoMigrate(&Message{})
|
||||
DB.AutoMigrate(&Friend{})
|
||||
DB.AutoMigrate(&Block{})
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package models
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
@ -111,26 +112,43 @@ func CountExplicitPhotos(userID uint64, visibility []PhotoVisibility) (int64, er
|
|||
|
||||
// PaginateGalleryPhotos gets a page of all public user photos for the site gallery. Admin view
|
||||
// returns ALL photos regardless of Gallery status.
|
||||
func PaginateGalleryPhotos(adminView bool, explicitOK bool, pager *Pagination) ([]*Photo, error) {
|
||||
func PaginateGalleryPhotos(userID uint64, adminView bool, explicitOK bool, pager *Pagination) ([]*Photo, error) {
|
||||
var (
|
||||
p = []*Photo{}
|
||||
query *gorm.DB
|
||||
p = []*Photo{}
|
||||
query *gorm.DB
|
||||
blocklist = BlockedUserIDs(userID)
|
||||
wheres = []string{}
|
||||
placeholders = []interface{}{}
|
||||
)
|
||||
|
||||
var explicit = []bool{false}
|
||||
if explicitOK {
|
||||
explicit = []bool{true, false}
|
||||
// Universal filters: public + gallery photos only.
|
||||
wheres = append(wheres, "visibility = ?", "gallery = ?")
|
||||
placeholders = append(placeholders, PhotoPublic, true)
|
||||
|
||||
// Filter blocked users.
|
||||
if len(blocklist) > 0 {
|
||||
wheres = append(wheres, "user_id NOT IN ?")
|
||||
placeholders = append(placeholders, blocklist)
|
||||
}
|
||||
|
||||
// Non-explicit pics unless the user opted in.
|
||||
if !explicitOK {
|
||||
wheres = append(wheres, "explicit = ?")
|
||||
placeholders = append(placeholders, false)
|
||||
}
|
||||
|
||||
// Only certified user photos.
|
||||
wheres = append(wheres,
|
||||
"EXISTS (SELECT 1 FROM users WHERE id = photos.user_id AND certified = true)",
|
||||
)
|
||||
|
||||
// Admin view: get ALL PHOTOS on the site, period.
|
||||
if adminView {
|
||||
query = DB
|
||||
} else {
|
||||
query = DB.Where(
|
||||
"visibility = ? AND gallery = ? AND explicit IN ?",
|
||||
PhotoPublic,
|
||||
true,
|
||||
explicit,
|
||||
strings.Join(wheres, " AND "),
|
||||
placeholders...,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -124,19 +124,25 @@ type UserSearch struct {
|
|||
AgeMax int
|
||||
}
|
||||
|
||||
// SearchUsers
|
||||
func SearchUsers(search *UserSearch, pager *Pagination) ([]*User, error) {
|
||||
// SearchUsers from the perspective of a given user.
|
||||
func SearchUsers(userID uint64, search *UserSearch, pager *Pagination) ([]*User, error) {
|
||||
if search == nil {
|
||||
search = &UserSearch{}
|
||||
}
|
||||
|
||||
var (
|
||||
users = []*User{}
|
||||
query *gorm.DB
|
||||
wheres = []string{}
|
||||
placeholders = []interface{}{}
|
||||
users = []*User{}
|
||||
query *gorm.DB
|
||||
wheres = []string{}
|
||||
placeholders = []interface{}{}
|
||||
blockedUserIDs = BlockedUserIDs(userID)
|
||||
)
|
||||
|
||||
if len(blockedUserIDs) > 0 {
|
||||
wheres = append(wheres, "id NOT IN ?")
|
||||
placeholders = append(placeholders, blockedUserIDs)
|
||||
}
|
||||
|
||||
if search.EmailOrUsername != "" {
|
||||
ilike := "%" + strings.TrimSpace(strings.ToLower(search.EmailOrUsername)) + "%"
|
||||
wheres = append(wheres, "(email LIKE ? OR username LIKE ?)")
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"git.kirsle.net/apps/gosocial/pkg/controller/account"
|
||||
"git.kirsle.net/apps/gosocial/pkg/controller/admin"
|
||||
"git.kirsle.net/apps/gosocial/pkg/controller/api"
|
||||
"git.kirsle.net/apps/gosocial/pkg/controller/block"
|
||||
"git.kirsle.net/apps/gosocial/pkg/controller/friend"
|
||||
"git.kirsle.net/apps/gosocial/pkg/controller/inbox"
|
||||
"git.kirsle.net/apps/gosocial/pkg/controller/index"
|
||||
|
@ -41,6 +42,8 @@ func New() http.Handler {
|
|||
mux.Handle("/messages/compose", middleware.LoginRequired(inbox.Compose()))
|
||||
mux.Handle("/friends", middleware.LoginRequired(friend.Friends()))
|
||||
mux.Handle("/friends/add", middleware.LoginRequired(friend.AddFriend()))
|
||||
mux.Handle("/users/block", middleware.LoginRequired(block.BlockUser()))
|
||||
mux.Handle("/users/blocked", middleware.LoginRequired(block.Blocked()))
|
||||
mux.Handle("/admin/unimpersonate", middleware.LoginRequired(admin.Unimpersonate()))
|
||||
|
||||
// Certification Required. Pages that only full (verified) members can access.
|
||||
|
|
99
web/templates/account/block_list.html
Normal file
99
web/templates/account/block_list.html
Normal file
|
@ -0,0 +1,99 @@
|
|||
{{define "title"}}Blocked 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">Blocked Users</h1>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="p-4">
|
||||
|
||||
<div class="block">
|
||||
You have blocked {{.Pager.Total}} user{{Pluralize64 .Pager.Total}}
|
||||
(page {{.Pager.Page}} of {{.Pager.Pages}}).
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<nav class="pagination" role="navigation" aria-label="pagination">
|
||||
<a class="pagination-previous{{if not .Pager.HasPrevious}} is-disabled{{end}}" title="Previous"
|
||||
href="{{.Request.URL.Path}}?page={{.Pager.Previous}}">Previous</a>
|
||||
<a class="pagination-next{{if not .Pager.HasNext}} is-disabled{{end}}" title="Next"
|
||||
href="{{.Request.URL.Path}}?page={{.Pager.Next}}">Next page</a>
|
||||
<ul class="pagination-list">
|
||||
{{$Root := .}}
|
||||
{{range .Pager.Iter}}
|
||||
<li>
|
||||
<a class="pagination-link{{if .IsCurrent}} is-current{{end}}"
|
||||
aria-label="Page {{.Page}}"
|
||||
href="{{$Root.Request.URL.Path}}?page={{.Page}}">
|
||||
{{.Page}}
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div class="columns is-multiline">
|
||||
|
||||
{{range .BlockedUsers}}
|
||||
<div class="column is-half-tablet is-one-third-desktop">
|
||||
|
||||
<form action="/users/block" method="POST">
|
||||
{{InputCSRF}}
|
||||
<input type="hidden" name="username" value="{{.Username}}">
|
||||
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<div class="media block">
|
||||
<div class="media-left">
|
||||
<figure class="image is-64x64">
|
||||
{{if .ProfilePhoto.ID}}
|
||||
<img src="{{PhotoURL .ProfilePhoto.CroppedFilename}}">
|
||||
{{else}}
|
||||
<img src="/static/img/shy.png">
|
||||
{{end}}
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-4">{{or .Name "(no name)"}}</p>
|
||||
<p class="subtitle is-6">
|
||||
<span class="icon"><i class="fa fa-user"></i></span>
|
||||
<a href="/u/{{.Username}}">{{.Username}}</a>
|
||||
{{if not .Certified}}
|
||||
<span class="has-text-danger">
|
||||
<span class="icon"><i class="fa fa-certificate"></i></span>
|
||||
<span>Not Certified!</span>
|
||||
</span>
|
||||
{{end}}
|
||||
|
||||
{{if .IsAdmin}}
|
||||
<span class="has-text-danger">
|
||||
<span class="icon"><i class="fa fa-gavel"></i></span>
|
||||
<span>Admin</span>
|
||||
</span>
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="card-footer">
|
||||
<button type="submit" name="unblock" value="true" class="card-footer-item button is-danger">
|
||||
<span class="icon"><i class="fa fa-xmark"></i></span>
|
||||
<span>Unblock User</span>
|
||||
</button>
|
||||
</footer>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
{{end}}<!-- range .BlockedUsers -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -105,6 +105,12 @@
|
|||
Certification Photo
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/users/blocked">
|
||||
<span class="icon"><i class="fa fa-hand"></i></span>
|
||||
Blocked Users
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/logout">
|
||||
<span class="icon"><i class="fa fa-arrow-right-from-bracket"></i></span>
|
||||
|
|
|
@ -89,77 +89,81 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="level">
|
||||
<div class="level-item">
|
||||
<div class="field has-addons">
|
||||
<form action="/friends/add" method="POST">
|
||||
{{InputCSRF}}
|
||||
<input type="hidden" name="username" value="{{.User.Username}}">
|
||||
<p class="control">
|
||||
<button type="submit" class="button"
|
||||
{{if not (eq .IsFriend "none")}}title="Friendship {{.IsFriend}}"{{end}}>
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
{{if eq .IsFriend "approved"}}
|
||||
<i class="fa fa-check has-text-success"></i>
|
||||
{{else if eq .IsFriend "pending"}}
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
{{else}}
|
||||
<i class="fa fa-plus"></i>
|
||||
{{end}}
|
||||
</span>
|
||||
<span>Friend{{if eq .IsFriend "approved"}}s{{end}}</span>
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
<div class="columns is-centered is-gapless">
|
||||
<div class="column is-narrow has-text-centered">
|
||||
<form action="/friends/add" method="POST">
|
||||
{{InputCSRF}}
|
||||
<input type="hidden" name="username" value="{{.User.Username}}">
|
||||
|
||||
<p class="control">
|
||||
<a href="/messages/compose?to={{.User.Username}}" class="button">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa fa-message"></i>
|
||||
</span>
|
||||
<span>Message</span>
|
||||
<button type="submit" class="button is-fullwidth"
|
||||
{{if not (eq .IsFriend "none")}}title="Friendship {{.IsFriend}}"{{end}}>
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
{{if eq .IsFriend "approved"}}
|
||||
<i class="fa fa-check has-text-success"></i>
|
||||
{{else if eq .IsFriend "pending"}}
|
||||
<i class="fa fa-spinner fa-spin"></i>
|
||||
{{else}}
|
||||
<i class="fa fa-plus"></i>
|
||||
{{end}}
|
||||
</span>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p class="control">
|
||||
<button type="button" class="button">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa fa-thumbs-up"></i>
|
||||
</span>
|
||||
<span>Like</span>
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<!-- <p class="control">
|
||||
<button type="button" class="button">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa fa-flag"></i>
|
||||
</span>
|
||||
<span>Flag</span>
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
<p class="control">
|
||||
<button type="button" class="button">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa fa-hand"></i>
|
||||
</span>
|
||||
<span>Block</span>
|
||||
</span>
|
||||
</button>
|
||||
</p> -->
|
||||
</div>
|
||||
<span>Friend{{if eq .IsFriend "approved"}}s{{end}}</span>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column is-narrow has-text-centered">
|
||||
<a href="/messages/compose?to={{.User.Username}}" class="button is-fullwidth">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa fa-message"></i>
|
||||
</span>
|
||||
<span>Message</span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="column is-narrow has-text-centered">
|
||||
<button type="button" class="button is-fullwidth">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa fa-thumbs-up"></i>
|
||||
</span>
|
||||
<span>Like</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- <div class="column is-narrow has-text-centered">
|
||||
<button type="button" class="button is-fullwidth">
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa fa-flag"></i>
|
||||
</span>
|
||||
<span>Flag</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>-->
|
||||
|
||||
<div class="column is-narrow has-text-centered">
|
||||
<form action="/users/block" method="POST">
|
||||
{{InputCSRF}}
|
||||
<input type="hidden" name="username" value="{{.User.Username}}">
|
||||
|
||||
<button type="submit" class="button is-fullwidth"
|
||||
{{if not (eq .IsFriend "none")}}title="Friendship {{.IsFriend}}"{{end}}>
|
||||
<span class="icon-text">
|
||||
<span class="icon">
|
||||
<i class="fa fa-hand"></i>
|
||||
</span>
|
||||
<span>Block</span>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div><!-- columns -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
id="display_name"
|
||||
name="display_name"
|
||||
placeholder="John Doe"
|
||||
value="{{$User.Name}}">
|
||||
value="{{or $User.Name ""}}">
|
||||
</div>
|
||||
|
||||
<div class="column field is-half">
|
||||
|
|
|
@ -119,7 +119,7 @@
|
|||
<div class="block">
|
||||
<div class="columns">
|
||||
<div class="column is-narrow">
|
||||
<strong>Status:
|
||||
<strong>Status:</strong>
|
||||
</div>
|
||||
<div class="column">
|
||||
{{if eq .Status "pending"}}
|
||||
|
|
Loading…
Reference in New Issue
Block a user