Delete the inner circle

This commit is contained in:
Noah Petherbridge 2024-08-10 11:54:37 -07:00
parent 1936bcde37
commit e70ede301f
45 changed files with 15 additions and 1225 deletions

View File

@ -6,11 +6,9 @@ const (
// - Chat: have operator controls in the chat room
// - Forum: ability to edit and delete user posts
// - Photo: omniscient view of all gallery photos, can edit/delete photos
// - Inner circle: ability to remove users from it
ScopeChatModerator = "social.moderator.chat"
ScopeForumModerator = "social.moderator.forum"
ScopePhotoModerator = "social.moderator.photo"
ScopeCircleModerator = "social.moderator.inner-circle"
ScopeChatModerator = "social.moderator.chat"
ScopeForumModerator = "social.moderator.forum"
ScopePhotoModerator = "social.moderator.photo"
// Certification photo management
// - Approve: ability to respond to pending certification pics
@ -48,9 +46,6 @@ const (
// Admins with this scope can not be blocked by users.
ScopeUnblockable = "admin.unblockable"
// Special scope to mark an admin automagically in the Inner Circle
ScopeIsInnerCircle = "admin.override.inner-circle"
// The global wildcard scope gets all available permissions.
ScopeSuperuser = "*"
)
@ -60,7 +55,6 @@ var AdminScopeDescriptions = map[string]string{
ScopeChatModerator: "Have operator controls in the chat room (can mark cameras as explicit, or kick/ban people from chat).",
ScopeForumModerator: "Ability to moderate the forum (edit or delete posts).",
ScopePhotoModerator: "Ability to moderate photo galleries (can see all private or friends-only photos, and edit or delete them).",
ScopeCircleModerator: "Ability to remove members from the inner circle.",
ScopeCertificationApprove: "Ability to see pending certification pictures and approve or reject them.",
ScopeCertificationList: "Ability to see existing certification pictures that have already been approved or rejected.",
ScopeCertificationView: "Ability to see and double check a specific user's certification picture on demand.",
@ -78,7 +72,6 @@ var AdminScopeDescriptions = map[string]string{
ScopeChangeLog: "Ability to see website change logs (e.g. history of a certification photo, gallery photo settings, etc.)",
ScopeUserNotes: "Ability to see all notes written about a user, or to see all notes written by admins.",
ScopeUnblockable: "This admin can not be added to user block lists.",
ScopeIsInnerCircle: "This admin is automatically part of the inner circle.",
ScopeSuperuser: "This admin has access to ALL admin features on the website.",
}
@ -94,7 +87,6 @@ func ListAdminScopes() []string {
ScopeChatModerator,
ScopeForumModerator,
ScopePhotoModerator,
ScopeCircleModerator,
ScopeCertificationApprove,
ScopeCertificationList,
ScopeCertificationView,
@ -112,7 +104,6 @@ func ListAdminScopes() []string {
ScopeChangeLog,
ScopeUserNotes,
ScopeUnblockable,
ScopeIsInnerCircle,
}
}

View File

@ -107,9 +107,6 @@ const (
PhotoQuotaUncertified = 6
PhotoQuotaCertified = 100
// Min number of public photos for inner circle members to see the prompt to invite.
InnerCircleMinimumPublicPhotos = 5
// Rate limit for too many Site Gallery pictures.
// Some users sign up and immediately max out their gallery and spam
// the Site Gallery page. These limits can ensure only a few Site Gallery

View File

@ -106,7 +106,6 @@ var (
// Default forum categories for forum landing page.
ForumCategories = []string{
"Rules and Announcements",
"The Inner Circle",
"Nudists",
"Exhibitionists",
"Photo Boards",

View File

@ -1,216 +0,0 @@
package account
import (
"fmt"
"net/http"
"code.nonshy.com/nonshy/website/pkg/config"
"code.nonshy.com/nonshy/website/pkg/log"
"code.nonshy.com/nonshy/website/pkg/models"
"code.nonshy.com/nonshy/website/pkg/session"
"code.nonshy.com/nonshy/website/pkg/templates"
)
// InnerCircle is the landing page for inner circle members only.
func InnerCircle() http.HandlerFunc {
tmpl := templates.Must("account/inner_circle.html")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
currentUser, err := session.CurrentUser(r)
if err != nil || !currentUser.IsInnerCircle() {
templates.NotFoundPage(w, r)
return
}
var vars = map[string]interface{}{
"InnerCircleMinimumPublicPhotos": config.InnerCircleMinimumPublicPhotos,
}
if err := tmpl.Execute(w, r, vars); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
}
// InviteCircle is the landing page to invite a user into the circle.
func InviteCircle() http.HandlerFunc {
tmpl := templates.Must("account/invite_circle.html")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
currentUser, err := session.CurrentUser(r)
if err != nil || !currentUser.IsInnerCircle() {
templates.NotFoundPage(w, r)
return
}
// Invite whom?
username := r.FormValue("to")
user, err := models.FindUser(username)
if err != nil {
templates.NotFoundPage(w, r)
return
}
if currentUser.ID == user.ID && currentUser.InnerCircle {
session.FlashError(w, r, "You are already part of the inner circle.")
templates.Redirect(w, "/inner-circle")
return
}
// Any blocking?
if models.IsBlocking(currentUser.ID, user.ID) && !currentUser.IsAdmin {
session.FlashError(w, r, "You are blocked from inviting this user to the circle.")
templates.Redirect(w, "/inner-circle")
return
}
// POSTing?
if r.Method == http.MethodPost {
var (
confirm = r.FormValue("intent") == "confirm"
)
if !confirm {
templates.Redirect(w, "/u/"+username)
return
}
// Add them!
if err := models.AddToInnerCircle(user); err != nil {
session.FlashError(w, r, "Couldn't add to the inner circle: %s", err)
} else {
session.Flash(w, r, "%s has been added to the inner circle!", user.Username)
}
log.Info("InnerCircle: %s adds %s to the inner circle", currentUser.Username, user.Username)
templates.Redirect(w, "/u/"+user.Username+"/photos")
return
}
var vars = map[string]interface{}{
"User": user,
}
if err := tmpl.Execute(w, r, vars); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
}
// RemoveCircle is the admin-only page to remove a member from the circle.
func RemoveCircle() http.HandlerFunc {
tmpl := templates.Must("account/remove_circle.html")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
currentUser, err := session.CurrentUser(r)
if err != nil || !currentUser.IsInnerCircle() {
templates.NotFoundPage(w, r)
return
}
// Remove whom?
username := r.FormValue("to")
user, err := models.FindUser(username)
if err != nil {
templates.NotFoundPage(w, r)
return
}
// POSTing?
if r.Method == http.MethodPost {
var (
confirm = r.FormValue("intent") == "confirm"
)
if !confirm {
templates.Redirect(w, "/u/"+username)
return
}
// Admin (with the correct scope): remove them now.
if currentUser.HasAdminScope(config.ScopeCircleModerator) {
if err := models.RemoveFromInnerCircle(user); err != nil {
session.FlashError(w, r, "Couldn't remove from the inner circle: %s", err)
}
session.Flash(w, r, "%s has been removed from the inner circle!", user.Username)
} else {
// Non-admin user: request removal only.
fb := &models.Feedback{
Intent: "report.circle",
Subject: "Inner Circle Removal Request",
TableName: "users",
TableID: user.ID,
Message: fmt.Sprintf(
"An inner circle member has flagged that **%s** no longer qualifies to be a part of the inner circle and should be removed.",
user.Username,
),
}
if err := models.CreateFeedback(fb); err != nil {
session.FlashError(w, r, "Couldn't create admin notification: %s", err)
} else {
session.Flash(w, r, "A request to remove %s from the inner circle has been sent to the site admin.", user.Username)
}
}
templates.Redirect(w, "/u/"+user.Username)
return
}
var vars = map[string]interface{}{
"User": user,
}
if err := tmpl.Execute(w, r, vars); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
}
// LeaveCircle allows users to remove themself from the circle.
func LeaveCircle() http.HandlerFunc {
tmpl := templates.Must("account/leave_circle.html")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
currentUser, err := session.CurrentUser(r)
if err != nil || !currentUser.IsInnerCircle() {
templates.NotFoundPage(w, r)
return
}
// Submitting the form?
if r.Method == http.MethodPost {
var (
confirm = r.PostFormValue("confirm") == "true"
dontReinvite = r.PostFormValue("dont_reinvite") == "true"
)
if !confirm {
templates.Redirect(w, r.URL.Path)
return
}
// Remove them from the circle now.
if err := models.RemoveFromInnerCircle(currentUser); err != nil {
session.FlashError(w, r, "Error updating your inner circle status: %s", err)
templates.Redirect(w, r.URL.Path)
return
}
// Confirmation message, + don't reinvite again?
if dontReinvite {
currentUser.SetProfileField("inner_circle_optout", "true")
session.Flash(w, r, "You have been removed from the inner circle, and you WILL NOT be invited again in the future.")
} else {
session.Flash(w, r, "You have been removed from the inner circle.")
}
templates.Redirect(w, "/me")
return
}
if err := tmpl.Execute(w, r, nil); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
})
}

View File

@ -159,7 +159,6 @@ func Search() http.HandlerFunc {
Certified: certifiedOnly,
NearCity: city,
NotCertified: isCertified == "false",
InnerCircle: isCertified == "circle",
ShyAccounts: isCertified == "shy",
IsBanned: isCertified == "banned",
IsDisabled: isCertified == "disabled",

View File

@ -50,12 +50,7 @@ func Feedback() http.HandlerFunc {
if err != nil {
session.FlashError(w, r, "Couldn't visit user %d: %s", fb.TableID, err)
} else {
// If this is an "inner circle removal" report, go to their gallery and filter pics by Public.
if fb.Intent == "report.circle" {
templates.Redirect(w, "/u/"+user.Username+"/photos?visibility=public")
} else {
templates.Redirect(w, "/u/"+user.Username)
}
templates.Redirect(w, "/u/"+user.Username)
return
}
case "photos":

View File

@ -104,8 +104,6 @@ func Landing() http.HandlerFunc {
avatar = "/static/img/shy-private.png"
case models.PhotoFriends:
avatar = "/static/img/shy-friends.png"
case models.PhotoInnerCircle:
avatar = "/static/img/shy-secret.png"
}
// Country flag emoji.
@ -125,7 +123,6 @@ func Landing() http.HandlerFunc {
// Create the JWT claims.
claims := Claims{
IsAdmin: currentUser.HasAdminScope(config.ScopeChatModerator),
VIP: currentUser.IsInnerCircle(),
Avatar: avatar,
ProfileURL: "/u/" + currentUser.Username,
Nickname: currentUser.NameOrUsername(),

View File

@ -64,7 +64,6 @@ func AddEdit() http.HandlerFunc {
isExplicit = r.PostFormValue("explicit") == "true"
isPrivileged = r.PostFormValue("privileged") == "true"
isPermitPhotos = r.PostFormValue("permit_photos") == "true"
isInnerCircle = r.PostFormValue("inner_circle") == "true"
isPrivate = r.PostFormValue("private") == "true"
)
@ -84,7 +83,6 @@ func AddEdit() http.HandlerFunc {
models.NewFieldDiff("Explicit", forum.Explicit, isExplicit),
models.NewFieldDiff("Privileged", forum.Privileged, isPrivileged),
models.NewFieldDiff("PermitPhotos", forum.PermitPhotos, isPermitPhotos),
models.NewFieldDiff("InnerCircle", forum.InnerCircle, isInnerCircle),
models.NewFieldDiff("Private", forum.Private, isPrivate),
}
@ -94,7 +92,6 @@ func AddEdit() http.HandlerFunc {
forum.Explicit = isExplicit
forum.Privileged = isPrivileged
forum.PermitPhotos = isPermitPhotos
forum.InnerCircle = isInnerCircle
forum.Private = isPrivate
// Save it.
@ -131,7 +128,6 @@ func AddEdit() http.HandlerFunc {
Explicit: isExplicit,
Privileged: isPrivileged,
PermitPhotos: isPermitPhotos,
InnerCircle: isInnerCircle,
Private: isPrivate,
}
@ -149,7 +145,6 @@ func AddEdit() http.HandlerFunc {
"* Explicit: %v\n"+
"* Privileged: %v\n"+
"* Photos: %v\n"+
"* Inner Circle: %v\n"+
"* Private: %v",
forum.Category,
forum.Title,
@ -158,7 +153,6 @@ func AddEdit() http.HandlerFunc {
forum.Explicit,
forum.Privileged,
forum.PermitPhotos,
forum.InnerCircle,
forum.Private,
))

View File

@ -35,12 +35,6 @@ func Forum() http.HandlerFunc {
return
}
// Is it an inner circle forum?
if forum.InnerCircle && !currentUser.IsInnerCircle() {
templates.NotFoundPage(w, r)
return
}
// Is it a private forum?
if forum.Private && !currentUser.IsAdmin {
templates.NotFoundPage(w, r)

View File

@ -51,12 +51,6 @@ func Thread() http.HandlerFunc {
return
}
// Is it an inner circle forum?
if forum.InnerCircle && !currentUser.IsInnerCircle() {
templates.NotFoundPage(w, r)
return
}
// Is it a private forum?
if forum.Private && !currentUser.IsAdmin {
templates.NotFoundPage(w, r)

View File

@ -83,9 +83,8 @@ func Edit() http.HandlerFunc {
setProfilePic = r.FormValue("intent") == "profile-pic"
crop = pphoto.ParseCropCoords(r.FormValue("crop"))
// Are we GOING private or changing to Inner Circle?
// Are we GOING private?
goingPrivate = visibility == models.PhotoPrivate && visibility != photo.Visibility
goingCircle = visibility == models.PhotoInnerCircle && visibility != photo.Visibility
)
if len(altText) > config.AltTextMaxLength {
@ -164,7 +163,7 @@ func Edit() http.HandlerFunc {
}
// If this picture has moved to Private, revoke any notification we gave about it before.
if goingPrivate || goingCircle {
if goingPrivate {
log.Info("The picture is GOING PRIVATE (to %s), revoke any notifications about it", photo.Visibility)
models.RemoveNotification("photos", photo.ID)
}
@ -241,13 +240,6 @@ func Delete() http.HandlerFunc {
}
}
// Inner circle warning: if this deletion would drop them below the 5 public picture
// threshold, warn them they will be removed from the circle if they continue.
var innerCircleWarning bool
if currentUser.IsInnerCircle() && photo.Visibility == models.PhotoPublic && models.CountPublicPhotos(currentUser.ID) <= 5 {
innerCircleWarning = true
}
// Confirm deletion?
if r.Method == http.MethodPost {
confirm := r.PostFormValue("confirm") == "true"
@ -291,15 +283,6 @@ func Delete() http.HandlerFunc {
// Log the change.
models.LogDeleted(currentUser, requestUser, "photos", photo.ID, "Deleted the photo.", photo)
// Remove them from the inner circle?
if innerCircleWarning {
if err := models.RemoveFromInnerCircle(currentUser); err != nil {
session.FlashError(w, r, "Couldn't remove from the inner circle: %s", err)
} else {
session.Flash(w, r, "You have been removed from the inner circle because your count of public photos has dropped below 5.")
}
}
session.Flash(w, r, "Photo deleted!")
// Maybe kick them from chat if this deletion makes them into a Shy Account.
@ -313,8 +296,7 @@ func Delete() http.HandlerFunc {
}
var vars = map[string]interface{}{
"Photo": photo,
"InnerCircleWarning": innerCircleWarning,
"Photo": photo,
}
if err := tmpl.Execute(w, r, vars); err != nil {

View File

@ -222,15 +222,6 @@ func notifyFriendsNewPhoto(photo *models.Photo, currentUser *models.User) {
notifyUserIDs = models.PrivateGranteeUserIDs(currentUser.ID)
log.Info("Notify %d private grantees about the new photo by %s", len(notifyUserIDs), currentUser.Username)
}
} else if photo.Visibility == models.PhotoInnerCircle {
// Inner circle members. If the pic is also Explicit, further narrow to explicit friend IDs.
if photo.Explicit {
notifyUserIDs = models.FriendIDsInCircleAreExplicit(currentUser.ID)
log.Info("Notify %d EXPLICIT circle friends about the new photo by %s", len(notifyUserIDs), currentUser.Username)
} else {
notifyUserIDs = models.FriendIDsInCircle(currentUser.ID)
log.Info("Notify %d circle friends about the new photo by %s", len(notifyUserIDs), currentUser.Username)
}
} else {
// Friends only: we will notify exactly the friends we selected above.
notifyUserIDs = friendIDs

View File

@ -32,9 +32,6 @@ func UserPhotos() http.HandlerFunc {
filterVisibility = r.FormValue("visibility")
sort = r.FormValue("sort")
sortOK bool
// Inner circle invite view?
innerCircleInvite = r.FormValue("intent") == "inner_circle"
)
// Sort options.
@ -73,11 +70,6 @@ func UserPhotos() http.HandlerFunc {
isShyFrom = !isOwnPhotos && (currentUser.IsShyFrom(user) || (isShy && !areFriends))
)
// Inner circle invite: not if we are not in the circle ourselves.
if innerCircleInvite && !currentUser.IsInnerCircle() {
innerCircleInvite = false
}
// Bail early if we are shy from this user.
if isShy && isShyFrom {
var vars = map[string]interface{}{
@ -126,11 +118,6 @@ func UserPhotos() http.HandlerFunc {
visibility = append(visibility, models.PhotoFriends)
}
// Inner circle photos.
if currentUser.IsInnerCircle() {
visibility = append(visibility, models.PhotoInnerCircle)
}
// If we are Filtering by Visibility, ensure the target visibility is accessible to us.
if filterVisibility != "" {
var isOK bool
@ -220,14 +207,11 @@ func UserPhotos() http.HandlerFunc {
"NoteCount": models.CountNotesAboutUser(currentUser, user),
"FriendCount": models.CountFriends(user.ID),
"PublicPhotoCount": models.CountPublicPhotos(user.ID),
"InnerCircleMinimumPublicPhotos": config.InnerCircleMinimumPublicPhotos,
"Pager": pager,
"LikeMap": likeMap,
"CommentMap": commentMap,
"ViewStyle": viewStyle,
"ExplicitCount": explicitCount,
"InnerCircleOptOut": user.GetProfileField("inner_circle_optout") == "true",
"InnerCircleInviteView": innerCircleInvite,
// Search filters
"Sort": sort,

View File

@ -55,12 +55,6 @@ func View() http.HandlerFunc {
return
}
// Is this a circle photo?
if photo.Visibility == models.PhotoInnerCircle && !currentUser.IsInnerCircle() {
templates.NotFoundPage(w, r)
return
}
// Is this user private and we're not friends?
var (
areFriends = models.AreFriends(user.ID, currentUser.ID)

View File

@ -20,7 +20,6 @@ type Forum struct {
Explicit bool `gorm:"index"`
Privileged bool
PermitPhotos bool
InnerCircle bool `gorm:"index"`
Private bool `gorm:"index"`
CreatedAt time.Time
UpdatedAt time.Time
@ -97,11 +96,6 @@ func PaginateForums(user *User, categories []string, pager *Pagination) ([]*Foru
wheres = append(wheres, "explicit = false")
}
// Hide circle forums if the user isn't in the circle.
if !user.IsInnerCircle() {
wheres = append(wheres, "inner_circle is not true")
}
// Hide private forums except for admins.
if !user.IsAdmin {
wheres = append(wheres, "private is not true")

View File

@ -47,11 +47,6 @@ func PaginateRecentPosts(user *User, categories []string, allComments bool, page
wheres = append(wheres, "forums.explicit = false")
}
// Circle membership.
if !user.IsInnerCircle() {
wheres = append(wheres, "forums.inner_circle is not true")
}
// Private forums.
if !user.IsAdmin {
wheres = append(wheres, "forums.private is not true")

View File

@ -95,11 +95,6 @@ func SearchForum(user *User, search *Search, filters ForumSearchFilters, pager *
wheres = append(wheres, "forums.explicit = false")
}
// Circle membership.
if !user.IsInnerCircle() {
wheres = append(wheres, "forums.inner_circle is not true")
}
// Private forums.
if !user.IsAdmin {
wheres = append(wheres, "forums.private is not true")

View File

@ -45,7 +45,6 @@ const (
NotificationCertApproved NotificationType = "cert_approved"
NotificationPrivatePhoto NotificationType = "private_photo" // private photo grants
NotificationNewPhoto NotificationType = "new_photo"
NotificationInnerCircle NotificationType = "inner_circle"
NotificationCustom NotificationType = "custom" // custom message pushed
)

View File

@ -12,7 +12,7 @@ type NotificationFilter struct {
NewPhotos bool `json:"photos"`
AlsoCommented bool `json:"replies"` // also_comment and also_posted
PrivatePhoto bool `json:"private"`
Misc bool `json:"misc"` // friendship_approved, cert_approved, cert_rejected, inner_circle, custom
Misc bool `json:"misc"` // friendship_approved, cert_approved, cert_rejected, custom
}
var defaultNotificationFilter = NotificationFilter{

View File

@ -33,10 +33,9 @@ type Photo struct {
type PhotoVisibility string
const (
PhotoPublic PhotoVisibility = "public" // on profile page and/or public gallery
PhotoFriends PhotoVisibility = "friends" // only friends can see it
PhotoPrivate PhotoVisibility = "private" // private
PhotoInnerCircle PhotoVisibility = "circle" // inner circle
PhotoPublic PhotoVisibility = "public" // on profile page and/or public gallery
PhotoFriends PhotoVisibility = "friends" // only friends can see it
PhotoPrivate PhotoVisibility = "private" // private
// Special visibility in case, on User Gallery view, user applies a filter
// for friends-only picture but they are not friends with the user.
@ -51,14 +50,6 @@ var (
PhotoPrivate,
}
// "All" but also for Inner Circle members.
PhotoVisibilityCircle = []PhotoVisibility{
PhotoPublic,
PhotoFriends,
PhotoPrivate,
PhotoInnerCircle,
}
// Site Gallery visibility for when your friends show up in the gallery.
// Or: "Friends + Gallery" photos can appear to your friends in the Site Gallery.
PhotoVisibilityFriends = []string{
@ -273,11 +264,6 @@ func CountPhotosICanSee(user *User, viewer *User) int64 {
PhotoPublic,
}
// Is the viewer in the inner circle?
if viewer.IsInnerCircle() {
visibilities = append(visibilities, PhotoInnerCircle)
}
// Is the viewer friends with the target?
if AreFriends(user.ID, viewer.ID) {
visibilities = append(visibilities, PhotoFriends)
@ -350,7 +336,6 @@ func MapPhotoCounts(users []*User, viewer *User) PhotoCountMap {
// User ID filters for the viewer's context.
myFriendIDs = FriendIDs(viewer.ID)
myPrivateGrantedIDs = PrivateGrantedUserIDs(viewer.ID)
isInnerCircle = viewer.IsInnerCircle()
)
// Define "all photos visibilities"
@ -367,10 +352,6 @@ func MapPhotoCounts(users []*User, viewer *User) PhotoCountMap {
PhotoPrivate,
}
)
if isInnerCircle {
photosPublic = append(photosPublic, PhotoInnerCircle)
photosFriends = append(photosFriends, PhotoInnerCircle)
}
// Flatten the userIDs of all passed in users.
for _, user := range users {
@ -445,7 +426,7 @@ func CountExplicitPhotos(userID uint64, visibility []PhotoVisibility) (int64, er
return count, result.Error
}
// CountPublicPhotos returns the number of public photos on a user's page (to control whether the "invite to circle" prompt can appear).
// CountPublicPhotos returns the number of public photos on a user's page.
func CountPublicPhotos(userID uint64) int64 {
query := DB.Where(
"user_id = ? AND visibility = ?",
@ -541,10 +522,6 @@ func PaginateGalleryPhotos(user *User, conf Gallery, pager *Pagination) ([]*Phot
PhotoPrivate,
}
)
if user.IsInnerCircle() {
photosPublic = append(photosPublic, PhotoInnerCircle)
photosFriends = append(photosFriends, PhotoInnerCircle)
}
// Admins see everything on the site (only an admin user can get an admin view).
adminView = user.HasAdminScope(config.ScopePhotoModerator) && adminView

View File

@ -28,7 +28,6 @@ type User struct {
Birthdate time.Time
Certified bool
Explicit bool `gorm:"index"` // user has opted-in to see explicit content
InnerCircle bool `gorm:"index"` // user is in the inner circle
CreatedAt time.Time `gorm:"index"`
UpdatedAt time.Time `gorm:"index"`
LastLoginAt time.Time `gorm:"index"`
@ -262,7 +261,6 @@ type UserSearch struct {
NearCity *WorldCities
Certified bool
NotCertified bool
InnerCircle bool
ShyAccounts bool
IsBanned bool
IsDisabled bool
@ -436,11 +434,6 @@ func SearchUsers(user *User, search *UserSearch, pager *Pagination) ([]*User, er
wheres = append(wheres, "is_admin = true")
}
if search.InnerCircle {
wheres = append(wheres, "inner_circle = ?")
placeholders = append(placeholders, true)
}
if search.ShyAccounts {
a, b := WhereClauseShyAccounts()
wheres = append(wheres, a)
@ -655,8 +648,6 @@ func (u *User) VisibleAvatarURL(currentUser *User) string {
switch visibility {
case PhotoPrivate:
return "/static/img/shy-private.png"
case PhotoInnerCircle:
return "/static/img/shy-secret.png"
case PhotoFriends:
return "/static/img/shy-friends.png"
}
@ -681,9 +672,6 @@ func (u *User) CanSeeProfilePicture(currentUser *User) (bool, PhotoVisibility) {
} else if visibility == PhotoFriends && !u.UserRelationship.IsFriend {
// Friends only
return false, visibility
} else if visibility == PhotoInnerCircle && currentUser != nil && !currentUser.IsInnerCircle() {
// Inner circle only
return false, visibility
} else if u.ProfilePhoto.CroppedFilename != "" {
// Happy path
return true, visibility

View File

@ -1,68 +0,0 @@
package models
import (
"errors"
"code.nonshy.com/nonshy/website/pkg/config"
"code.nonshy.com/nonshy/website/pkg/log"
)
// Helper functions relating to the inner circle.
// IsInnerCircle returns whether the user is in the inner circle (including if the user is an admin, who is always in the inner circle).
func (u *User) IsInnerCircle() bool {
return u.InnerCircle || u.HasAdminScope(config.ScopeIsInnerCircle)
}
// AddToInnerCircle adds a user to the circle, sending them a notification in the process.
func AddToInnerCircle(u *User) error {
if u.InnerCircle {
return errors.New("already a part of the inner circle")
}
u.InnerCircle = true
if err := u.Save(); err != nil {
return err
}
// Send them a notification.
notif := &Notification{
UserID: u.ID,
AboutUserID: &u.ID,
Type: NotificationInnerCircle,
Link: "/inner-circle",
TableName: "__inner_circle",
TableID: u.ID,
}
if err := CreateNotification(notif); err != nil {
log.Error("AddToInnerCircle: couldn't create notification: %s", err)
}
return nil
}
// RemoveFromInnerCircle kicks a user from the inner circle. Any photo they
// had that was marked circle-only is updated to public.
func RemoveFromInnerCircle(u *User) error {
if !u.InnerCircle {
return errors.New("is not a part of the inner circle")
}
u.InnerCircle = false
if err := u.Save(); err != nil {
return err
}
// Update their circle-only photos to public.
if err := DB.Model(&Photo{}).Where(
"user_id = ? AND visibility = ?",
u.ID, PhotoInnerCircle,
).Update("visibility", PhotoPrivate); err != nil {
log.Error("RemoveFromInnerCircle: couldn't update photo visibility: %s", err.Error)
}
// Revoke any historic notification about the circle.
RemoveNotification("__inner_circle", u.ID)
return nil
}

View File

@ -5,10 +5,9 @@ package models
// The zero-values should fail safely: in case the UserRelationship isn't populated correctly,
// private profile pics show as private by default.
type UserRelationship struct {
Computed bool // check whether the SetUserRelationships function has been run
IsFriend bool // if true, a friends-only profile pic can show
IsPrivateGranted bool // if true, a private profile pic can show
IsInnerCirclePeer bool // if true, the current user is in the inner circle so may see circle-only profile picture
Computed bool // check whether the SetUserRelationships function has been run
IsFriend bool // if true, a friends-only profile pic can show
IsPrivateGranted bool // if true, a private profile pic can show
}
// SetUserRelationships updates a set of User objects to populate their UserRelationships in
@ -18,7 +17,6 @@ func SetUserRelationships(currentUser *User, users []*User) error {
var (
friendIDs = FriendIDs(currentUser.ID)
privateGrants = PrivateGrantedUserIDs(currentUser.ID)
isInnerCircle = currentUser.IsInnerCircle()
)
// Map them for easier lookup.
@ -37,8 +35,6 @@ func SetUserRelationships(currentUser *User, users []*User) error {
// Inject the UserRelationships.
for _, u := range users {
u.UserRelationship.IsInnerCirclePeer = isInnerCircle
if u.ID == currentUser.ID {
// Current user - set both bools to true - you can always see your own profile pic.
u.UserRelationship.IsFriend = true

View File

@ -78,9 +78,6 @@ func New() http.Handler {
mux.Handle("/comments", middleware.LoginRequired(comment.PostComment()))
mux.Handle("GET /comments/subscription", middleware.LoginRequired(comment.Subscription()))
mux.Handle("GET /admin/unimpersonate", middleware.LoginRequired(admin.Unimpersonate()))
mux.Handle("GET /inner-circle", middleware.LoginRequired(account.InnerCircle()))
mux.Handle("/inner-circle/invite", middleware.LoginRequired(account.InviteCircle()))
mux.Handle("/inner-circle/leave", middleware.LoginRequired(account.LeaveCircle()))
mux.Handle("GET /admin/transparency/{username}", middleware.LoginRequired(admin.Transparency()))
// Certification Required. Pages that only full (verified) members can access.
@ -105,7 +102,6 @@ func New() http.Handler {
mux.Handle("/admin/add-user", middleware.AdminRequired(config.ScopeUserCreate, admin.AddUser()))
mux.Handle("/forum/admin", middleware.AdminRequired(config.ScopeForumAdmin, forum.Manage()))
mux.Handle("/forum/admin/edit", middleware.AdminRequired(config.ScopeForumAdmin, forum.AddEdit()))
mux.Handle("/inner-circle/remove", middleware.LoginRequired(account.RemoveCircle()))
mux.Handle("/admin/photo/mark-explicit", middleware.AdminRequired("", admin.MarkPhotoExplicit()))
mux.Handle("GET /admin/changelog", middleware.AdminRequired(config.ScopeChangeLog, admin.ChangeLog()))

View File

@ -52,11 +52,6 @@ func TemplateFuncs(r *http.Request) template.FuncMap {
`<strong style="color: #FF77FF">s</strong>`,
)
},
"PrettyCircle": func() template.HTML {
return template.HTML(
`<span style="color: #0077ff">I</span><span style="color: #1c77ff">n</span><span style="color: #3877ff">n</span><span style="color: #5477ff">e</span><span style="color: #7077ff">r</span><span style="color: #8c77ff"> </span><span style="color: #aa77ff">c</span><span style="color: #b877ff">i</span><span style="color: #c677ff">r</span><span style="color: #d477ff">c</span><span style="color: #e277ff">l</span><span style="color: #f077ff">e</span>`,
)
},
"Pluralize": Pluralize[int],
"Pluralize64": Pluralize[int64],
"PluralizeU64": Pluralize[uint64],

View File

@ -94,10 +94,6 @@ img {
background-image: linear-gradient(141deg, #b329b1 0, #9948c7 71%, #7156d2 100%);
}
.hero.is-inner-circle {
background-image: linear-gradient(141deg, #294eb3 0, #9948c7 71%, #d256d2 100%)
}
/* Mobile: notification badge near the hamburger menu */
.nonshy-mobile-notification {
position: absolute;

View File

@ -194,10 +194,6 @@
your naked journey.
</p>
<p>
The <strong>most</strong> {{PrettyTitle}} of our members may be rewarded with access to exclusive features.
</p>
<p>
Check out the full <a href="/features"><strong>Features List</strong></a> for more details.
</p>

View File

@ -218,14 +218,6 @@
Edit Profile &amp; Settings
</a>
</li>
{{if .CurrentUser.IsInnerCircle}}
<li>
<a href="/inner-circle">
<span class="icon"><img src="/static/img/circle-16.png"></span>
{{PrettyCircle}}
</a>
</li>
{{end}}
<li>
<a href="/photo/certification">
<span class="icon"><i class="fa fa-certificate"></i></span>
@ -574,8 +566,6 @@
<span class="icon">
{{if and $Body.Photo (eq $Body.Photo.Visibility "private")}}
<i class="fa fa-eye {{if $Body.Photo.Explicit}}has-text-danger{{else}}has-text-private{{end}}"></i>
{{else if and $Body.Photo (eq $Body.Photo.Visibility "circle")}}
<img src="/static/img/circle-16.png">
{{else}}
<i class="fa fa-image {{if $Body.Photo.Explicit}}has-text-danger{{else}}has-text-link{{end}}"></i>
{{end}}
@ -604,15 +594,6 @@
<span>
Your <strong>certification photo</strong> was rejected!
</span>
{{else if eq .Type "inner_circle"}}
<span class="icon"><img src="/static/img/circle-16.png"></span>
<span>
You have been added to the {{PrettyCircle}} of nonshy.
</span>
<div class="block content mt-2">
<a href="/inner-circle">Click to learn more</a> about the inner circle.
</div>
{{else}}
{{.AboutUser.Username}} {{.Type}} {{.TableName}} {{.TableID}}
{{end}}

View File

@ -116,9 +116,6 @@
{{else}}
{{.NameOrUsername}}
{{end}}
{{if and $Root.CurrentUser.IsInnerCircle .InnerCircle}}
<img src="/static/img/circle-16.png">
{{end}}
</a>
{{if eq .Visibility "private"}}
<sup class="fa fa-mask is-size-7" title="Private Profile"></sup>

View File

@ -1,220 +0,0 @@
{{define "title"}}The inner circle{{end}}
{{define "content"}}
<div class="block">
<section class="hero is-inner-circle is-bold">
<div class="hero-body">
<div class="container">
<h1 class="title has-text-light">
<img src="/static/img/circle-24.png" class="mr-1">
The inner circle
</h1>
</div>
</div>
</section>
</div>
<div class="block p-4">
<div class="columns">
<div class="column is-one-quarter">
<!-- Quick Links -->
<div class="card block">
<header class="card-header has-background-link">
<p class="card-header-title has-text-light">
<span class="icon mr-1"><img src="/static/img/circle-16.png"></span>
<span>Quick Links</span>
</p>
</header>
<div class="card-content">
<p class="block">
Some quick links to {{PrettyCircle}}-only views mentioned
in the description on this page:
</p>
<aside class="menu">
<ul class="menu-list block">
<li>
<a href="/photo/gallery?visibility=circle">
<i class="fa fa-image mr-1"></i>
Gallery (circle-only photos)
</a>
</li>
<li>
<a href="/members?certified=circle">
<i class="fa fa-people-group mr-1"></i>
Inner circle members
</a>
</li>
<li>
<a href="/f/circle">
<i class="fa fa-comments mr-1"></i>
Forum (Circle Chat)
</a>
</li>
{{if .CurrentUser.Explicit}}
<li>
<a href="/f/circle-jerk">
<i class="fa fa-comments mr-1"></i>
Forum (Circle Jerk) <small class="fa fa-fire has-text-danger" title="Explicit"></small>
</a>
</li>
{{end}}
</ul>
</aside>
</div>
</div>
</div>
<div class="column content">
<p>
Congratulations! You have been added to the <strong>{{PrettyCircle}}</strong> because you
exemplify what it truly means to be a {{PrettyTitle}} nudist.
</p>
<h2>What is the inner circle?</h2>
<p>
The inner circle is for {{PrettyTitle}} members who <em>actually</em> share a lot of nude pictures,
<strong>with face</strong>, of themselves on their profile page. It is "the party inside the party"
designed only for members who truly embrace the spirit of the {{PrettyTitle}} website by boldly
sharing nude pics with face for other nonshy nudists to see.
</p>
<p>
As a member of the inner circle, you can see who else on the website is a part of the circle. Look
for the <img src="/static/img/circle-16.png" alt="Inner circle"> icon on profile pages, the
member directory, forums or the chat room. You may find that some of your {{PrettyTitle}} friends
were already in the circle! People who are <em>not</em> in the circle won't see these indicators
anywhere on the website.
</p>
<h2>What can I do for being in the inner circle?</h2>
<p>
As a part of the inner circle, you have access to the following new features:
</p>
<ul>
<li>
When
<a href="/photo/upload"><strong><i class="fa fa-upload mr-1"></i> Uploading a photo</strong></a> you
have a new Visibility option for "<strong>{{PrettyCircle}}</strong> <img src="/static/img/circle-16.png">"
so that only members of the inner circle can see those pictures.
</li>
<li>
On the
<a href="/photo/gallery"><strong><i class="fa fa-image mr-1"></i> Site Gallery</strong></a>
you can filter for <a href="/photo/gallery?visibility=circle">Inner Circle-only photos</a> shared
by other members of the circle.
</li>
<li>
On the
<a href="/members"><strong><i class="fa fa-people-group mr-1"></i> Member Directory</strong></a>
you can see who else is <a href="/members?certified=circle">in the inner circle.</a>
</li>
<li>
On the
<a href="/f/circle"><strong><i class="fa fa-comments mr-1"></i> Forums</strong></a>
you can access exclusive inner circle-only boards.
</li>
<li>
On the
<a href="/chat"><strong><i class="fa fa-message"></i> Chat Room</strong></a> you have access
to the exclusive Circle Chat channel, and you can see who else in the chat room is a part of
the inner circle. People outside the circle can't see who the inner circle members are!
</li>
<li>
On your <a href="/u/{{.CurrentUser.Username}}">profile page</a> you get an "Inner circle" badge near your
Certified status. This badge is <strong>only</strong> visible to members of the inner circle.
</li>
<li>
You may <strong>invite</strong> other members to join the inner circle.
</li>
</ul>
<h2 id="qualify">How does one qualify to be part of the inner circle?</h2>
<p>
The minimum qualifications for somebody to be in the inner circle include:
</p>
<p>
On <strong>public</strong> visibility, they have at least <strong>5 (five)</strong> photos
that show their <strong>face</strong> <em>and</em> <strong>nude body</strong> together.
</p>
<p>
If you see an inner circle member who had dropped below that threshold later (e.g., they moved
all of their pictures to private or they deleted them all from their page), you may
<strong>request removal</strong> from the circle when viewing their photo gallery page. Help
us keep the integrity of the inner circle by requesting removals when you see somebody who
should no longer qualify to be in the circle. <strong>Removal requests are anonymous!</strong>
</p>
<h2>How do I invite others to join the inner circle?</h2>
<p>
When you are viewing a <strong>member's photo gallery</strong> page, look for the prompt at the top of the
page.
</p>
<p>
If a member posts several <small>(e.g. {{.InnerCircleMinimumPublicPhotos}}+)</small> nude pics <strong>including face</strong> you should invite them to join
the inner circle. All members of the circle are allowed to invite new members to join. We trust your
judgment: please only invite like-minded nudists who <em>actually</em> share nudes <em>with face</em>
to join the inner circle.
</p>
<p>
<strong>Notice:</strong> the prompt to invite them to the circle will not be visible if they do not have
<em>at least</em> <strong>{{.InnerCircleMinimumPublicPhotos}}</strong> public photos on their page at all.
Those public photos aren't necessarily nudes, but hiding the prompt unless they have {{.InnerCircleMinimumPublicPhotos}}
photos can help reduce the risk of inviting somebody on accident.
</p>
<h2>Please keep the existence of the inner circle a secret</h2>
<p>
The inner circle is not publicly advertised on the site. This is to help ensure that "bad actors" won't
try and game the system (e.g., by begging somebody to invite them into the circle, or uploading a bare
minimum of nude pics for somebody to invite them only for them to delete their nudes and try and stay
in the inner circle). Plus, it adds an air of exclusivity to keep the existence of the circle on the
down low.
</p>
<h2>Still continue to share at least <em>some</em> nudes on "public"</h2>
<p>
With the new Photo visibility option for "inner circle only" you may tag your best nudes for only members
of the inner circle to see. However, you should still continue to share at least <em>some</em> photos on
"Public" as you were doing previously. This is for a couple of reasons:
</p>
<ul>
<li>
Members who are <em>not</em> in the circle won't see your circle-only photos. If for example you
placed <em>all</em> of your nudes on circle-only you would appear to look the same as someone who
uploaded no nudes at all.
</li>
<li>
The "<a href="/faq#shy-faqs">Shy Account</a>" system of the main website still applies: if you have
not one public photo on your page you may be marked as a Shy Account and be limited from some site
features such as the Chat Room.
</li>
</ul>
<h2>Leave the inner circle</h2>
<p>
If you would like to opt-out and leave the inner circle, you may do so by clicking
<a href="/inner-circle/leave">here</a>.
</p>
</div>
</div>
</div>
{{end}}

View File

@ -1,110 +0,0 @@
{{define "title"}}Invite to the inner circle{{end}}
{{define "content"}}
<div class="container">
<section class="hero is-inner-circle is-bold">
<div class="hero-body">
<div class="container">
<h1 class="title has-text-light">
<img src="/static/img/circle-24.png">
Invite to the inner circle
</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"><img src="/static/img/circle-16.png"></span>
Invite to the inner circle
</p>
</header>
<div class="card-content">
<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>
<div class="content">
<p>
Do you want to invite <strong>{{.User.Username}}</strong> to join the
{{PrettyCircle}}? Please review the following notes:
</p>
<ul>
<li>
The inner circle is designed for {{PrettyTitle}} members who <em>actually</em> post
several nude photos <strong>with face</strong> on their profile page.
If {{.User.Username}} only has clothed selfies on their page, or keeps
their face hidden or cropped out of their nudes, please <strong>do not</strong>
invite them to join the inner circle.
</li>
<li>
We especially want people who post such pics <strong>on public</strong> visibility.
If {{.User.Username}} has <strong>several</strong> public nude pics with face (e.g.
at least 5 or so pictures), invite them into the circle!
</li>
<li>
All members of the inner circle are allowed to invite others to join the
circle. We trust your judgment -- help ensure that the inner circle is only
made up of truly non-shy nudists who show their whole body on their profile
page.
</li>
</ul>
<p>
Note: while we use the word "invite" they will actually be added to the inner circle
immediately and receive a notification that they had been added. They won't know that
it was <em>you</em> who invited them to join the circle.
</p>
<p>
To proceed, please <strong>double check that they have 5 Public photos</strong>
that include their face and nude body.
</p>
</div>
<div class="field has-text-centered">
<a href="/u/{{.User.Username}}/photos?visibility=public&intent=inner_circle"
class="button is-success">
Continue to their Public gallery...
</a>
<a href="/u/{{.User.Username}}" class="button is-warning ml-1">
Cancel
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
window.addEventListener("DOMContentLoaded", (event) => {
let $file = document.querySelector("#file"),
$fileName = document.querySelector("#fileName");
$file.addEventListener("change", function() {
let file = this.files[0];
$fileName.innerHTML = file.name;
});
});
</script>
{{end}}

View File

@ -1,87 +0,0 @@
{{define "title"}}Leave the Inner Circle{{end}}
{{define "content"}}
<div class="container">
<section class="hero is-inner-circle is-bold">
<div class="hero-body">
<div class="container">
<h1 class="title has-text-light">
<img src="/static/img/circle-24.png" class="mr-1">
Leave the Inner Circle
</h1>
</div>
</div>
</section>
<div class="block p-4">
<div class="level">
<div class="level-item">
<div class="card" style="max-width: 512px">
<header class="card-header has-background-link">
<p class="card-header-title has-text-light">
<img src="/static/img/circle-16.png" class="mr-2">
Leave the Inner Circle
</p>
</header>
<div class="card-content content">
<form id="leave_circle" method="POST" action="/inner-circle/leave">
{{InputCSRF}}
<input type="hidden" name="confirm" value="true">
<p>
Are you sure that you want to <strong>leave the {{PrettyCircle}}?</strong>
</p>
<p>
This action can not be un-done easily; after leaving the inner circle, the only
way back in is for somebody in the circle to invite you again.
</p>
<p>
In case you <strong>do not want</strong> to be invited into the circle again,
you can check the box below.
</p>
<label class="checkbox mb-4 has-text-warning">
<input type="checkbox"
name="dont_reinvite"
id="dont_reinvite"
value="true">
Don't let anyone invite me to the {{PrettyCircle}} again.
</label>
<div class="block has-text-center">
<button type="submit" class="button is-danger">Leave the Inner Circle</button>
<button type="button" class="button is-success" onclick="history.back()">Cancel</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{{end}}
{{define "scripts"}}
<script>
document.addEventListener("DOMContentLoaded", () => {
let $form = document.querySelector("#leave_circle"),
$cb = document.querySelector("#dont_reinvite");
$form.addEventListener("submit", (e) => {
if ($cb.checked) {
if (!window.confirm(
"Are you sure you want to leave the circle, permanently?\n\n" +
"You have requested that nobody can re-invite you into the circle again in the future. " +
"This action CAN NOT be undone!\n\n" +
"If you want to leave open the possibility to be re-invited in the future, click Cancel " +
"and un-check that box."
)) {
e.preventDefault();
return false;
}
}
});
})
</script>
{{end}}

View File

@ -120,17 +120,6 @@
</div>
{{end}}
{{if and .CurrentUser.IsInnerCircle .User.IsInnerCircle}}
<div class="pt-1">
<div class="icon-text has-text-danger">
<span class="icon">
<img src="/static/img/circle-16.png">
</span>
<strong>{{PrettyCircle}}</strong>
</div>
</div>
{{end}}
{{if .User.IsAdmin}}
<div class="pt-1">
<div class="icon-text has-text-danger">

View File

@ -1,116 +0,0 @@
{{define "title"}}Remove from the inner circle{{end}}
{{define "content"}}
<div class="container">
<section class="hero is-inner-circle is-bold">
<div class="hero-body">
<div class="container">
<h1 class="title has-text-light">
<img src="/static/img/circle-24.png">
Remove from the inner circle
</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-warning">
<p class="card-header-title has-text-black">
<span class="icon"><img src="/static/img/circle-16.png"></span>
Remove from the inner circle
</p>
</header>
<div class="card-content">
<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="/inner-circle/remove" method="POST">
{{InputCSRF}}
<input type="hidden" name="to" value="{{.User.Username}}">
{{if .CurrentUser.HasAdminScope "social.moderator.inner-circle"}}
<div class="content">
<p>
Do you want to <strong class="has-text-danger">remove</strong> {{.User.Username}} from
the {{PrettyCircle}}? Doing so will:
</p>
<ul>
<li>
Unset their inner circle flag, removing them from all inner circle features.
</li>
<li>
Set any photo they had for "inner circle only" to be "public" instead.
</li>
<li>
Clean up any notification they once received about being invited to the inner circle.
</li>
</ul>
</div>
{{else}}
<div class="content">
<p>
Do you want to <strong class="has-text-danger">request removal</strong> of {{.User.Username}}
from the {{PrettyCircle}}?
</p>
<p>
If {{.User.Username}} should no longer <a href="/inner-circle#qualify">qualify</a> to be a
part of the inner circle, you may flag them to be removed from the circle. For example: to
be invited into the circle someone should need to have <strong>at least 5 (five) pictures</strong>
that are on <strong>public</strong> visibility and which show their <strong>face and nude body</strong>
together. But if they have taken down their pictures and should no longer qualify to remain in
the {{PrettyCircle}}, you may request them to be removed and an admin will check them out.
</p>
<p>
Note: removal requests are <strong>completely anonymous</strong>: we trust your judgment when
inviting others to join the circle so we also trust you to help maintain the integrity of the
circle when flagging people for removal.
</p>
</div>
{{end}}
<div class="field has-text-centered">
<button type="submit" name="intent" value="confirm" class="button is-danger">
{{if .CurrentUser.IsAdmin}}Remove{{else}}Request removal{{end}} from the inner circle
</button>
<button type="submit" name="intent" value="cancel" class="button is-warning ml-1">
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
window.addEventListener("DOMContentLoaded", (event) => {
let $file = document.querySelector("#file"),
$fileName = document.querySelector("#fileName");
$file.addEventListener("change", function() {
let file = this.files[0];
$fileName.innerHTML = file.name;
});
});
</script>
{{end}}

View File

@ -142,9 +142,6 @@
</optgroup>
<optgroup label="Profile Status">
<option value="shy"{{if eq $Root.Certified "shy"}} selected{{end}}>Shy Accounts</option>
{{if .CurrentUser.IsInnerCircle}}
<option value="circle"{{if eq $Root.Certified "circle"}} selected{{end}}>Inner circle only</option>
{{end}}
<option value="admin"{{if eq $Root.Certified "admin"}} selected{{end}}>Website administrators</option>
</optgroup>
{{if .CurrentUser.HasAdminScope "admin.user.ban"}}
@ -377,9 +374,6 @@
{{else}}
{{.NameOrUsername}}
{{end}}
{{if and $Root.CurrentUser.IsInnerCircle .InnerCircle}}
<img src="/static/img/circle-16.png">
{{end}}
</a>
{{if eq .Visibility "private"}}
<sup class="fa fa-mask is-size-7" title="Private Profile"></sup>

View File

@ -201,12 +201,6 @@
<span class="icon"><i class="fa fa-pen-to-square"></i></span>
<span>My User Notes</span>
</a>
{{if .CurrentUser.IsInnerCircle}}
<a class="navbar-item" href="/inner-circle">
<span class="icon"><img src="/static/img/circle-16.png"></span>
<span>Inner circle</span>
</a>
{{end}}
<a class="navbar-item" href="/settings">
<span class="icon"><i class="fa fa-gear"></i></span>
<span>Settings</span>

View File

@ -419,22 +419,6 @@
<a href="/photo/private">granted access</a> to see your
private photos.
</li>
{{if and .LoggedIn .CurrentUser.IsInnerCircle}}
<li>
<strong>{{PrettyCircle}}</strong>
<img src="/static/img/circle-24.png" width="16" height="16">:
your profile pic displays as a gradient
<img src="/static/img/shy-secret.png" width="16" height="16">
placeholder image for members outside the inner circle.
<ul>
<li>
<strong>Note:</strong> only {{PrettyCircle}} members see this info on the FAQ page.
Members outside the circle will see the gradient avatar, but be told that it means
"Private."
</li>
</ul>
</li>
{{end}}
</ul>
<p>

View File

@ -128,19 +128,6 @@
</p>
{{end}}
{{if .CurrentUser.IsAdmin}}
<label class="checkbox mt-3">
<input type="checkbox"
name="inner_circle"
value="true"
{{if and .EditForum .EditForum.InnerCircle}}checked{{end}}>
Inner circle <img src="/static/img/circle-16.png" class="ml-1">
</label>
<p class="help">
This forum is only available to inner circle members.
</p>
{{end}}
{{if .CurrentUser.IsAdmin}}
<label class="checkbox mt-3">
<input type="checkbox"

View File

@ -59,13 +59,6 @@
</div>
<div>
{{if .InnerCircle}}
<div class="tag is-info is-light">
<span class="icon"><img src="/static/img/circle-10.png" width="9" height="9"></span>
InnerCircle
</div>
{{end}}
{{if .Explicit}}
<div class="tag is-danger is-light">
<span class="icon"><i class="fa fa-fire"></i></span>

View File

@ -69,7 +69,6 @@
<div class="column is-3 pt-0 pb-1">
<h2 class="is-size-4">
{{if .InnerCircle}}<img src="/static/img/circle-24.png" width="20" height="20" class="mr-1">{{end}}
<strong><a href="/f/{{.Fragment}}">{{.Title}}</a></strong>
</h2>

View File

@ -27,8 +27,6 @@
<img class="is-rounded" src="/static/img/shy-private.png">
{{else if and (eq $User.ProfilePhoto.Visibility "friends") (not $User.UserRelationship.IsFriend)}}
<img class="is-rounded" src="/static/img/shy-friends.png">
{{else if and (eq $User.ProfilePhoto.Visibility "circle") (not $User.UserRelationship.IsInnerCirclePeer)}}
<img class="is-rounded" src="/static/img/shy-secret.png">
{{else}}
<img class="is-rounded" src="{{PhotoURL $User.ProfilePhoto.CroppedFilename}}">
{{end}}

View File

@ -9,8 +9,6 @@
<img src="/static/img/shy-private.png" class="is-rounded">
{{else if and (eq .ProfilePhoto.Visibility "friends") (not .UserRelationship.IsFriend)}}
<img src="/static/img/shy-friends.png" class="is-rounded">
{{else if and (eq .ProfilePhoto.Visibility "circle") (not .UserRelationship.IsInnerCirclePeer)}}
<img src="/static/img/shy-secret.png">
{{else}}
<img src="{{PhotoURL .ProfilePhoto.CroppedFilename}}" class="is-rounded">
{{end}}
@ -30,8 +28,6 @@
<img src="/static/img/shy-private.png">
{{else if and (eq .ProfilePhoto.Visibility "friends") (not .UserRelationship.IsFriend)}}
<img src="/static/img/shy-friends.png">
{{else if and (eq .ProfilePhoto.Visibility "circle") (not .UserRelationship.IsInnerCirclePeer)}}
<img src="/static/img/shy-secret.png">
{{else}}
<img src="{{PhotoURL .ProfilePhoto.CroppedFilename}}">
{{end}}
@ -51,8 +47,6 @@
<img src="/static/img/shy-private.png">
{{else if and (eq .ProfilePhoto.Visibility "friends") (not .UserRelationship.IsFriend)}}
<img src="/static/img/shy-friends.png">
{{else if and (eq .ProfilePhoto.Visibility "circle") (not .UserRelationship.IsInnerCirclePeer)}}
<img src="/static/img/shy-secret.png">
{{else}}
<img src="{{PhotoURL .ProfilePhoto.CroppedFilename}}">
{{end}}
@ -72,8 +66,6 @@
<img src="/static/img/shy-private.png">
{{else if and (eq .ProfilePhoto.Visibility "friends") (not .UserRelationship.IsFriend)}}
<img src="/static/img/shy-friends.png">
{{else if and (eq .ProfilePhoto.Visibility "circle") (not .UserRelationship.IsInnerCirclePeer)}}
<img src="/static/img/shy-secret.png">
{{else}}
<img src="{{PhotoURL .ProfilePhoto.CroppedFilename}}">
{{end}}
@ -93,8 +85,6 @@
<img class="is-rounded" src="/static/img/shy-private.png">
{{else if and (eq .ProfilePhoto.Visibility "friends") (not .UserRelationship.IsFriend)}}
<img class="is-rounded" src="/static/img/shy-friends.png">
{{else if and (eq .ProfilePhoto.Visibility "circle") (not .UserRelationship.IsInnerCirclePeer)}}
<img src="/static/img/shy-secret.png">
{{else}}
<img class="is-rounded" src="{{PhotoURL .ProfilePhoto.CroppedFilename}}">
{{end}}

View File

@ -40,30 +40,6 @@
<div class="block">
Are you sure you want to delete this photo?
</div>
{{if .InnerCircleWarning}}
<div class="notification has-background-danger-light">
<strong class="has-text-danger">
<i class="fa fa-exclamation-triangle mr-1"></i>
Important
{{PrettyCircle}}
<img src="/static/img/circle-10.png" alt="Inner circle icon">
Information
</strong>
<div class="mt-2">
When you delete this photo, your count of <strong>public</strong> photos will drop below 5 (five)
and you will be immediately <strong>removed from the inner circle.</strong>
</div>
<div class="mt-2">
If you wish to avoid this, please upload a new <strong>public</strong> photo
before deleting this one. {{PrettyCircle}} members are expected to maintain at
least 5 nudes with face on their public gallery &mdash; it's what qualified you
to be invited to the inner circle in the first place!
<a href="/inner-circle" target="_blank">Learn more <i class="fa fa-external-link"></i></a>
</div>
</div>
{{end}}
<div class="block has-text-center">
<button type="submit" class="button is-danger">Delete Photo</button>
<button type="button" class="button" onclick="history.back()">Cancel</button>

View File

@ -47,15 +47,6 @@
Friends
</span>
</span>
{{else if eq .Visibility "circle"}}
<span class="tag is-info is-light">
<span class="icon">
<img src="/static/img/circle-10.png" width="9" height="9">
</span>
<span>
{{PrettyCircle}}
</span>
</span>
{{else}}
<span class="tag is-private is-light">
<span class="icon"><i class="fa fa-lock"></i></span>
@ -217,9 +208,6 @@
{{if eq .ProfilePictureHiddenVisibility "friends"}}
<img src="/static/img/shy-friends.png" width="16" height="16">
<strong class="has-text-warning">Friends only</strong>
{{else if eq .ProfilePictureHiddenVisibility "circle"}}
<img src="/static/img/shy-secret.png" width="16" height="16">
<strong class="has-text-private">Private</strong>
{{else}}
<img src="/static/img/shy-private.png" width="16" height="16">
<strong class="has-text-private">Private</strong>
@ -229,31 +217,6 @@
</div>
{{end}}
<!-- Inner circle invite notice -->
{{if .InnerCircleInviteView}}
<div class="notification is-info is-light content">
<p>
<img src="/static/img/circle-24.png" width="16" height="16">
<strong class="ml-2">{{PrettyCircle}} invitation</strong>
</p>
<p>
You are now viewing @{{.User.Username}}'s <strong>Public gallery photos</strong> to verify
they should be invited to the {{PrettyCircle}}.
</p>
<p>
Please take a look and see if they have at least <strong>five (5) photos</strong> that clearly show
their <strong>face</strong> as well as their full nude body together.
</p>
<p>
If they qualify to join the {{PrettyCircle}}, the button to invite them will be at
<strong>the bottom of this page</strong>.
</p>
</div>
{{end}}
<div class="block">
<div class="level mb-2">
<div class="level-left">
@ -426,9 +389,6 @@
<select id="visibility" name="visibility">
<option value="">All photos</option>
<option value="public"{{if eq .FilterVisibility "public"}} selected{{end}}>Public only</option>
{{if .CurrentUser.IsInnerCircle}}
<option value="circle"{{if eq .FilterVisibility "circle"}} selected{{end}}>Inner circle only</option>
{{end}}
<!-- Friends & Private: always show on Site Gallery, show if available on User Gallery -->
{{if or .IsSiteGallery .AreFriends .IsOwnPhotos}}
@ -519,26 +479,6 @@
</div>
{{end}}
<!-- Inner circle invitation -->
{{if and (not .IsSiteGallery) (not .InnerCircleOptOut)}}
{{if and (.CurrentUser.IsInnerCircle) (not .User.InnerCircle) (ne .CurrentUser.Username .User.Username) (ge .PublicPhotoCount .InnerCircleMinimumPublicPhotos)}}
<div class="block mt-0">
<span class="icon"><img src="/static/img/circle-16.png"></span>
Does <strong>{{.User.Username}}</strong> show a lot of nudity? Consider
<a href="/inner-circle/invite?to={{.User.Username}}">inviting them to join the {{PrettyCircle}}</a>.
</div>
{{else if (and .CurrentUser.IsInnerCircle .User.IsInnerCircle)}}
<div class="block mt-0">
<span class="icon"><img src="/static/img/circle-16.png"></span>
<strong>{{.User.Username}}</strong> is a part of the {{PrettyCircle}}.
Do they <a href="/inner-circle#qualify">still qualify</a> to be in the circle?
<a href="/inner-circle/remove?to={{.User.Username}}" class="has-text-danger">
<ins>Request removal</ins>
</a> if they do not.
</div>
{{end}}
{{end}}
{{SimplePager .Pager}}
<!-- "Full" view style? (blog style) -->
@ -567,8 +507,6 @@
<i class="fa fa-user-group has-text-warning" title="Friends"></i>
{{else if eq .Visibility "private"}}
<i class="fa fa-lock has-text-private-light" title="Private"></i>
{{else if eq .Visibility "circle"}}
<img src="/static/img/circle-16.png">
{{else}}
<i class="fa fa-eye has-text-link-light" title="Public"></i>
{{end}}
@ -702,8 +640,6 @@
<i class="fa fa-user-group has-text-warning" title="Friends"></i>
{{else if eq .Visibility "private"}}
<i class="fa fa-lock has-text-private-light" title="Private"></i>
{{else if eq .Visibility "circle"}}
<img src="/static/img/circle-16.png">
{{else}}
<i class="fa fa-eye has-text-link-light" title="Public"></i>
{{end}}
@ -814,34 +750,6 @@
</div>
{{end}}
<!-- Inner circle invite notice -->
{{if .InnerCircleInviteView}}
<div class="notification is-info is-light content">
<p>
<img src="/static/img/circle-24.png" width="16" height="16">
<strong class="ml-2">{{PrettyCircle}} invitation</strong>
</p>
<p>
If <strong>@{{.User.Username}}</strong> shows at least 5 nude photos <strong>with face</strong>
on their public gallery, click on the button below to add them to the {{PrettyCircle}}.
</p>
<form action="/inner-circle/invite" method="POST">
{{InputCSRF}}
<input type="hidden" name="to" value="{{.User.Username}}">
<button type="submit" name="intent" value="confirm" class="button is-link">
Add to the inner circle
</button>
<button type="submit" name="intent" value="cancel" class="button is-warning ml-1">
Cancel
</button>
</form>
</div>
{{end}}
</div>
</div>

View File

@ -298,27 +298,6 @@
Gallery if that option is enabled, below.
</p>
</div>
{{if .CurrentUser.IsInnerCircle}}
<div>
<label class="radio">
<input type="radio"
name="visibility"
value="circle"
{{if eq .EditPhoto.Visibility "circle"}}checked{{end}}>
<strong class="has-text-link ml-1">
<span>{{PrettyCircle}}</span>
<span class="icon">
<img src="/static/img/circle-16.png">
</span>
</strong>
</label>
<p class="help">
Only members of the <a href="/inner-circle">inner circle</a> will see this photo. This is
like the "Public" visibility except only people in the inner circle will see it on your
profile page or on the Site Gallery (if that option is enabled, below).
</p>
</div>
{{end}}
<div>
<label class="radio">
<input type="radio"