Ability to delete DMs and minor spit & polish

This commit is contained in:
Noah Petherbridge 2022-12-20 21:11:43 -08:00
parent 96a90c059e
commit 72a7f57f03
10 changed files with 237 additions and 62 deletions

View File

@ -18,7 +18,7 @@ var (
PageSizeSiteGallery = 16
PageSizeUserGallery = 16
PageSizeInboxList = 20 // sidebar list
PageSizeInboxThread = 20 // conversation view
PageSizeInboxThread = 10 // conversation view
PageSizeForums = 100 // TODO: for main category index view
PageSizeThreadList = 20 // 20 threads per board, 20 posts per thread
PageSizeForumAdmin = 20

View File

@ -0,0 +1,84 @@
package inbox
import (
"net/http"
"strconv"
"code.nonshy.com/nonshy/website/pkg/models"
"code.nonshy.com/nonshy/website/pkg/session"
"code.nonshy.com/nonshy/website/pkg/templates"
)
// Delete a new chat coming from a user's profile page.
func Delete() http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
session.FlashError(w, r, "Invalid method.")
templates.Redirect(w, "/")
return
}
// Parse parameters.
var (
id uint64
idStr = r.FormValue("id")
deleteAll = r.FormValue("intent") == "delete-thread"
next = r.FormValue("next")
)
if value, err := strconv.Atoi(idStr); err == nil {
id = uint64(value)
} else {
session.FlashError(w, r, "Request error.")
templates.Redirect(w, "/")
return
}
// The redirect URL must be local.
if len(next) == 0 || next[0] != '/' {
next = "/"
}
// Get the current user.
currentUser, err := session.CurrentUser(r)
if err != nil {
session.FlashError(w, r, "Error getting the current user: %s", err)
templates.Redirect(w, next)
return
}
// Lookup the message.
message, err := models.GetMessage(id)
if err != nil {
session.FlashError(w, r, err.Error())
templates.Redirect(w, next)
}
// Delete whole thread?
if deleteAll {
if err := models.DeleteMessageThread(message); err != nil {
session.FlashError(w, r, "Error removing thread: %s", err)
} else {
session.Flash(w, r, "Message thread has been removed.")
}
templates.Redirect(w, next)
return
}
// We should be a party on this message.
if message.SourceUserID != currentUser.ID {
session.FlashError(w, r, "You did not create that message so you can't delete it.")
templates.Redirect(w, next)
return
}
// Do the needful.
if err := message.Delete(); err != nil {
session.FlashError(w, r, "Error deleting the message: %s", err)
} else {
session.Flash(w, r, "Message deleted!")
}
templates.Redirect(w, next)
})
}

View File

@ -75,6 +75,15 @@ func GetMessageThread(sourceUserID, targetUserID uint64, pager *Pagination) ([]*
return m, result.Error
}
// DeleteMessageThread removes all message history between two people.
func DeleteMessageThread(message *Message) error {
return DB.Where(
"(source_user_id = ? AND target_user_id = ?) OR (source_user_id = ? AND target_user_id = ?)",
message.SourceUserID, message.TargetUserID,
message.TargetUserID, message.SourceUserID,
).Delete(&Message{}).Error
}
// CountUnreadMessages gets the count of unread messages for a user.
func CountUnreadMessages(userID uint64) (int64, error) {
query := DB.Where(
@ -106,3 +115,8 @@ func (m *Message) Save() error {
result := DB.Save(m)
return result.Error
}
// Delete a message.
func (m *Message) Delete() error {
return DB.Delete(m).Error
}

View File

@ -52,6 +52,7 @@ func New() http.Handler {
mux.Handle("/messages", middleware.LoginRequired(inbox.Inbox()))
mux.Handle("/messages/read/", middleware.LoginRequired(inbox.Inbox()))
mux.Handle("/messages/compose", middleware.LoginRequired(inbox.Compose()))
mux.Handle("/messages/delete", middleware.LoginRequired(inbox.Delete()))
mux.Handle("/friends", middleware.LoginRequired(friend.Friends()))
mux.Handle("/friends/add", middleware.LoginRequired(friend.AddFriend()))
mux.Handle("/users/block", middleware.LoginRequired(block.BlockUser()))

View File

@ -1,5 +1,9 @@
/* Custom CSS styles */
abbr {
cursor: help;
}
/* Container for large profile pic on user pages */
.profile-photo {
width: 150px;
@ -10,10 +14,12 @@
padding: 4px;
position: relative;
}
.profile-photo img {
max-width: 100%;
height: auto;
}
.profile-photo .corner {
position: absolute;
top: 0;
@ -36,14 +42,17 @@
.has-text-private {
color: #CC00CC;
}
.has-text-private-light {
color: #FF99FF;
}
.hero.is-private {
background-color: #b748c7;
}
.hero.is-private.is-bold {
background-image: linear-gradient(141deg,#b329b1 0,#9948c7 71%,#7156d2 100%);
background-image: linear-gradient(141deg, #b329b1 0, #9948c7 71%, #7156d2 100%);
}
/* Mobile: notification badge near the hamburger menu */
@ -53,6 +62,7 @@
right: 50px;
z-index: 1000;
}
@media screen and (min-width: 1024px) {
.nonshy-mobile-notification {
display: none;

View File

@ -5,8 +5,11 @@
<section class="hero is-link is-bold">
<div class="hero-body">
<div class="container">
<h1 class="title">People</h1>
<h2 class="subtitle">Explore</h2>
<h1 class="title">
<i class="fa fa-people-group mr-2"></i>
People
</h1>
<h2 class="subtitle">Member Directory</h2>
</div>
</div>
</section>

View File

@ -20,11 +20,19 @@
<ul>
<li><a href="#certification">What does <strong>certification</strong> mean, and what is a <strong>"verification selfie"</strong>?</a></li>
<li><a href="#need-certification">Do I <strong>need</strong> to send a "verification selfie"?</a></li>
<li><a href="#why-certify">Why the hard requirement to certify?</a> <strong class="tag is-success is-light">NEW Sept. 9 2022</strong></li>
<li><a href="#cannot-certify">Are there <strong>alternative options</strong> to becoming Certified?</a> <strong><span class="tag is-success is-light">NEW Sept. 8 2022</span></strong></li>
<li><a href="#private-avatar">Can my <strong>Profile Picture be kept private?</strong></a> <strong><span class="tag is-success is-light">NEW Sept. 8 2022</span></strong></li>
<li><a href="#why-certify">Why the hard requirement to certify?</a></li>
<li><a href="#cannot-certify">Are there <strong>alternative options</strong> to becoming Certified?</a></li>
<li><a href="#uncertified">What can non-certified members do?</a></li>
</ul>
</li>
<li>
<a href="#privacy-faqs">Privacy FAQs</a>
<ul>
<li><a href="#private-avatar">Can my <strong>Profile Picture be kept private?</strong></a></li>
<li><a href="#profile-visibility">What are the <strong>visibility options</strong> for my profile page?</a></li>
<li><a href="#delete-messages">How do I delete direct messages (DMs)?</a></li>
</ul>
</li>
<li>
@ -91,7 +99,7 @@
<h3 id="why-certify">Why the hard requirement to certify?</h3>
<p>
<strong class="tag is-success is-light">NEW Sept. 9 2022</strong>
I didn't set out to build "just another social network" that allows for random, faceless, anonymous
people to sign up - you can do that literally anywhere else. This website was designed
<em>specifically</em> to get ahead of what happened with Tumblr, Pornhub and other sites
@ -155,7 +163,6 @@
<h3 id="cannot-certify">Are there alternative options to becoming Certified?</h3>
<p>
<strong><span class="tag is-success is-light">NEW Sept. 8 2022</span></strong>
I understand that some nudists need to exercise a degree of discretion and will
not want to take a face pic with {{PrettyTitle}}'s name and upload that <em>anywhere at all</em> onto the Internet. For
example if you are a teacher or work in law enforcement or the clergy and you need to keep
@ -180,10 +187,31 @@
picture can be marked private or "friends only" if you like; see below.
</p>
<h3 id="uncertified">What can non-certified members do?</h3>
<p>
Before you have an approved certification photo, you can mainly only access and edit your
own profile page, and upload a few pictures while you await approval. Your pictures won't
be shown to members on the Site Gallery until you're certified, and most of the website's
features (namely, the Forums, Site Gallery and Member Search Directory) are gated behind
certification.
</p>
<p>
Certified members may, at their own discretion, be able to find your profile page by
browsing the Member Directory. They may send you a friend request or reach out to you.
Or, if you happen to know a member's profile URL on this site, you (the non-certified
member) can view their profile page and photo gallery (depending on their privacy
settings), send them a friend request or direct message. But basically, it will be
difficult to discover users to interact with until after you are certified - and
this is intentional to help guard against spam bots and creepy people.
</p>
<h2 id="privacy-faqs">Privacy FAQs</h2>
<h3 id="private-avatar">Can my Profile Picture be kept private?</h3>
<p>
<strong><span class="tag is-success is-light">NEW Sept. 8 2022</span></strong>
You <em>may</em> set your Profile Picture to be "Friends only" or "Private" visibility
if you wish to be more discreet about your face pictures.
</p>
@ -207,26 +235,6 @@
</li>
</ul>
<h3 id="uncertified">What can non-certified members do?</h3>
<p>
Before you have an approved certification photo, you can mainly only access and edit your
own profile page, and upload a few pictures while you await approval. Your pictures won't
be shown to members on the Site Gallery until you're certified, and most of the website's
features (namely, the Forums, Site Gallery and Member Search Directory) are gated behind
certification.
</p>
<p>
Certified members may, at their own discretion, be able to find your profile page by
browsing the Member Directory. They may send you a friend request or reach out to you.
Or, if you happen to know a member's profile URL on this site, you (the non-certified
member) can view their profile page and photo gallery (depending on their privacy
settings), send them a friend request or direct message. But basically, it will be
difficult to discover users to interact with until after you are certified - and
this is intentional to help guard against spam bots and creepy people.
</p>
<h3 id="profile-visibility">What are the visibility options for my profile page?</h3>
<p>
@ -258,6 +266,26 @@
</li>
</ul>
<h3 id="delete-messages">How do I delete direct messages (DMs)?</h3>
<p>
You have two options for deleting your private one-on-one chat messages.
</p>
<p>
You can delete <strong>your own</strong> messages by clicking on the "Delete"
button that appears below them. This deletes your message from <em>both</em> of
your threads, and (if unread), will not notify them that they had an unread
message.
</p>
<p>
You can delete <strong>the whole thread</strong> by using the "Delete whole thread"
button at the bottom of the conversation. This will remove <em>both</em> of your
chat history with one another and make it like you never exchanged a DM before
at all.
</p>
<h1 id="photo-faqs">Photo FAQs</h1>
<h3 id="nudes-required">Do I have to post my nudes here?</h3>

View File

@ -5,7 +5,10 @@
<section class="hero is-link is-bold">
<div class="hero-body">
<div class="container">
<h1 class="title">Friends</h1>
<h1 class="title">
<i class="fa fa-user-group mr-2"></i>
Friends
</h1>
</div>
</div>
</section>

View File

@ -4,12 +4,16 @@
<section class="hero is-info is-bold">
<div class="hero-body">
<div class="container">
<h1 class="title">Messages</h1>
<h1 class="title">
<i class="fa fa-envelope mr-2"></i>
Messages
</h1>
<h2 class="subtitle">{{if .IsSentBox}}Sent{{else}}Inbox{{end}}</h2>
</div>
</div>
</section>
{{$Root := .}}
{{$UserMap := .UserMap}}
{{$Request := .Request}}
@ -58,7 +62,16 @@
{{template "avatar-64x64" $SourceUser}}
</div>
<div class="media-content">
<p class="title is-4">{{$SourceUser.NameOrUsername}}</p>
<p class="title is-4">
{{$SourceUser.NameOrUsername}}
{{if $SourceUser.IsAdmin}}
<span class="tag is-danger is-light ml-2 p-1" style="font-size: x-small">
<i class="fa fa-gavel mr-1"></i>
Admin
</span>
{{end}}
</p>
<p class="subtitle is-6">
<span class="icon"><i class="fa fa-user"></i></span>
<a href="/u/{{$SourceUser.Username}}">{{$SourceUser.Username}}</a>
@ -68,13 +81,6 @@
<span>Not Certified!</span>
</span>
{{end}}
{{if $SourceUser.IsAdmin}}
<span class="has-text-danger">
<span class="icon"><i class="fa fa-gavel"></i></span>
<span>Admin</span>
</span>
{{end}}
</p>
</div>
</div>
@ -83,8 +89,23 @@
{{ToMarkdown .Message}}
</div>
<div class="block">
<em>Sent on {{.CreatedAt.Format "2006-01-02 15:04:05"}}</em>
<em>Sent <abbr title="{{.CreatedAt.Format "2006-01-02 15:04:05"}}">
{{SincePrettyCoarse .CreatedAt}} ago
</em>
{{if not .Read}}<span class="tag is-success ml-2">UNREAD</span>{{end}}
<!-- Our message? We can delete it. -->
{{if eq $Root.CurrentUser.ID $SourceUser.ID}}
<form action="/messages/delete" method="POST" class="is-inline" onsubmit="return confirm('Delete this message?')">
{{InputCSRF}}
<input type="hidden" name="id" value="{{.ID}}">
<input type="hidden" name="next" value="{{$Root.Request.URL.Path}}">
<button class="button has-text-danger is-outline is-small p-1 ml-4">
<i class="fa fa-trash mr-2"></i>
Delete
</button>
</form>
{{end}}
</div>
<hr class="block">
@ -98,6 +119,20 @@
Found <strong>{{.ThreadPager.Total}}</strong> message{{Pluralize64 .ThreadPager.Total}} in this thread
(page {{.ThreadPager.Page}} of {{.ThreadPager.Pages}}).
</div>
<!-- "Delete ALL" Form -->
<form action="/messages/delete" method="POST"
class="is-inline"
onsubmit="return confirm('Are you sure you want to delete this whole entire thread for both of you? It will be like you two had never chatted before at all.')">
{{InputCSRF}}
<input type="hidden" name="intent" value="delete-thread">
<input type="hidden" name="id" value="{{$Root.MessageID}}">
<input type="hidden" name="next" value="/messages">
<button class="button has-text-danger is-outline is-small p-1 ml-4">
<i class="fa fa-trash mr-2"></i>
Delete whole thread
</button>
</form>
</div>
<div class="level-right">
{{if .ThreadPager.HasPrevious}}
@ -128,7 +163,7 @@
</header>
<div class="card-content">
<div class="tabs is-toggle">
<div class="tabs is-toggle is-fullwidth">
<ul>
<li{{if not .IsSentBox}} class="is-active"{{end}}>
<a href="/messages">Inbox</a>

View File

@ -97,29 +97,26 @@
<div class="container">
<section class="hero is-info is-bold">
<div class="hero-body">
{{if .IsSiteGallery}}
<h1 class="title">
{{template "title" .}}
</h1>
{{else}}
<div class="level">
<div class="level-left">
<h1 class="title">
{{template "title" .}}
</h1>
</div>
{{if .IsOwnPhotos}}
<div class="level-right">
<div>
<a href="/photo/upload" class="button">
<span class="icon"><i class="fa fa-upload"></i></span>
<span>Upload Photos</span>
</a>
<div class="container">
<div class="level">
<div class="level-left">
<h1 class="title">
<span class="icon mr-4"><i class="fa fa-image"></i></span>
<span>{{template "title" .}}</span>
</h1>
</div>
{{if or .IsOwnPhotos .IsSiteGallery}}
<div class="level-right">
<div>
<a href="/photo/upload" class="button">
<span class="icon"><i class="fa fa-upload"></i></span>
<span>Upload Photo</span>
</a>
</div>
</div>
{{end}}
</div>
{{end}}
</div>
{{end}}
</div>
</section>