Likes on Comments, and other minor improvements
* Add "Like" buttons to comments and forum posts. * Make "private" profiles more private (logged-in users see only their profile pic, display name, and can friend request or message, if they are not approved friends of the private user) * Add "logged-out view" visibility setting to profiles: to share a link to your page on other sites. Opt-in setting - default is login required to view your public profile page. * CSRF cookie fix. * Updated FAQ & Privacy pages.
This commit is contained in:
parent
d2700490cc
commit
8419958b25
|
@ -29,7 +29,7 @@ const (
|
|||
const (
|
||||
BcryptCost = 14
|
||||
SessionCookieName = "session_id"
|
||||
CSRFCookieName = "csrf_token"
|
||||
CSRFCookieName = "xsrf_token"
|
||||
CSRFInputName = "_csrf" // html input name
|
||||
SessionCookieMaxAge = 60 * 60 * 24 * 30
|
||||
SessionRedisKeyFormat = "session/%s"
|
||||
|
|
|
@ -30,12 +30,26 @@ func Profile() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
// Get the current user (if logged in).
|
||||
// Forcing an external view? (preview of logged-out profile view for visibility=external accounts)
|
||||
if r.FormValue("view") == "external" {
|
||||
vars := map[string]interface{}{
|
||||
"User": user,
|
||||
"IsPrivate": true,
|
||||
"IsExternalView": true,
|
||||
}
|
||||
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get the current user (if logged in). If not, check for external view.
|
||||
currentUser, err := session.CurrentUser(r)
|
||||
if err != nil {
|
||||
// The viewer is not logged in, bail now with the basic profile page. If this
|
||||
// user is private, redirect to login.
|
||||
if user.Visibility == models.UserVisibilityPrivate {
|
||||
// user doesn't allow external viewers, redirect to login page.
|
||||
if user.Visibility != models.UserVisibilityExternal {
|
||||
session.FlashError(w, r, "You must be signed in to view this page.")
|
||||
templates.Redirect(w, "/login?next="+url.QueryEscape(r.URL.String()))
|
||||
return
|
||||
|
|
|
@ -93,14 +93,16 @@ func Settings() http.HandlerFunc {
|
|||
case "preferences":
|
||||
var (
|
||||
explicit = r.PostFormValue("explicit") == "true"
|
||||
private = r.PostFormValue("private") == "true"
|
||||
visibility = models.UserVisibility(r.PostFormValue("visibility"))
|
||||
)
|
||||
|
||||
user.Explicit = explicit
|
||||
if private {
|
||||
user.Visibility = models.UserVisibilityPrivate
|
||||
} else {
|
||||
user.Visibility = models.UserVisibilityPublic
|
||||
|
||||
for _, cmp := range models.UserVisibilityOptions {
|
||||
if visibility == cmp {
|
||||
user.Visibility = visibility
|
||||
}
|
||||
}
|
||||
|
||||
if err := user.Save(); err != nil {
|
||||
|
|
|
@ -5,8 +5,6 @@ import (
|
|||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
)
|
||||
|
||||
// Envelope is the standard JSON response envelope.
|
||||
|
@ -27,8 +25,6 @@ func ParseJSON(r *http.Request, v interface{}) error {
|
|||
return err
|
||||
}
|
||||
|
||||
log.Error("body: %+v", body)
|
||||
|
||||
// Parse params from JSON.
|
||||
if err := json.Unmarshal(body, v); err != nil {
|
||||
return err
|
||||
|
|
|
@ -16,6 +16,7 @@ func Likes() http.HandlerFunc {
|
|||
TableName string `json:"name"`
|
||||
TableID uint64 `json:"id"`
|
||||
Unlike bool `json:"unlike,omitempty"`
|
||||
Referrer string `json:"page"`
|
||||
}
|
||||
|
||||
// Response JSON schema.
|
||||
|
@ -51,8 +52,18 @@ func Likes() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
// Sanity check things. The page= param (Referrer) must be a relative URL, the path
|
||||
// is useful for "liked your comment" notifications to supply the Link URL for the
|
||||
// notification.
|
||||
if len(req.Referrer) > 0 && req.Referrer[0] != '/' {
|
||||
req.Referrer = ""
|
||||
}
|
||||
|
||||
// Who do we notify about this like?
|
||||
var targetUser *models.User
|
||||
var (
|
||||
targetUser *models.User
|
||||
notificationMessage string
|
||||
)
|
||||
switch req.TableName {
|
||||
case "photos":
|
||||
if photo, err := models.GetPhoto(req.TableID); err == nil {
|
||||
|
@ -70,6 +81,15 @@ func Likes() http.HandlerFunc {
|
|||
} else {
|
||||
log.Error("For like on users table: didn't find user %d: %s", req.TableID, err)
|
||||
}
|
||||
case "comments":
|
||||
log.Error("subject is users, find %d", req.TableID)
|
||||
if comment, err := models.GetComment(req.TableID); err == nil {
|
||||
targetUser = &comment.User
|
||||
notificationMessage = comment.Message
|
||||
log.Warn("found user %s", targetUser.Username)
|
||||
} else {
|
||||
log.Error("For like on users table: didn't find user %d: %s", req.TableID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Is the table likeable?
|
||||
|
@ -108,6 +128,8 @@ func Likes() http.HandlerFunc {
|
|||
Type: models.NotificationLike,
|
||||
TableName: req.TableName,
|
||||
TableID: req.TableID,
|
||||
Message: notificationMessage,
|
||||
Link: req.Referrer,
|
||||
}
|
||||
if err := models.CreateNotification(notif); err != nil {
|
||||
log.Error("Couldn't create Likes notification: %s", err)
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
|
||||
var ThreadPathRegexp = regexp.MustCompile(`^/forum/thread/(\d+)$`)
|
||||
|
||||
// Thread view for a specific board index.
|
||||
// Thread view for the comment thread body of a forum post.
|
||||
func Thread() http.HandlerFunc {
|
||||
tmpl := templates.Must("forum/thread.html")
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -74,6 +74,13 @@ func Thread() http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
// Get the like map for these comments.
|
||||
commentIDs := []uint64{}
|
||||
for _, com := range comments {
|
||||
commentIDs = append(commentIDs, com.ID)
|
||||
}
|
||||
commentLikeMap := models.MapLikes(currentUser, "comments", commentIDs)
|
||||
|
||||
// Is the current user subscribed to notifications on this thread?
|
||||
_, isSubscribed := models.IsSubscribed(currentUser, "threads", thread.ID)
|
||||
|
||||
|
@ -81,6 +88,7 @@ func Thread() http.HandlerFunc {
|
|||
"Forum": forum,
|
||||
"Thread": thread,
|
||||
"Comments": comments,
|
||||
"LikeMap": commentLikeMap,
|
||||
"Pager": pager,
|
||||
"IsSubscribed": isSubscribed,
|
||||
}
|
||||
|
|
|
@ -76,6 +76,13 @@ func View() http.HandlerFunc {
|
|||
log.Error("Couldn't list comments for photo %d: %s", photo.ID, err)
|
||||
}
|
||||
|
||||
// Get the like map for these comments.
|
||||
commentIDs := []uint64{}
|
||||
for _, com := range comments {
|
||||
commentIDs = append(commentIDs, com.ID)
|
||||
}
|
||||
commentLikeMap := models.MapLikes(currentUser, "comments", commentIDs)
|
||||
|
||||
// Is the current user subscribed to notifications on this thread?
|
||||
_, isSubscribed := models.IsSubscribed(currentUser, "photos", photo.ID)
|
||||
|
||||
|
@ -86,6 +93,7 @@ func View() http.HandlerFunc {
|
|||
"LikeMap": likeMap,
|
||||
"CommentMap": commentMap,
|
||||
"Comments": comments,
|
||||
"CommentLikeMap": commentLikeMap,
|
||||
"IsSubscribed": isSubscribed,
|
||||
}
|
||||
if err := tmpl.Execute(w, r, vars); err != nil {
|
||||
|
|
|
@ -58,6 +58,7 @@ func MakeCSRFCookie(r *http.Request, w http.ResponseWriter) string {
|
|||
Name: config.CSRFCookieName,
|
||||
Value: token,
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
}
|
||||
// log.Debug("MakeCSRFCookie: giving cookie value %s to user", token)
|
||||
http.SetCookie(w, cookie)
|
||||
|
|
|
@ -20,6 +20,7 @@ type Like struct {
|
|||
var LikeableTables = map[string]interface{}{
|
||||
"photos": nil,
|
||||
"users": nil,
|
||||
"comments": nil,
|
||||
}
|
||||
|
||||
// AddLike to something.
|
||||
|
|
|
@ -40,9 +40,17 @@ type UserVisibility string
|
|||
|
||||
const (
|
||||
UserVisibilityPublic UserVisibility = "public"
|
||||
UserVisibilityExternal = "external"
|
||||
UserVisibilityPrivate = "private"
|
||||
)
|
||||
|
||||
// All visibility options.
|
||||
var UserVisibilityOptions = []UserVisibility{
|
||||
UserVisibilityPublic,
|
||||
UserVisibilityExternal,
|
||||
UserVisibilityPrivate,
|
||||
}
|
||||
|
||||
// Preload related tables for the user (classmethod).
|
||||
func (u *User) Preload() *gorm.DB {
|
||||
return DB.Preload("ProfileField").Preload("ProfilePhoto")
|
||||
|
|
|
@ -51,6 +51,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
});
|
||||
|
||||
// Touching the user drop-down button toggles it.
|
||||
if (userMenu !== null) {
|
||||
userMenu.addEventListener("touchstart", (e) => {
|
||||
// On mobile/tablet screens they had to hamburger menu their way here anyway, let it thru.
|
||||
if (screen.width < 1024) {
|
||||
|
@ -64,6 +65,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
userMenu.classList.add(activeClass);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Touching a link from the user menu lets it click thru.
|
||||
(document.querySelectorAll(".navbar-dropdown") || []).forEach(node => {
|
||||
|
|
|
@ -6,6 +6,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
// Bind to the like buttons.
|
||||
(document.querySelectorAll(".nonshy-like-button") || []).forEach(node => {
|
||||
node.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
if (busy) return;
|
||||
|
||||
let $icon = node.querySelector(".icon"),
|
||||
|
@ -36,6 +37,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
"name": tableName, // TODO
|
||||
"id": parseInt(tableID),
|
||||
"unlike": !liking,
|
||||
"page": window.location.pathname + window.location.search + window.location.hash,
|
||||
}),
|
||||
})
|
||||
.then((response) => response.json())
|
||||
|
|
|
@ -201,6 +201,12 @@
|
|||
{{end}}
|
||||
{{else if eq .TableName "users"}}
|
||||
profile page.
|
||||
{{else if eq .TableName "comments"}}
|
||||
{{if .Link}}
|
||||
<a href="{{.Link}}">comment</a>:
|
||||
{{else}}
|
||||
comment.
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{.TableName}}.
|
||||
{{end}}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{{define "title"}}{{.User.Username}}{{end}}
|
||||
{{define "content"}}
|
||||
<div class="container">
|
||||
<section class="hero {{if .LoggedIn}}is-info{{else}}is-light is-bold{{end}}">
|
||||
<section class="hero {{if and .LoggedIn (not .IsPrivate)}}is-info{{else}}is-light is-bold{{end}}">
|
||||
<div class="hero-body">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
|
@ -14,7 +14,7 @@
|
|||
{{end}}
|
||||
|
||||
<!-- CurrentUser can upload a new profile pic -->
|
||||
{{if and .LoggedIn (eq .CurrentUser.ID .User.ID)}}
|
||||
{{if and .LoggedIn (eq .CurrentUser.ID .User.ID) (not .IsPrivate)}}
|
||||
<span class="corner">
|
||||
<button class="button is-small p-1 is-success">
|
||||
<a href="/photo/upload?intent=profile_pic"
|
||||
|
@ -36,7 +36,7 @@
|
|||
({{.User.Status}})
|
||||
</h2>
|
||||
{{end}}
|
||||
{{if not .LoggedIn}}
|
||||
{{if or (not .LoggedIn) .IsPrivate}}
|
||||
<h2 class="subtitle">is on {{PrettyTitle}}, a social network for nudists & exhibitionists.</h2>
|
||||
<p>
|
||||
{{PrettyTitle}} is a new social network for <strong>real</strong> nudists and exhibionists.
|
||||
|
@ -47,7 +47,7 @@
|
|||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if .LoggedIn}}
|
||||
{{if and .LoggedIn (not .IsPrivate)}}
|
||||
<div class="column is-narrow">
|
||||
<div class="box">
|
||||
<div>
|
||||
|
@ -97,7 +97,7 @@
|
|||
{{end}}<!-- if .LoggedIn -->
|
||||
</div>
|
||||
|
||||
{{if .LoggedIn}}
|
||||
{{if and .LoggedIn (not .IsExternalView)}}
|
||||
<div class="columns is-centered is-gapless">
|
||||
<div class="column is-narrow has-text-centered">
|
||||
<form action="/friends/add" method="POST">
|
||||
|
@ -134,6 +134,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Like button -->
|
||||
{{if not .IsPrivate}}
|
||||
{{$Like := .LikeMap.Get .User.ID}}
|
||||
<div class="column is-narrow has-text-centered">
|
||||
<button type="button" class="button is-fullwidth nonshy-like-button"
|
||||
|
@ -148,6 +149,7 @@
|
|||
</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="column is-narrow has-text-centered">
|
||||
<form action="/users/block" method="POST">
|
||||
|
@ -183,7 +185,7 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
{{if not .LoggedIn}}
|
||||
{{if or (not .LoggedIn) .IsPrivate}}
|
||||
<div class="py-6"></div>
|
||||
{{else if .IsPrivate}}
|
||||
<div class="block p-4">
|
||||
|
|
|
@ -220,18 +220,46 @@
|
|||
|
||||
<div class="card-content">
|
||||
<div class="field">
|
||||
<label class="label">Private Profile</label>
|
||||
<label class="label">Profile Visibility</label>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox"
|
||||
name="private"
|
||||
value="true"
|
||||
<input type="radio"
|
||||
name="visibility"
|
||||
value="public"
|
||||
{{if eq .CurrentUser.Visibility "public"}}checked{{end}}>
|
||||
Public + Login Required
|
||||
</label>
|
||||
<p class="help">
|
||||
The default is that users must be logged-in to even look at your profile
|
||||
page. If your profile URL (/u/{{.CurrentUser.Username}}) is visited by a
|
||||
logged-out browser, they are prompted to log in.
|
||||
</p>
|
||||
|
||||
<label class="checkbox mt-2">
|
||||
<input type="radio"
|
||||
name="visibility"
|
||||
value="external"
|
||||
{{if eq .CurrentUser.Visibility "external"}}checked{{end}}>
|
||||
Public + Limited Logged-out View
|
||||
</label>
|
||||
<p class="help">
|
||||
Your profile is fully visible to logged-in users, but if a logged-out browser
|
||||
visits your page they will see a very minimal view: only your profile picture
|
||||
and display name are shown.
|
||||
<a href="/u/{{.CurrentUser.Username}}?view=external" target="_blank">Preview <i class="fa fa-external-link"></i></a>
|
||||
</p>
|
||||
|
||||
<label class="checkbox mt-2">
|
||||
<input type="radio"
|
||||
name="visibility"
|
||||
value="private"
|
||||
{{if eq .CurrentUser.Visibility "private"}}checked{{end}}>
|
||||
Mark my profile page as "private"
|
||||
</label>
|
||||
<p class="help">
|
||||
If you check this box then only friends who you have approved are able to
|
||||
see your profile page and gallery. Your gallery photos also will NOT appear
|
||||
on the Site Gallery page.
|
||||
on the Site Gallery page. If your profile page is visited by a logged-out
|
||||
viewer, they are prompted to log in.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -42,6 +42,37 @@
|
|||
until your profile has been certified.
|
||||
</p>
|
||||
|
||||
<h3>What are the visibility options for my profile page?</h3>
|
||||
|
||||
<p>
|
||||
There are currently three different choices for your profile visibility on your
|
||||
<a href="/settings">Settings</a> page:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
The <strong>default</strong> visibility is <strong class="has-text-success-dark">"Public + Login Required."</strong> Users must be
|
||||
logged-in to an account in order to see anything about your profile page - if an
|
||||
external (logged out) browser visits your profile URL, they will be redirected to
|
||||
log in to an account first.
|
||||
</li>
|
||||
<li>
|
||||
You may optionally go <em>more</em> public with a <strong class="has-text-warning-dark">"Limited Logged-out View."</strong>
|
||||
This enables your profile URL (e.g.,
|
||||
{{if .LoggedIn}}<a href="/u/{{.CurrentUser.Username}}?view=external">/u/{{.CurrentUser.Username}}</a>{{else}}/u/username{{end}})
|
||||
to show a <em>basic</em> page (with your square profile picture and display name) to
|
||||
logged-out browsers. This may be useful if you wish to link to your page from an external
|
||||
site (e.g. your Twitter page) and present new users with a better experience than just
|
||||
a redirect to login page.
|
||||
</li>
|
||||
<li>
|
||||
You may <strong class="has-text-private">"Mark my profile as 'private'"</strong> to
|
||||
be private even from other logged-in members who are not on your Friends
|
||||
list. Logged-in users will see only your square profile picture and display
|
||||
name, and be able only to send you a friend request or a message.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h1>Photo FAQs</h1>
|
||||
|
||||
<h3>Do I have to post my nudes here?</h3>
|
||||
|
|
|
@ -157,6 +157,21 @@
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<div class="column is-narrow">
|
||||
{{$Like := $Root.LikeMap.Get .ID}}
|
||||
<a href="#" class="has-text-dark nonshy-like-button"
|
||||
data-table-name="comments" data-table-id="{{.ID}}"
|
||||
title="Like">
|
||||
<span class="icon{{if $Like.UserLikes}} has-text-danger{{end}}"><i class="fa fa-heart"></i></span>
|
||||
<span class="nonshy-likes">
|
||||
Like
|
||||
{{if gt $Like.Count 0}}
|
||||
({{$Like.Count}})
|
||||
{{end}}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="column is-narrow">
|
||||
<a href="/contact?intent=report&subject=report.comment&id={{.ID}}" class="has-text-dark">
|
||||
<span class="icon"><i class="fa fa-flag"></i></span>
|
||||
|
|
|
@ -21,15 +21,9 @@
|
|||
|
||||
<p>
|
||||
This website was designed by a life-long nudist, exhibitionist and software engineer to create
|
||||
a safe space for like-minded individuals online, especially in the modern online political
|
||||
climate and after Tumblr, Pornhub and other social networks had begun clamping down and kicking
|
||||
off all the nudists from their platforms.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This website is open to <em>all</em> nudists and exhibitionists, but I understand that not all
|
||||
nudists want to see any sexual content, so this site provides some controls to support
|
||||
both camps:
|
||||
a safe space for like-minded individuals online. This website is open to <em>all</em> nudists
|
||||
and exhibitionists (18+), but I understand that not all nudists want to see any sexual content, so
|
||||
this site provides some controls to support both camps:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
|
@ -44,6 +38,11 @@
|
|||
</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
You can read more <a href="/about">about this website</a> and check out the <a href="/faq">FAQ</a>
|
||||
page for more information.
|
||||
</p>
|
||||
|
||||
<h1>Site Rules</h1>
|
||||
|
||||
<ul>
|
||||
|
|
|
@ -241,6 +241,21 @@
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<div class="column is-narrow">
|
||||
{{$Like := $Root.CommentLikeMap.Get .ID}}
|
||||
<a href="#" class="has-text-dark nonshy-like-button"
|
||||
data-table-name="comments" data-table-id="{{.ID}}"
|
||||
title="Like">
|
||||
<span class="icon{{if $Like.UserLikes}} has-text-danger{{end}}"><i class="fa fa-heart"></i></span>
|
||||
<span class="nonshy-likes">
|
||||
Like
|
||||
{{if gt $Like.Count 0}}
|
||||
({{$Like.Count}})
|
||||
{{end}}
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="column is-narrow">
|
||||
<a href="/contact?intent=report&subject=report.comment&id={{.ID}}" class="has-text-dark">
|
||||
<span class="icon"><i class="fa fa-flag"></i></span>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
</p>
|
||||
|
||||
<p>
|
||||
This page was last updated on <strong>August 26, 2022.</strong>
|
||||
This page was last updated on <strong>August 29, 2022.</strong>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
|
@ -43,6 +43,10 @@
|
|||
</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
By default, your profile page on {{PrettyTitle}} may <strong>only</strong> be seen
|
||||
by logged-in members of the website.
|
||||
</li>
|
||||
<li>
|
||||
You may mark your entire profile as "Private" which limits some of the contact you
|
||||
may receive:
|
||||
|
@ -58,6 +62,12 @@
|
|||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Optionally, you may mark your Public profile to allow a limited "logged out" view which
|
||||
shows only your square profile picture and display name. This may be useful to link to
|
||||
your profile from external sites (like Twitter) so the visitor isn't just redirected to a
|
||||
"login required" page.
|
||||
</li>
|
||||
<li>
|
||||
Profile photos have visibility settings including Public, Friends-only or Private:
|
||||
<ul>
|
||||
|
@ -77,6 +87,12 @@
|
|||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<strong>Notice:</strong> the square default profile picture that appears on your page
|
||||
will always be visible to all logged-in users. The full size version on your Gallery
|
||||
page may be restricted to friends or private, but the square cropped version that appears
|
||||
next to your username on many parts of the website is always seen by logged-in users.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h3>Site-Wide Photo Gallery</h3>
|
||||
|
|
Loading…
Reference in New Issue
Block a user