The inner circle
This commit is contained in:
parent
17d9760b61
commit
9788ea6a33
|
@ -90,6 +90,7 @@ var (
|
|||
// Default forum categories for forum landing page.
|
||||
ForumCategories = []string{
|
||||
"Rules and Announcements",
|
||||
"The Inner Circle",
|
||||
"Nudists",
|
||||
"Exhibitionists",
|
||||
"Photo Boards",
|
||||
|
|
137
pkg/controller/account/inner_circle.go
Normal file
137
pkg/controller/account/inner_circle.go
Normal file
|
@ -0,0 +1,137 @@
|
|||
package account
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
if err := tmpl.Execute(w, r, nil); 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)
|
||||
}
|
||||
|
||||
session.Flash(w, r, "%s has been added to the inner circle!", user.Username)
|
||||
templates.Redirect(w, "/photo/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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Add them!
|
||||
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)
|
||||
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
|
||||
}
|
||||
})
|
||||
}
|
|
@ -77,7 +77,8 @@ func Search() http.HandlerFunc {
|
|||
Gender: gender,
|
||||
Orientation: orientation,
|
||||
MaritalStatus: maritalStatus,
|
||||
Certified: isCertified == "true",
|
||||
Certified: isCertified != "false",
|
||||
InnerCircle: isCertified == "circle",
|
||||
AgeMin: ageMin,
|
||||
AgeMax: ageMax,
|
||||
}, pager)
|
||||
|
|
|
@ -63,6 +63,7 @@ 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"
|
||||
)
|
||||
|
||||
// Sanity check admin-only settings.
|
||||
|
@ -79,6 +80,7 @@ func AddEdit() http.HandlerFunc {
|
|||
forum.Explicit = isExplicit
|
||||
forum.Privileged = isPrivileged
|
||||
forum.PermitPhotos = isPermitPhotos
|
||||
forum.InnerCircle = isInnerCircle
|
||||
|
||||
// Save it.
|
||||
if err := forum.Save(); err == nil {
|
||||
|
@ -111,6 +113,7 @@ func AddEdit() http.HandlerFunc {
|
|||
Explicit: isExplicit,
|
||||
Privileged: isPrivileged,
|
||||
PermitPhotos: isPermitPhotos,
|
||||
InnerCircle: isInnerCircle,
|
||||
}
|
||||
|
||||
if err := models.CreateForum(forum); err == nil {
|
||||
|
|
|
@ -41,6 +41,12 @@ func Forum() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
// Is it an inner circle forum?
|
||||
if forum.InnerCircle && !currentUser.IsInnerCircle() {
|
||||
templates.NotFoundPage(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the pinned threads.
|
||||
pinned, err := models.PinnedThreads(forum)
|
||||
if err != nil {
|
||||
|
|
|
@ -54,6 +54,12 @@ func Thread() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
// Is it an inner circle forum?
|
||||
if forum.InnerCircle && !currentUser.IsInnerCircle() {
|
||||
templates.NotFoundPage(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Ping the view count on this thread.
|
||||
if err := thread.View(currentUser.ID); err != nil {
|
||||
log.Error("Couldn't ping view count on thread %d: %s", thread.ID, err)
|
||||
|
|
|
@ -156,6 +156,9 @@ func notifyFriendsNewPhoto(photo *models.Photo, currentUser *models.User) {
|
|||
// Private grantees
|
||||
friendIDs = models.PrivateGranteeUserIDs(currentUser.ID)
|
||||
log.Info("Notify %d private grantees about the new photo by %s", len(friendIDs), currentUser.Username)
|
||||
} else if photo.Visibility == models.PhotoInnerCircle {
|
||||
friendIDs = models.FriendIDsInCircle(currentUser.ID)
|
||||
log.Info("Notify %d circle friends about the new photo by %s", len(friendIDs), currentUser.Username)
|
||||
} else {
|
||||
// Get all our friend IDs. If this photo is Explicit, only select
|
||||
// the friends who've opted-in for Explicit photo visibility.
|
||||
|
|
|
@ -101,6 +101,11 @@ func UserPhotos() http.HandlerFunc {
|
|||
visibility = append(visibility, models.PhotoFriends)
|
||||
}
|
||||
|
||||
// Inner circle photos.
|
||||
if currentUser.IsInnerCircle() {
|
||||
visibility = append(visibility, models.PhotoInnerCircle)
|
||||
}
|
||||
|
||||
// Explicit photo filter?
|
||||
explicit := currentUser.Explicit
|
||||
if isOwnPhotos {
|
||||
|
|
|
@ -20,6 +20,7 @@ type Forum struct {
|
|||
Explicit bool `gorm:"index"`
|
||||
Privileged bool
|
||||
PermitPhotos bool
|
||||
InnerCircle bool
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
@ -95,6 +96,11 @@ 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")
|
||||
}
|
||||
|
||||
// Filters?
|
||||
if len(wheres) > 0 {
|
||||
query = query.Where(
|
||||
|
@ -172,5 +178,14 @@ func CategorizeForums(fs []*Forum, categories []string) []*CategorizedForum {
|
|||
result[idx].Forums = append(result[idx].Forums, forum)
|
||||
}
|
||||
|
||||
return result
|
||||
// Remove any blank categories with no boards.
|
||||
var filtered = []*CategorizedForum{}
|
||||
for _, forum := range result {
|
||||
if len(forum.Forums) == 0 {
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, forum)
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
|
|
@ -131,6 +131,30 @@ func FriendIDsAreExplicit(userId uint64) []uint64 {
|
|||
return userIDs
|
||||
}
|
||||
|
||||
// FriendIDsInCircle returns friend IDs who are part of the inner circle.
|
||||
func FriendIDsInCircle(userId uint64) []uint64 {
|
||||
var (
|
||||
userIDs = []uint64{}
|
||||
)
|
||||
|
||||
err := DB.Table(
|
||||
"friends",
|
||||
).Joins(
|
||||
"JOIN users ON (users.id = friends.target_user_id)",
|
||||
).Select(
|
||||
"friends.target_user_id AS friend_id",
|
||||
).Where(
|
||||
"friends.source_user_id = ? AND friends.approved = ? AND (users.inner_circle = ? OR users.is_admin = ?)",
|
||||
userId, true, true, true,
|
||||
).Scan(&userIDs)
|
||||
|
||||
if err.Error != nil {
|
||||
log.Error("SQL error collecting circle FriendIDs for %d: %s", userId, err)
|
||||
}
|
||||
|
||||
return userIDs
|
||||
}
|
||||
|
||||
// CountFriendRequests gets a count of pending requests for the user.
|
||||
func CountFriendRequests(userID uint64) (int64, error) {
|
||||
var count int64
|
||||
|
|
|
@ -40,6 +40,7 @@ const (
|
|||
NotificationCertApproved = "cert_approved"
|
||||
NotificationPrivatePhoto = "private_photo"
|
||||
NotificationNewPhoto = "new_photo"
|
||||
NotificationInnerCircle = "inner_circle"
|
||||
NotificationCustom = "custom" // custom message pushed
|
||||
)
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ const (
|
|||
PhotoPublic PhotoVisibility = "public" // on profile page and/or public gallery
|
||||
PhotoFriends = "friends" // only friends can see it
|
||||
PhotoPrivate = "private" // private
|
||||
PhotoInnerCircle = "circle" // inner circle
|
||||
)
|
||||
|
||||
// PhotoVisibility preset settings.
|
||||
|
@ -42,6 +43,14 @@ 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{
|
||||
|
@ -213,6 +222,12 @@ func PaginateGalleryPhotos(user *User, conf Gallery, pager *Pagination) ([]*Phot
|
|||
placeholders = []interface{}{}
|
||||
)
|
||||
|
||||
// Define "all photos visibilities"
|
||||
var photosAll = PhotoVisibilityAll
|
||||
if user.IsInnerCircle() {
|
||||
photosAll = PhotoVisibilityCircle
|
||||
}
|
||||
|
||||
// Admins see everything on the site (only an admin user can get an admin view).
|
||||
adminView = user.IsAdmin && adminView
|
||||
|
||||
|
@ -229,7 +244,7 @@ func PaginateGalleryPhotos(user *User, conf Gallery, pager *Pagination) ([]*Phot
|
|||
)
|
||||
placeholders = append(placeholders,
|
||||
friendIDs, PhotoVisibilityFriends,
|
||||
privateUserIDs, PhotoVisibilityAll,
|
||||
privateUserIDs, photosAll,
|
||||
)
|
||||
} else {
|
||||
// You can see friends' Friend photos but only public for non-friends.
|
||||
|
@ -240,7 +255,7 @@ func PaginateGalleryPhotos(user *User, conf Gallery, pager *Pagination) ([]*Phot
|
|||
)
|
||||
placeholders = append(placeholders,
|
||||
friendIDs, PhotoVisibilityFriends,
|
||||
privateUserIDs, PhotoVisibilityAll,
|
||||
privateUserIDs, photosAll,
|
||||
friendIDs, PhotoPublic,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -25,7 +25,8 @@ type User struct {
|
|||
Name *string
|
||||
Birthdate time.Time
|
||||
Certified bool
|
||||
Explicit bool // user has opted-in to see explicit content
|
||||
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"`
|
||||
|
@ -185,6 +186,7 @@ type UserSearch struct {
|
|||
Orientation string
|
||||
MaritalStatus string
|
||||
Certified bool
|
||||
InnerCircle bool
|
||||
AgeMin int
|
||||
AgeMax int
|
||||
}
|
||||
|
@ -249,6 +251,11 @@ func SearchUsers(user *User, search *UserSearch, pager *Pagination) ([]*User, er
|
|||
placeholders = append(placeholders, search.Certified, UserStatusActive)
|
||||
}
|
||||
|
||||
if search.InnerCircle {
|
||||
wheres = append(wheres, "inner_circle = ? OR is_admin = ?")
|
||||
placeholders = append(placeholders, true, true)
|
||||
}
|
||||
|
||||
if search.AgeMin > 0 {
|
||||
date := time.Now().AddDate(-search.AgeMin, 0, 0)
|
||||
wheres = append(wheres, "birthdate <= ?")
|
||||
|
|
67
pkg/models/user_inner_circle.go
Normal file
67
pkg/models/user_inner_circle.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"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.IsAdmin
|
||||
}
|
||||
|
||||
// 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", PhotoPublic); err != nil {
|
||||
log.Error("RemoveFromInnerCircle: couldn't update photo visibility: %s", err)
|
||||
}
|
||||
|
||||
// Revoke any historic notification about the circle.
|
||||
RemoveNotification("__inner_circle", u.ID)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -62,6 +62,8 @@ func New() http.Handler {
|
|||
mux.Handle("/comments", middleware.LoginRequired(comment.PostComment()))
|
||||
mux.Handle("/comments/subscription", middleware.LoginRequired(comment.Subscription()))
|
||||
mux.Handle("/admin/unimpersonate", middleware.LoginRequired(admin.Unimpersonate()))
|
||||
mux.Handle("/inner-circle", middleware.LoginRequired(account.InnerCircle()))
|
||||
mux.Handle("/inner-circle/invite", middleware.LoginRequired(account.InviteCircle()))
|
||||
|
||||
// Certification Required. Pages that only full (verified) members can access.
|
||||
mux.Handle("/photo/gallery", middleware.CertRequired(photo.SiteGallery()))
|
||||
|
@ -81,6 +83,7 @@ func New() http.Handler {
|
|||
mux.Handle("/admin/user-action", middleware.AdminRequired(admin.UserActions()))
|
||||
mux.Handle("/forum/admin", middleware.AdminRequired(forum.Manage()))
|
||||
mux.Handle("/forum/admin/edit", middleware.AdminRequired(forum.AddEdit()))
|
||||
mux.Handle("/inner-circle/remove", middleware.AdminRequired(account.RemoveCircle()))
|
||||
|
||||
// JSON API endpoints.
|
||||
mux.HandleFunc("/v1/version", api.Version())
|
||||
|
|
|
@ -39,10 +39,14 @@ func TemplateFuncs(r *http.Request) template.FuncMap {
|
|||
))
|
||||
},
|
||||
"PrettyTitleShort": func() template.HTML {
|
||||
return template.HTML(fmt.Sprintf(
|
||||
`<strong style="color: #0077FF">n</strong>` +
|
||||
return template.HTML(`<strong style="color: #0077FF">n</strong>` +
|
||||
`<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],
|
||||
|
|
|
@ -55,6 +55,10 @@ abbr {
|
|||
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;
|
||||
|
|
BIN
web/static/img/circle-10.png
Normal file
BIN
web/static/img/circle-10.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 733 B |
BIN
web/static/img/circle-16.png
Normal file
BIN
web/static/img/circle-16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 859 B |
BIN
web/static/img/circle-24.png
Normal file
BIN
web/static/img/circle-24.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
BIN
web/static/img/circle-32.png
Normal file
BIN
web/static/img/circle-32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -326,6 +326,8 @@
|
|||
<span class="icon">
|
||||
{{if and $Body.Photo (eq $Body.Photo.Visibility "private")}}
|
||||
<i class="fa fa-eye has-text-private"></i>
|
||||
{{else if and $Body.Photo (eq $Body.Photo.Visibility "circle")}}
|
||||
<img src="/static/img/circle-16.png">
|
||||
{{else}}
|
||||
<i class="fa fa-image has-text-link"></i>
|
||||
{{end}}
|
||||
|
@ -349,6 +351,15 @@
|
|||
<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}}
|
||||
|
|
118
web/templates/account/inner_circle.html
Normal file
118
web/templates/account/inner_circle.html
Normal file
|
@ -0,0 +1,118 @@
|
|||
{{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">
|
||||
<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="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>
|
||||
|
||||
<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="/members"><strong><i class="fa fa-comments mr-1"></i> Forums</strong></a>
|
||||
you can access exclusive inner circle-only boards.
|
||||
</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>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 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>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{{end}}
|
104
web/templates/account/invite_circle.html
Normal file
104
web/templates/account/invite_circle.html
Normal file
|
@ -0,0 +1,104 @@
|
|||
{{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">
|
||||
<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>
|
||||
|
||||
<form action="/inner-circle/invite" method="POST">
|
||||
{{InputCSRF}}
|
||||
<input type="hidden" name="to" value="{{.User.Username}}">
|
||||
|
||||
<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>
|
||||
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>
|
||||
</div>
|
||||
|
||||
<div class="field has-text-centered">
|
||||
<button type="submit" name="intent" value="confirm" class="button is-success">
|
||||
Add to 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}}
|
|
@ -104,6 +104,17 @@
|
|||
</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">
|
||||
|
|
94
web/templates/account/remove_circle.html
Normal file
94
web/templates/account/remove_circle.html
Normal file
|
@ -0,0 +1,94 @@
|
|||
{{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">
|
||||
<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}}">
|
||||
|
||||
<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>
|
||||
|
||||
<div class="field has-text-centered">
|
||||
<button type="submit" name="intent" value="confirm" class="button is-danger">
|
||||
Remove 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}}
|
|
@ -54,10 +54,13 @@
|
|||
|
||||
<div class="column">
|
||||
<div class="field">
|
||||
<label class="label">Certified:</label>
|
||||
<label class="label">Status:</label>
|
||||
<div class="select is-fullwidth">
|
||||
<select id="certified" name="certified">
|
||||
<option value="true">Only certified users</option>
|
||||
{{if .CurrentUser.IsInnerCircle}}
|
||||
<option value="circle"{{if eq $Root.Certified "circle"}} selected{{end}}>Inner circle only</option>
|
||||
{{end}}
|
||||
<option value="false"{{if eq $Root.Certified "false"}} selected{{end}}>Show all users</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -189,6 +192,9 @@
|
|||
{{else}}
|
||||
{{.NameOrUsername}}
|
||||
{{end}}
|
||||
{{if .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>
|
||||
|
|
|
@ -167,6 +167,12 @@
|
|||
<span class="icon"><i class="fa fa-eye"></i></span>
|
||||
<span>Private Photos</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>
|
||||
|
|
|
@ -127,6 +127,19 @@
|
|||
Check this box if the forum allows photos to be uploaded (not implemented)
|
||||
</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}}
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
|
|
|
@ -76,6 +76,13 @@
|
|||
</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>
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
<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>
|
||||
|
||||
|
|
|
@ -40,6 +40,15 @@
|
|||
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">
|
||||
</span>
|
||||
<span>
|
||||
{{PrettyCircle}}
|
||||
</span>
|
||||
</span>
|
||||
{{else}}
|
||||
<span class="tag is-private is-light">
|
||||
<span class="icon"><i class="fa fa-lock"></i></span>
|
||||
|
@ -251,6 +260,9 @@
|
|||
<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}}
|
||||
<option value="friends"{{if eq .FilterVisibility "friends"}} selected{{end}}>Friends only</option>
|
||||
<option value="private"{{if eq .FilterVisibility "private"}} selected{{end}}>Private only</option>
|
||||
</select>
|
||||
|
@ -328,6 +340,25 @@
|
|||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Inner circle invitation -->
|
||||
{{if and (.CurrentUser.IsInnerCircle) (not .User.InnerCircle) (ne .CurrentUser.Username .User.Username)}}
|
||||
<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}}.
|
||||
{{if .CurrentUser.IsAdmin}}
|
||||
<a href="/inner-circle/remove?to={{.User.Username}}" class="has-text-danger ml-2">
|
||||
<i class="fa fa-gavel"></i> Remove from circle?
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{template "pager" .}}
|
||||
|
||||
<!-- "Full" view style? (blog style) -->
|
||||
|
@ -356,6 +387,8 @@
|
|||
<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}}
|
||||
|
@ -463,6 +496,8 @@
|
|||
<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}}
|
||||
|
|
|
@ -215,7 +215,7 @@
|
|||
value="public"
|
||||
{{if or (not .EditPhoto) (eq .EditPhoto.Visibility "public")}}checked{{end}}>
|
||||
<strong class="has-text-link ml-1">
|
||||
<span>Public</span>
|
||||
<span>Public <small>(members only)</small></span>
|
||||
<span class="icon"><i class="fa fa-eye"></i></span>
|
||||
</strong>
|
||||
</label>
|
||||
|
@ -225,6 +225,27 @@
|
|||
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"
|
||||
|
|
Loading…
Reference in New Issue
Block a user