website/web/templates/photo/gallery.html
Noah Petherbridge 481bd0ae61 Deactivate Account; Friends Lists on Profiles
* Add a way for users to temporarily deactivate their accounts, in a
  recoverable way should they decide to return later.
* A deactivated account may log in but have limited options: to
  reactivate their account, permanently delete it, or log out.
* Fix several bugs around the display of comments, messages and
  forum threads for disabled, banned, or blocked users:
  * Messages (inbox and sentbox) will be hidden and the unread indicator
    will not count unread messages the user can't access.
  * Comments on photos and forum posts are hidden, and top-level threads
    on the "Newest" tab will show "[unavailable]" for their text and
    username.
  * Your historical notifications will hide users who are blocked, banned
    or disabled.
* Add a "Friends" tab to user profile pages, to see other users' friends.
  * The page is Certification Required so non-cert users can't easily
    discover any members on the site.
2023-10-22 15:02:24 -07:00

618 lines
30 KiB
HTML

<!--
Photo Gallery Template, shared by Site Photos + User Photos.
When Site Gallery: .IsSiteGallery is defined and true.
When User Gallery: .User is defined, .IsOwnPhotos may be.
-->
{{define "title"}}
{{if .IsSiteGallery}}
Member Gallery
{{else}}
Photos of {{.User.Username}}
{{if eq .User.Visibility "private"}}<sup class="fa fa-mask ml-2 is-size-6" title="Private Profile"></sup>{{end}}
{{end}}
{{end}}
<!-- Reusable card body -->
{{define "card-body"}}
<div>
<small class="has-text-grey">Uploaded {{.CreatedAt.Format "Jan _2 2006 15:04:05"}}</small>
</div>
<div class="mt-2">
{{if .Explicit}}
<span class="tag is-danger is-light">
<span class="icon"><i class="fa fa-fire"></i></span>
<span>Explicit</span>
</span>
{{end}}
{{if eq .Visibility "public"}}
<span class="tag is-info is-light">
<span class="icon"><i class="fa fa-eye"></i></span>
<span>
Public
</span>
</span>
{{else if eq .Visibility "friends"}}
<span class="tag is-warning is-light">
<span class="icon"><i class="fa fa-user-group"></i></span>
<span>
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>
<span>
Private
</span>
</span>
{{end}}
{{if .Gallery}}
<span class="tag is-success is-light">
<span class="icon"><i class="fa fa-image"></i></span>
<span>Gallery</span>
</span>
{{end}}
</div>
{{end}}
<!-- Reusable card footer -->
{{define "card-footer"}}
<a class="card-footer-item" href="/photo/edit?id={{.ID}}">
<span class="icon"><i class="fa fa-edit"></i></span>
<span>Edit</span>
</a>
<a class="card-footer-item has-text-danger" href="/photo/delete?id={{.ID}}">
<span class="icon"><i class="fa fa-trash"></i></span>
<span>Delete</span>
</a>
{{end}}
<!-- Main content template -->
{{define "content"}}
<div class="container">
<section class="hero is-info is-bold">
<div class="hero-body">
<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>
</div>
</div>
</section>
<!-- ugly hack.. needed by the card-footers later below. -->
{{$Root := .}}
<div class="block p-4">
<!-- Profile Tab for user view -->
{{if not .IsSiteGallery}}
<div class="tabs is-boxed">
<ul>
<li>
<a href="/u/{{.User.Username}}">
<span class="icon is-small">
<i class="fa fa-user"></i>
</span>
<span>Profile</span>
</a>
</li>
<li class="is-active">
<a>
<span class="icon is-small">
<i class="fa fa-image"></i>
</span>
<span>
Photos
{{if .PhotoCount}}<span class="tag is-link is-light ml-1">{{.PhotoCount}}</span>{{end}}
</span>
</a>
</li>
<li>
<a href="/notes/u/{{.User.Username}}">
<span class="icon is-small">
<i class="fa fa-pen-to-square"></i>
</span>
<span>
Notes
{{if .NoteCount}}<span class="tag is-link is-light ml-1">{{.NoteCount}}</span>{{end}}
</span>
</a>
</li>
<li>
<a href="/friends/u/{{.User.Username}}">
<span class="icon is-small">
<i class="fa fa-user-group"></i>
</span>
<span>
Friends
{{if .FriendCount}}<span class="tag is-link is-light ml-1">{{.FriendCount}}</span>{{end}}
</span>
</a>
</li>
</ul>
</div>
{{end}}
<!-- Photo Detail Modal -->
<div class="modal" id="detail-modal">
<div class="modal-background"></div>
<div class="modal-content photo-modal">
<div class="image is-fullwidth">
<img id="detailImg">
</div>
</div>
<button class="modal-close is-large" aria-label="close"></button>
</div>
<!-- Shy User alert banner (Site Gallery) -->
{{if and .IsSiteGallery .IsShyUser}}
<div class="notification is-danger is-light">
<i class="fa fa-exclamation-triangle"></i> You have a <strong>Shy Account</strong> so you will only see
pictures of you and your friends here. <a href="/faq#shy-faqs">Learn more <small class="fa fa-external-link"></small></a>
</div>
{{end}}
<!-- Shy User alert banner (User Gallery - IsShyFrom) -->
{{if .IsShyFrom}}
<div class="notification is-danger is-light">
<i class="fa fa-exclamation-triangle"></i> You have a <strong>Shy Account</strong> and you are not friends
with this person so can not see their gallery. <a href="/faq#shy-faqs">Learn more <small class="fa fa-external-link"></small></a>
</div>
{{end}}
<div class="block">
<div class="level{{if .IsOwnPhotos}}mb-0{{end}}">
<div class="level-left">
<div class="level-item">
{{if .Pager.Total}}
<span>
Found <strong>{{.Pager.Total}}</strong> photo{{Pluralize64 .Pager.Total}} (page {{.Pager.Page}} of {{.Pager.Pages}}).
{{if .ExplicitCount}}
{{.ExplicitCount}} explicit photo{{Pluralize64 .ExplicitCount}} hidden per your <a href="/settings#prefs">settings</a>.
{{end}}
</span>
{{end}}
</div>
</div>
<div class="level-right">
<div class="level-item">
<div class="tabs is-toggle is-small is-hidden-mobile">
<ul>
<li{{if eq .ViewStyle "cards"}} class="is-active"{{end}}>
<a href="{{.Request.URL.Path}}?view=cards">Cards</a>
</li>
<li{{if eq .ViewStyle "full"}} class="is-active"{{end}}>
<a href="{{.Request.URL.Path}}?view=full">Full</a>
</li>
</ul>
</div>
</div>
</div>
</div>
{{if .IsSiteGallery}}
<div class="block">
<form action="/photo/gallery" method="GET">
<div class="card nonshy-collapsible-mobile">
<header class="card-header has-background-link-light">
<p class="card-header-title">
Search Filters
</p>
<button class="card-header-icon" type="button">
<span class="icon">
<i class="fa fa-angle-up"></i>
</span>
</button>
</header>
<div class="card-content">
<div class="columns is-multiline mb-0">
{{if .CurrentUser.Explicit}}
<div class="column">
<div class="field">
<label class="label" for="explicit">Explicit:</label>
<div class="select is-fullwidth">
<select id="explicit" name="explicit">
<option value="">Show all</option>
<option value="true"{{if eq .FilterExplicit "true"}} selected{{end}}>Only explicit</option>
<option value="false"{{if eq .FilterExplicit "false"}} selected{{end}}>Hide explicit</option>
</select>
</div>
</div>
</div>
{{end}}
<div class="column">
<div class="field">
<label class="label" for="visibility">Visibility:</label>
<div class="select is-fullwidth">
<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>
</div>
</div>
</div>
<div class="column">
<div class="field">
<label class="label" for="sort">Sort by:</label>
<div class="select is-fullwidth">
<select id="sort" name="sort">
<option value="created_at desc"{{if eq .Sort "created_at desc"}} selected{{end}}>Most recent</option>
<option value="created_at asc"{{if eq .Sort "created_at asc"}} selected{{end}}>Oldest first</option>
</select>
</div>
</div>
</div>
{{if .CurrentUser.HasAdminScope "social.moderator.photo"}}
<div class="column">
<div class="field">
<label class="label has-text-danger" for="admin_view">Admin view:</label>
<div class="select is-fullwidth">
<select id="admin_view" name="admin_view">
<option value="">Default (disabled)</option>
<option value="true"{{if .AdminView}} selected{{end}}>Show all photos</option>
</select>
</div>
</div>
</div>
{{end}}
</div>
<div class="has-text-centered">
<a href="/photo/gallery" class="button">Reset</a>
<button type="submit" class="button is-success">
Apply Filters
</button>
</div>
</div>
</div>
</form>
</div>
{{end}}
{{if .IsOwnPhotos}}
<div class="block">
<a href="/photo/private" class="has-text-private">
<span class="icon"><i class="fa fa-lock"></i></span>
<span>Manage who can see <strong>my</strong> private photos</span>
</a>
</div>
{{else if not .IsSiteGallery}}
<div class="block">
{{if not .IsMyPrivateUnlockedFor}}
<a href="/photo/private/share?to={{.User.Username}}" class="has-text-private">
<span class="icon"><i class="fa fa-unlock"></i></span>
<span>Grant <strong>{{.User.Username}}</strong> access to see <strong>my</strong> private photos</span>
</a>
{{else}}
<span class="icon"><i class="fa fa-unlock has-text-private"></i></span>
<span>You had granted <strong>{{.User.Username}}</strong> access to see <strong>your</strong> private photos.</span>
<a href="/photo/private">Manage that here.</a>
{{end}}
</div>
{{end}}
{{if .AreWeGrantedPrivate}}
<div class="block mt-0">
<span class="icon"><i class="fa fa-eye has-text-private"></i></span>
<strong>{{.User.Username}}</strong> has <span class="has-text-private">granted</span> you
access to see their private photos.
</div>
{{end}}
<!-- Inner circle invitation -->
{{if not .IsSiteGallery}}
{{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) -->
{{if eq .ViewStyle "full"}}
{{range .Photos}}
<div class="card block">
<header class="card-header {{if .Explicit}}has-background-danger{{else}}has-background-link{{end}}">
<!-- Site Gallery header -->
{{if $Root.IsSiteGallery}}
<div class="card-header-title has-text-light">
{{if $Root.UserMap.Has .UserID}}
{{$Owner := $Root.UserMap.Get .UserID}}
<div class="columns is-mobile is-gapless nonshy-fullwidth">
<div class="column is-narrow mr-2">
{{template "avatar-24x24" $Owner}}
</div>
<div class="column">
<a href="/u/{{$Owner.Username}}" class="has-text-light">
{{$Owner.Username}}
<i class="fa fa-external-link ml-2"></i>
</a>
</div>
<div class="column is-narrow">
<span class="icon">
{{if eq .Visibility "friends"}}
<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}}
</span>
</div>
</div>
{{else}}
<span class="fa fa-user mr-2"></span>
<span>[deleted]</span>
{{end}}
</div>
{{else}}
<!-- User Gallery Full Header -->
<p class="card-header-title has-text-light">
<span class="icon">
<i class="fa fa-image"></i>
</span>
{{or .Caption "Photo"}}
</p>
{{end}}
</header>
<div class="card-image has-text-centered is-clipped">
<!-- GIF video? -->
{{if HasSuffix .Filename ".mp4"}}
<video loop controls
{{if BlurExplicit .}}class="blurred-explicit"
{{else if (not (eq ($Root.CurrentUser.GetProfileField "autoplay_gif") "false"))}}autoplay
{{end}}>
<source src="{{PhotoURL .Filename}}" type="video/mp4">
</video>
{{else}}
<img src="{{PhotoURL .Filename}}"{{if BlurExplicit .}} class="blurred-explicit"{{end}}>
{{end}}
</div>
<div class="card-content">
{{if .Caption}}
{{.Caption}}
{{else}}<em>No caption</em>{{end}}
{{template "card-body" .}}
<!-- Like & Comments buttons -->
{{if not $Root.AdminView}}
<div class="mt-4 columns is-centered is-mobile is-gapless">
<div class="column is-narrow mr-1">
{{$Like := $Root.LikeMap.Get .ID}}
<button type="button" class="button is-small nonshy-like-button"
data-table-name="photos" 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>
</button>
</div>
<div class="column is-narrow">
{{$Comments := $Root.CommentMap.Get .ID}}
<a href="/photo/view?id={{.ID}}#comments" class="button is-small">
<span class="icon"><i class="fa fa-comment"></i></span>
<span>{{$Comments}} Comment{{Pluralize64 $Comments}}</span>
</a>
</div>
</div>
{{end}}
</div>
<footer class="card-footer">
{{if or $Root.IsOwnPhotos $Root.CurrentUser.IsAdmin}}
{{template "card-footer" .}}
{{end}}
{{if not $Root.IsOwnPhotos}}
<a class="card-footer-item has-text-danger" href="/contact?intent=report&subject=report.photo&id={{.ID}}">
<span class="icon"><i class="fa fa-flag"></i></span>
<span>Report</span>
</a>
{{end}}
</footer>
</div>
{{end}}
<!-- "Cards" style (default) -->
{{else}}
<div class="columns is-multiline">
{{range .Photos}}
<div class="column is-one-quarter-desktop is-half-tablet">
<div class="card">
<!-- Header only on Site Gallery version -->
{{if $Root.IsSiteGallery}}
<header class="card-header {{if .Explicit}}has-background-danger{{else}}has-background-link{{end}}">
<div class="card-header-title has-text-light">
{{if $Root.UserMap.Has .UserID}}
{{$Owner := $Root.UserMap.Get .UserID}}
<div class="columns is-mobile is-gapless nonshy-fullwidth">
<div class="column is-narrow mr-2">
{{template "avatar-24x24" $Owner}}
</div>
<div class="column">
<a href="/u/{{$Owner.Username}}" class="has-text-light">
{{$Owner.Username}}
<i class="fa fa-external-link ml-2"></i>
</a>
</div>
<div class="column is-narrow">
<span class="icon">
{{if eq .Visibility "friends"}}
<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}}
</span>
</div>
</div>
{{else}}
<span class="fa fa-user mr-2"></span>
<span>[deleted]</span>
{{end}}
</div>
</header>
{{end}}
<div class="card-image has-text-centered is-clipped">
<!-- GIF video? -->
{{if HasSuffix .Filename ".mp4"}}
<video loop controls
{{if BlurExplicit .}}class="blurred-explicit"
{{else if (not (eq ($Root.CurrentUser.GetProfileField "autoplay_gif") "false"))}}autoplay
{{end}}>
<source src="{{PhotoURL .Filename}}" type="video/mp4">
</video>
{{else}}
<a href="{{PhotoURL .Filename}}" target="_blank"
class="js-modal-trigger" data-target="detail-modal"
onclick="setModalImage(this.href)">
<img src="{{PhotoURL .Filename}}"{{if BlurExplicit .}} class="blurred-explicit"{{end}}>
</a>
{{end}}
</div>
<div class="card-content">
{{if .Caption}}
{{.Caption}}
{{else}}<em>No caption</em>{{end}}
{{template "card-body" .}}
<!-- Like & Comments buttons -->
{{if not $Root.AdminView}}
<div class="mt-4 columns is-centered is-mobile is-gapless">
<div class="column is-narrow mr-1">
{{$Like := $Root.LikeMap.Get .ID}}
<button type="button" class="button is-small nonshy-like-button"
data-table-name="photos" 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>
</button>
</div>
<div class="column is-narrow">
{{$Comments := $Root.CommentMap.Get .ID}}
<a href="/photo/view?id={{.ID}}#comments" class="button is-small">
<span class="icon"><i class="fa fa-comment"></i></span>
<span>{{$Comments}} Comment{{Pluralize64 $Comments}}</span>
</a>
</div>
</div>
{{end}}
</div>
<footer class="card-footer">
{{if or $Root.IsOwnPhotos $Root.CurrentUser.IsAdmin}}
{{template "card-footer" .}}
{{end}}
{{if not $Root.IsOwnPhotos}}
<a class="card-footer-item has-text-danger" href="/contact?intent=report&subject=report.photo&id={{.ID}}">
<span class="icon"><i class="fa fa-flag"></i></span>
<span class="is-hidden-desktop">Report</span>
</a>
{{end}}
</footer>
</div>
</div>
{{end}}
</div>
{{end}}<!-- ViewStyle -->
{{SimplePager .Pager}}
</div>
</div>
</div>
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", () => {
// Get our modal to trigger it on click of a detail img.
let $modal = document.querySelector("#detail-modal");
document.querySelectorAll(".js-modal-trigger").forEach(node => {
node.addEventListener("click", (e) => {
e.preventDefault();
setModalImage(node.href);
$modal.classList.add("is-active");
})
});
});
function setModalImage(url) {
let $modalImg = document.querySelector("#detailImg");
$modalImg.src = url;
return false;
}
</script>
{{end}}