Noah
8085e092bc
* Enhance user experience replying to a forum thread. An inline reply textarea is added to page footers, "Quote" buttons on posts will quote the markdown source and focus the reply textarea, and "Reply" buttons will put an "@ mention" and focus the reply textarea. Users with scripts disabled will still be sent to the regular reply page as before. * Improve all pagers by adding a "QueryPlus" template function that merges the page number with other current query parameters. * Fix private profile picture avatars not displaying in your Notifications for profile pics you're allowed to see.
545 lines
25 KiB
HTML
545 lines
25 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}}
|
|
<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}}
|
|
|
|
<!-- Reusable pager -->
|
|
{{define "pager"}}
|
|
<nav class="pagination" role="navigation" aria-label="pagination">
|
|
<a class="pagination-previous{{if not .Pager.HasPrevious}} is-disabled{{end}}" title="Previous"
|
|
href="{{.Request.URL.Path}}?{{QueryPlus "page" .Pager.Previous}}">Previous</a>
|
|
<a class="pagination-next{{if not .Pager.HasNext}} is-disabled{{end}}" title="Next"
|
|
href="{{.Request.URL.Path}}?{{QueryPlus "page" .Pager.Next}}">Next page</a>
|
|
<ul class="pagination-list">
|
|
{{$Root := .}}
|
|
{{range .Pager.Iter}}
|
|
<li>
|
|
<a class="pagination-link{{if .IsCurrent}} is-current{{end}}"
|
|
aria-label="Page {{.Page}}"
|
|
href="{{$Root.Request.URL.Path}}?{{QueryPlus "page" .Page}}">
|
|
{{.Page}}
|
|
</a>
|
|
</li>
|
|
{{end}}
|
|
</ul>
|
|
</nav>
|
|
{{end}}
|
|
|
|
<!-- Main content template -->
|
|
{{define "content"}}
|
|
<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>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
</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>
|
|
</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>
|
|
|
|
<div class="block">
|
|
<div class="level{{if .IsOwnPhotos}}mb-0{{end}}">
|
|
<div class="level-left">
|
|
<div class="level-item">
|
|
<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 settings.
|
|
{{end}}
|
|
</span>
|
|
</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 <span class="tag is-success ml-2">NEW</span>
|
|
</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>
|
|
<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.IsAdmin}}
|
|
<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>
|
|
<span class="tag is-success">NEW</span>
|
|
</a>
|
|
</div>
|
|
{{else if and (not .IsSiteGallery) (not .IsMyPrivateUnlockedFor)}}
|
|
<div class="block">
|
|
<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>
|
|
<span class="tag is-success">NEW</span>
|
|
</a>
|
|
</div>
|
|
{{else if and (not .IsSiteGallery) .IsMyPrivateUnlockedFor}}
|
|
<div class="block">
|
|
<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>
|
|
<span class="tag is-success">NEW</span>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{template "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">
|
|
<figure class="image">
|
|
<img src="{{PhotoURL .Filename}}">
|
|
</figure>
|
|
</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}}
|
|
<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">
|
|
<figure class="image">
|
|
<a href="{{PhotoURL .Filename}}" target="_blank"
|
|
class="js-modal-trigger" data-target="detail-modal"
|
|
onclick="setModalImage(this.href)">
|
|
<img src="{{PhotoURL .Filename}}">
|
|
</a>
|
|
</figure>
|
|
</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 -->
|
|
|
|
{{template "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}} |