f2e847922f
* Profile pictures on profile pages now link to the gallery when clicked. * Admins can no longer automatically see the default profile pic on profile pages unless they have photo moderator ability. * Photo view counts are not added when an admin with photo moderator ability should not have otherwise been able to see the photo.
850 lines
42 KiB
HTML
850 lines
42 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>
|
|
{{if .Views}}
|
|
<small class="has-text-grey is-size-7 ml-2">
|
|
<i class="fa fa-eye"></i>
|
|
{{.Views}}
|
|
</small>
|
|
{{end}}
|
|
</div>
|
|
<div class="mt-2">
|
|
{{if .Pinned}}
|
|
<span class="tag is-success is-light">
|
|
<span class="icon"><i class="fa fa-thumbtack"></i></span>
|
|
<span>Pinned</span>
|
|
</span>
|
|
{{end}}
|
|
|
|
{{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}}
|
|
<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"}}
|
|
{{if not .IsSiteGallery}}
|
|
<style type="text/css">
|
|
{{template "profile-theme-hero-style" .User}}
|
|
</style>
|
|
{{end}}
|
|
<div class="container">
|
|
<section class="hero is-link 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="/u/{{.User.Username}}/notes">
|
|
<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="/u/{{.User.Username}}/friends">
|
|
<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">
|
|
<!-- Notes: to get the image to always scale and fit on screen, it is made as a background image in CSS
|
|
on the detailImg div; but we don't have the image's minimum size here, so the hidden <img> inside
|
|
provides the size pushing to make it visible on screen, otherwise the divs are 0x0 pixels and nothing
|
|
would be visible. -->
|
|
<div id="detailImg">
|
|
<img style="visibility: hidden">
|
|
|
|
<!-- Alt Text button for accessibility -->
|
|
<button class="button is-small alt-text py-1 px-2">
|
|
<strong>ALT</strong>
|
|
</button>
|
|
</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}}
|
|
|
|
<!-- Notice if the current user can not see the user's default profile picture -->
|
|
{{if .ProfilePictureHiddenVisibility}}
|
|
<div class="block">
|
|
<i class="fa fa-info-circle mr-1"></i>
|
|
<strong>Notice:</strong>
|
|
@{{.User.Username}}'s default profile picture is set to
|
|
{{if eq .ProfilePictureHiddenVisibility "friends"}}
|
|
<img src="/static/img/shy-friends.png" width="16" height="16">
|
|
<strong class="has-text-warning">Friends only</strong>
|
|
{{else}}
|
|
<img src="/static/img/shy-private.png" width="16" height="16">
|
|
<strong class="has-text-private">Private</strong>
|
|
{{end}}
|
|
visibility and can not be seen by you.
|
|
<a href="/faq#private-avatar" target="_blank">Learn more <i class="fa fa-external-link"></i></a>
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="block">
|
|
<div class="level mb-2">
|
|
<div class="level-left">
|
|
<div class="level-item">
|
|
{{if .Pager.Total}}
|
|
<span>
|
|
Found <strong>{{FormatNumberCommas .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>
|
|
{{else if .ExplicitCount}}
|
|
<!-- No pager, but still show explicit hint, e.g. in case user filters by Private but all privates are explicit -->
|
|
<span>
|
|
{{.ExplicitCount}} explicit photo{{Pluralize64 .ExplicitCount}} hidden per your <a href="/settings#prefs">settings</a>.
|
|
</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}}?{{QueryPlus "view" "cards"}}">Cards</a>
|
|
</li>
|
|
<li{{if eq .ViewStyle "full"}} class="is-active"{{end}}>
|
|
<a href="{{.Request.URL.Path}}?{{QueryPlus "view" "full"}}">Full</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Show an "Unsubscribe to this user's new photo notifications" if you are Friends. -->
|
|
{{if .AreFriends}}
|
|
<p class="block">
|
|
<a href="/comments/subscription?table_name=friend.photos&table_id={{.User.Username}}&next={{UrlEncode .Request.URL.String}}&subscribe={{if .AreNotificationsMuted}}true{{else}}false{{end}}"
|
|
class="{{if .AreNotificationsMuted}}has-text-success{{else}}{{end}}">
|
|
<span class="icon"><i class="fa fa-bell{{if not .AreNotificationsMuted}}-slash{{end}}"></i></span>
|
|
<span>
|
|
{{if .AreNotificationsMuted}}
|
|
Enable notifications about <strong>{{.User.Username}}</strong>'s new photos
|
|
{{else}}
|
|
Mute notifications about <strong>{{.User.Username}}</strong>'s new photos
|
|
{{end}}
|
|
</span>
|
|
</a>
|
|
</p>
|
|
{{end}}
|
|
|
|
<!-- If viewing our own profile, and we don't have a profile picture set, offer advice. -->
|
|
{{if and (not .IsSiteGallery) (eq .CurrentUser.ProfilePhoto.ID 0) (eq .CurrentUser.ID .User.ID)}}
|
|
<div class="notification is-success is-light content">
|
|
<p>
|
|
<i class="fa-regular fa-id-badge mr-1"></i>
|
|
<strong>Your default profile picture is not set</strong>
|
|
|
|
<p>
|
|
Your default profile picture is currently not set to anything, and appears to other members as
|
|
the default blue <img src="/static/img/shy.png" width="16" height="16"> placeholder image.
|
|
</p>
|
|
|
|
<ul>
|
|
<li>
|
|
To upload a <strong>new</strong> profile picture, <a href="/photo/upload?intent=profile_pic">click here</a>.
|
|
</li>
|
|
<li>
|
|
To use one of your <strong>existing</strong> photos as your profile picture:
|
|
<ol class="my-2">
|
|
<li>
|
|
Click on the "Edit" button beneath one of your photos below.
|
|
</li>
|
|
<li>
|
|
On the edit page, below the picture, click on the button to "Set this as my profile photo (crop image)"
|
|
and select the square shape you want for your profile pic.
|
|
</li>
|
|
<li>
|
|
Click on "Save Changes" when done!
|
|
</li>
|
|
</ol>
|
|
</li>
|
|
</ul>
|
|
|
|
<p>
|
|
Having a profile picture set, along with an approved <a href="/photo/certification">certification photo</a>,
|
|
is required to access the social features on {{PrettyTitle}} such as the chat room, forums and member directory.
|
|
</p>
|
|
</p>
|
|
</div>
|
|
{{end}}
|
|
|
|
<!-- Indicator if friends-only is selected. -->
|
|
{{if eq .FilterWho "friends"}}
|
|
<div class="notification is-success is-light">
|
|
Showing you all recent photos from <strong>yourself & your friends.</strong>
|
|
<a href="{{.Request.URL.Path}}?who=everybody">See all certified members' gallery photos?</a>
|
|
</div>
|
|
{{else if eq .FilterWho "friends+private"}}
|
|
<div class="notification is-success is-light">
|
|
Showing you all recent photos from <strong>yourself & your friends</strong> as well
|
|
as any <strong>private photos shared with you</strong> by others on the site (if they are
|
|
marked to appear in the Site Gallery).
|
|
</div>
|
|
{{else if eq .FilterWho "likes"}}
|
|
<div class="notification is-success is-light">
|
|
Showing you photos that you have <i class="fa fa-heart"></i> <strong>Liked.</strong>
|
|
</div>
|
|
{{end}}
|
|
|
|
<!-- Filters -->
|
|
<div class="block">
|
|
<form action="{{.Request.URL.Path}}" method="GET">
|
|
|
|
<div class="card nonshy-collapsible-mobile">
|
|
<header class="card-header has-background-link-light">
|
|
<p class="card-header-title has-text-dark">
|
|
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">
|
|
|
|
<!-- Site Gallery: friends-only filter -->
|
|
{{if .IsSiteGallery}}
|
|
<div class="column">
|
|
<div class="field">
|
|
<label class="label" for="who">Whose photos:</label>
|
|
<div class="select is-fullwidth">
|
|
<select id="who" name="who">
|
|
<option value="friends"{{if eq .FilterWho "friends"}} selected{{end}}>Myself & friends only</option>
|
|
<option value="friends+private"{{if eq .FilterWho "friends+private"}} selected{{end}}>Myself, friends, & private photo grants</option>
|
|
<option value="likes"{{if eq .FilterWho "likes"}} selected{{end}}>Photos I have 'liked'</option>
|
|
<option value="everybody"{{if eq .FilterWho "everybody"}} selected{{end}}>All certified members</option>
|
|
{{if .CurrentUser.HasAdminScope "social.moderator.photo"}}
|
|
<option value="uncertified"{{if eq .FilterWho "uncertified"}} selected{{end}}>☮ Non-certified members</option>
|
|
{{end}}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if or .CurrentUser.Explicit .IsOwnPhotos}}
|
|
<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>
|
|
|
|
<!-- Friends & Private: always show on Site Gallery, show if available on User Gallery -->
|
|
{{if or .IsSiteGallery .AreFriends .IsOwnPhotos}}
|
|
<option value="friends"{{if eq .FilterVisibility "friends"}} selected{{end}}>Friends only</option>
|
|
{{end}}
|
|
{{if or .IsSiteGallery .AreWeGrantedPrivate .IsOwnPhotos}}
|
|
<option value="private"{{if eq .FilterVisibility "private"}} selected{{end}}>Private only</option>
|
|
{{end}}
|
|
</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">
|
|
{{if not .IsSiteGallery}}
|
|
<option value="pinned desc nulls last, updated_at desc"{{if eq .Sort "pinned desc nulls last, updated_at desc"}} selected{{end}}>
|
|
Pinned, recently updated
|
|
</option>
|
|
{{end}}
|
|
<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>
|
|
<option value="like_count desc"{{if eq .Sort "like_count desc"}} selected{{end}}>Most likes</option>
|
|
<option value="comment_count desc"{{if eq .Sort "comment_count desc"}} selected{{end}}>Most comments</option>
|
|
<option value="views desc"{{if eq .Sort "views desc"}} selected{{end}}>Most views</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{if and .IsSiteGallery (.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="{{.Request.URL.Path}}" class="button">Reset</a>
|
|
<button type="submit" class="button is-success">
|
|
Apply Filters
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Retain cards vs. full parameter -->
|
|
<input type="hidden" name="view" value="{{.ViewStyle}}">
|
|
|
|
</form>
|
|
</div>
|
|
|
|
{{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}}
|
|
|
|
{{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}}
|
|
<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 controlsList="nodownload" playsinline
|
|
class="js-modal-trigger"
|
|
data-url="{{PhotoURL .Filename}}" data-photo-id="{{.ID}}"
|
|
{{if .AltText}}title="{{.AltText}}"{{end}}
|
|
{{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="/photo/view?id={{.ID}}" data-url="{{PhotoURL .Filename}}" data-photo-id="{{.ID}}" target="_blank"
|
|
class="js-modal-trigger">
|
|
<img src="{{PhotoURL .Filename}}" loading="lazy"
|
|
{{if BlurExplicit .}}class="blurred-explicit"{{end}}
|
|
{{if .AltText}}alt="{{.AltText}}" title="{{.AltText}}"{{end}}>
|
|
</a>
|
|
{{end}}
|
|
</div>
|
|
|
|
<div class="card-content">
|
|
{{if .Caption}}
|
|
{{.Caption}}
|
|
{{else}}<em>No caption</em>{{end}}
|
|
|
|
{{template "card-body" .}}
|
|
|
|
<!-- Quick mark photo as explicit -->
|
|
{{if and (not .Explicit) (ne .UserID $Root.CurrentUser.ID)}}
|
|
<div class="mt-2">
|
|
<a href="#"
|
|
class="has-text-danger is-size-7 nonshy-mark-explicit"
|
|
data-photo-id="{{.ID}}" data-photo-url="{{PhotoURL .Filename}}">
|
|
<i class="fa fa-fire mr-1"></i>
|
|
Should this photo be marked Explicit?
|
|
</a>
|
|
</div>
|
|
{{end}}
|
|
|
|
<!-- 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.HasAdminScope "social.moderator.photo")}}
|
|
{{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}}
|
|
<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 controlsList="nodownload" playsinline
|
|
class="js-modal-trigger"
|
|
data-url="{{PhotoURL .Filename}}" data-photo-id="{{.ID}}"
|
|
{{if .AltText}}title="{{.AltText}}"{{end}}
|
|
{{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="/photo/view?id={{.ID}}" data-url="{{PhotoURL .Filename}}" data-photo-id="{{.ID}}" target="_blank"
|
|
class="js-modal-trigger">
|
|
<img src="{{PhotoURL .Filename}}" loading="lazy"
|
|
{{if BlurExplicit .}}class="blurred-explicit"{{end}}
|
|
{{if .AltText}}alt="{{.AltText}}" title="{{.AltText}}"{{end}}>
|
|
</a>
|
|
{{end}}
|
|
</div>
|
|
<div class="card-content">
|
|
{{if .Caption}}
|
|
{{.Caption}}
|
|
{{else}}<em>No caption</em>{{end}}
|
|
|
|
{{template "card-body" .}}
|
|
|
|
<!-- Quick mark photo as explicit -->
|
|
{{if and (not .Explicit) (ne .UserID $Root.CurrentUser.ID)}}
|
|
<div class="mt-2">
|
|
<a href="#"
|
|
class="has-text-danger is-size-7 nonshy-mark-explicit"
|
|
data-photo-id="{{.ID}}" data-photo-url="{{PhotoURL .Filename}}">
|
|
<i class="fa fa-fire mr-1"></i>
|
|
Should this photo be marked Explicit?
|
|
</a>
|
|
</div>
|
|
{{end}}
|
|
|
|
<!-- 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.HasAdminScope "social.moderator.photo")}}
|
|
{{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}}
|
|
|
|
<!-- Admin change log link -->
|
|
{{if .CurrentUser.HasAdminScope "admin.changelog"}}
|
|
<div class="block">
|
|
<a href="/admin/changelog?table_name=photos{{if .User}}&about_user_id={{.User.ID}}{{end}}" class="button is-small has-text-warning">
|
|
<span class="icon"><i class="fa fa-peace mr-1"></i></span>
|
|
<span>{{if .User}}User{{else}}Site{{end}} Gallery change log</span>
|
|
</a>
|
|
</div>
|
|
{{end}}
|
|
|
|
</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"),
|
|
$altText = $modal.getElementsByTagName("button")[0];
|
|
|
|
function setModalImage(url, altText) {
|
|
let $modalImg = document.querySelector("#detailImg"),
|
|
$img = $modalImg.getElementsByTagName("img")[0];
|
|
$img.src = url;
|
|
$modalImg.style.backgroundImage = `url(${url})`;
|
|
|
|
// Alt text?
|
|
$modalImg.title = altText;
|
|
$altText.style.display = altText ? "block" : "none";
|
|
$altText.onclick = (e) => {
|
|
window.alert(altText);
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function markImageViewed(photoID) {
|
|
fetch(`/v1/photo/${photoID}/view`, {
|
|
method: "POST",
|
|
mode: "same-origin",
|
|
cache: "no-cache",
|
|
credentials: "same-origin",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
}).then(response => response.json())
|
|
.then(data => {
|
|
if (data.StatusCode !== 200) {
|
|
console.error("When marking photo %d as viewed: status code %d: %s", photoID, data.StatusCode, data.data.error);
|
|
return;
|
|
}
|
|
}).catch(window.alert);
|
|
}
|
|
|
|
document.querySelectorAll(".js-modal-trigger").forEach(node => {
|
|
let $img = node.getElementsByTagName("img"),
|
|
$video = node.tagName === 'VIDEO' ? node : null,
|
|
photoID = node.dataset.photoId,
|
|
altText = $img[0] != undefined ? $img[0].alt : '';
|
|
|
|
// Video (animated GIF) handlers.
|
|
if ($video !== null) {
|
|
|
|
// Log this video viewed if the user interacts with it in any way.
|
|
// Note: because videos don't open in the lightbox modal.
|
|
['pause', 'mouseover'].forEach(event => {
|
|
$video.addEventListener(event, (e) => {
|
|
// Log a view of this video.
|
|
markImageViewed(photoID);
|
|
});
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
// Images: open in the lightbox modal.
|
|
node.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
setModalImage(node.dataset.url, altText);
|
|
$modal.classList.add("is-active");
|
|
|
|
// Log a view of this photo.
|
|
markImageViewed(photoID);
|
|
})
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<!-- Mark Explicit modal -->
|
|
{{template "mark-explicit-modal" .}}
|
|
{{end}}
|