617 lines
31 KiB
HTML
617 lines
31 KiB
HTML
{{define "title"}}Upload a Photo{{end}}
|
|
{{define "content"}}
|
|
<div class="container">
|
|
<section class="hero is-info is-bold">
|
|
<div class="hero-body">
|
|
<div class="container">
|
|
<h1 class="title">
|
|
{{if .EditPhoto}}
|
|
Edit Photo
|
|
{{else if eq .Intent "profile_pic"}}
|
|
Upload a Profile Picture
|
|
{{else}}
|
|
Upload a Photo
|
|
{{end}}
|
|
</h1>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{{ $User := .CurrentUser }}
|
|
|
|
<!-- Drag/Drop Modal -->
|
|
<div class="modal" id="drop-modal">
|
|
<div class="modal-background"></div>
|
|
<div class="modal-content">
|
|
<div class="box content has-text-centered">
|
|
<h1><i class="fa fa-upload mr-2"></i> Drop image to select it for upload</h1>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{if .EditPhoto}}
|
|
<form action="/photo/edit" method="POST">
|
|
<input type="hidden" name="id" value="{{.EditPhoto.ID}}">
|
|
{{else}}
|
|
<form action="/photo/upload" method="POST" enctype="multipart/form-data">
|
|
{{end}}
|
|
{{InputCSRF}}
|
|
<input type="hidden" id="intent" name="intent" value="{{.Intent}}">
|
|
|
|
<div class="block p-4">
|
|
<!-- Upload disclaimers, but not if editing a photo -->
|
|
{{if not .EditPhoto}}
|
|
<div class="content block">
|
|
<p>
|
|
You can use this page to upload a new photo to your profile. Please remember
|
|
the rules below:
|
|
</p>
|
|
|
|
<ul>
|
|
<li>
|
|
🤳 <strong>Self pictures only:</strong> you may only upload pictures which depict
|
|
<em>you</em> in them. If the picture also contains other people, be sure you
|
|
have their consent to post it here!
|
|
</li>
|
|
<li>
|
|
🔞 <strong>Mark whether your picture is explicit:</strong> not all nudists want to
|
|
see sexual content or close-up shots of genitalia. If your picture is not a
|
|
"normal nude" please check the Explicit box to help the rest of us out!
|
|
</li>
|
|
<li>
|
|
🧑 <strong>Your main profile picture must show your face:</strong> it doesn't have
|
|
to be a nude pic but your face needs to be in it. Additional photos uploaded to
|
|
your page do not need to require your face in them.
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
{{end}}
|
|
|
|
<!-- Quota notification -->
|
|
{{if not .EditPhoto}}
|
|
<div class="notification {{if ge .PhotoCount .PhotoQuota}}is-warning{{else}}is-info{{end}} block">
|
|
<p class="block">
|
|
You have currently uploaded <strong>{{.PhotoCount}}</strong> of your allowed {{.PhotoQuota}} photos.
|
|
{{if ge .PhotoCount .PhotoQuota}}
|
|
To upload a new photo, please <a href="/photo/u/{{.CurrentUser.Username}}">delete</a>
|
|
an existing photo first to make room.
|
|
{{end}}
|
|
</p>
|
|
|
|
<p class="block">
|
|
You may upload <strong>{{SubtractInt .PhotoQuota .PhotoCount}}</strong> more photo{{Pluralize (SubtractInt .PhotoQuota .PhotoCount)}}.
|
|
{{if not .CurrentUser.Certified}}
|
|
After your account has been <a href="/photo/certification">certified</a>, you will be able to upload
|
|
additional pictures.
|
|
{{end}}
|
|
</p>
|
|
</div>
|
|
{{end}}
|
|
|
|
<!-- No profile picture and they aren't uploading one now? -->
|
|
{{if .NoProfilePicture}}
|
|
<div class="notification is-warning block">
|
|
<p class="block">
|
|
<strong>Notice:</strong> you currently do not have a Default Profile Picture set up on your
|
|
account. You can <a href="/photo/upload?intent=profile_pic">click here to upload a new one</a>
|
|
or click the Edit button on <a href="/photo/u/{{.CurrentUser.Username}}">one of your existing photos</a>
|
|
to crop a square profile picture from one of them.
|
|
</p>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{if or .EditPhoto (lt .PhotoCount .PhotoQuota)}}
|
|
<div class="columns">
|
|
<div class="column">
|
|
|
|
<div class="card">
|
|
<header class="card-header has-background-link">
|
|
<p class="card-header-title has-text-light">
|
|
<i class="fa fa-camera pr-2"></i>
|
|
{{if .EditPhoto}}
|
|
Your Photo
|
|
{{else}}
|
|
Select a Photo
|
|
{{end}}
|
|
</p>
|
|
</header>
|
|
|
|
<!-- Upload field, not when editing -->
|
|
{{if not .EditPhoto}}
|
|
<div class="card-content">
|
|
<p class="block">
|
|
Browse or drag a photo onto this page:
|
|
</p>
|
|
|
|
<div class="field block">
|
|
<div class="file has-name is-fullwidth">
|
|
<label class="file-label">
|
|
<input class="file-input" type="file"
|
|
name="file"
|
|
id="file"
|
|
accept=".jpg,.jpeg,.jpe,.png{{if ne .Intent "profile_pic"}},.gif{{end}}"
|
|
required>
|
|
<span class="file-cta">
|
|
<span class="file-icon">
|
|
<i class="fas fa-upload"></i>
|
|
</span>
|
|
<span class="file-label">
|
|
Choose a file…
|
|
</span>
|
|
</span>
|
|
<span class="file-name" id="fileName">
|
|
Select a file
|
|
</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="box" id="imagePreview" style="display: none">
|
|
<h3 class="subtitle">
|
|
{{if .NeedsCrop}}Crop image:{{else}}Selected image:{{end}}
|
|
</h3>
|
|
|
|
{{if .NeedsCrop}}
|
|
<p class="block">
|
|
Select a square crop of this image for your profile picture. The full
|
|
image will go among the rest of your photos, and the square version
|
|
will be used as your profile pic and avatar.
|
|
</p>
|
|
{{end}}
|
|
|
|
<!-- Container of img tags for the selected photo preview. -->
|
|
<div id="previewBox" class="block"></div>
|
|
|
|
{{if .NeedsCrop}}
|
|
<div class="block has-text-centered">
|
|
<button type="button" class="button block is-info" onclick="resetCrop()">Reset</button>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
<!-- Holder of image crop coordinates in x,y,w,h format. -->
|
|
<input type="hidden" name="crop" id="cropCoords">
|
|
</div>
|
|
{{else}}<!-- when .EditPhoto -->
|
|
<div class="card-content">
|
|
<figure id="editphoto-preview" class="image block">
|
|
{{if HasSuffix .EditPhoto.Filename ".mp4"}}
|
|
<video autoplay loop controls>
|
|
<source src="{{PhotoURL .EditPhoto.Filename}}" type="video/mp4">
|
|
</video>
|
|
{{else}}
|
|
<img src="{{PhotoURL .EditPhoto.Filename}}" id="editphoto-img">
|
|
{{end}}
|
|
</figure>
|
|
|
|
<!-- Re-crop as profile picture: not when it's a GIF -->
|
|
{{if not (HasSuffix .EditPhoto.Filename ".mp4")}}
|
|
<div class="block has-text-centered" id="editphoto-begin-crop">
|
|
<button type="button" class="button" onclick="setProfilePhoto()">
|
|
<span class="icon"><i class="fa fa-crop-simple"></i></span>
|
|
<span>Set this as my profile photo (crop image)</span>
|
|
</button>
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="block has-text-centered" id="editphoto-cropping" style="display: none">
|
|
<button type="button" class="button block is-info" onclick="resetCrop()">Reset</button>
|
|
</div>
|
|
|
|
<!-- Holder of image crop coordinates in x,y,w,h format. -->
|
|
<input type="hidden" name="crop" id="cropCoords">
|
|
</div>
|
|
{{end}}
|
|
|
|
</div><!-- /card -->
|
|
</div><!-- /column -->
|
|
|
|
<div class="column">
|
|
|
|
<div class="card">
|
|
<header class="card-header has-background-link">
|
|
<p class="card-header-title has-text-light">
|
|
<i class="fa fa-pencil pr-2"></i>
|
|
Photo Settings
|
|
</p>
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
|
|
<div class="field mb-5">
|
|
<label class="label" for="caption">Caption</label>
|
|
<input type="text" class="input"
|
|
name="caption"
|
|
id="caption"
|
|
placeholder="Caption"
|
|
value="{{.EditPhoto.Caption}}">
|
|
</div>
|
|
|
|
<div class="field mb-5">
|
|
<label class="label">Photo Visibility</label>
|
|
<div>
|
|
<label class="radio">
|
|
<input type="radio"
|
|
name="visibility"
|
|
value="public"
|
|
{{if or (not .EditPhoto) (eq .EditPhoto.Visibility "public")}}checked{{end}}>
|
|
<strong class="has-text-link ml-1">
|
|
<span>Public <small>(members only)</small></span>
|
|
<span class="icon"><i class="fa fa-eye"></i></span>
|
|
</strong>
|
|
</label>
|
|
<p class="help">
|
|
This photo will appear on your profile page and can be seen by any
|
|
logged-in user account. It may also appear on the site-wide Photo
|
|
Gallery if that option is enabled, below.
|
|
</p>
|
|
</div>
|
|
{{if .CurrentUser.IsInnerCircle}}
|
|
<div>
|
|
<label class="radio">
|
|
<input type="radio"
|
|
name="visibility"
|
|
value="circle"
|
|
{{if eq .EditPhoto.Visibility "circle"}}checked{{end}}>
|
|
<strong class="has-text-link ml-1">
|
|
<span>{{PrettyCircle}}</span>
|
|
<span class="icon">
|
|
<img src="/static/img/circle-16.png">
|
|
</span>
|
|
</strong>
|
|
</label>
|
|
<p class="help">
|
|
Only members of the <a href="/inner-circle">inner circle</a> will see this photo. This is
|
|
like the "Public" visibility except only people in the inner circle will see it on your
|
|
profile page or on the Site Gallery (if that option is enabled, below).
|
|
</p>
|
|
</div>
|
|
{{end}}
|
|
<div>
|
|
<label class="radio">
|
|
<input type="radio"
|
|
name="visibility"
|
|
value="friends"
|
|
{{if eq .EditPhoto.Visibility "friends"}}checked{{end}}>
|
|
<strong class="has-text-warning-dark ml-1">
|
|
<span>Friends only</span>
|
|
<span class="icon"><i class="fa fa-user-group"></i></span>
|
|
</strong>
|
|
</label>
|
|
<p class="help">
|
|
Only users you have accepted as a friend can see this photo on your
|
|
profile page and on the site-wide Photo Gallery if that option is
|
|
enabled, below.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<label class="radio">
|
|
<input type="radio"
|
|
name="visibility"
|
|
value="private"
|
|
{{if eq .EditPhoto.Visibility "private"}}checked{{end}}>
|
|
<strong class="has-text-private ml-1">
|
|
<span>Private</span>
|
|
<span class="icon"><i class="fa fa-lock"></i></span>
|
|
</strong>
|
|
</label>
|
|
<p class="help">
|
|
This photo is visible only to you and to users for whom you have
|
|
granted access
|
|
(<a href="/photo/private" target="_blank" class="has-text-private">manage grants <i class="fa fa-external-link"></i></a>).
|
|
</p>
|
|
</div>
|
|
|
|
<div class="has-text-warning-dark is-size-7 mt-4">
|
|
<i class="fa fa-info-circle mr-1"></i>
|
|
<strong class="has-text-warning-dark">Reminder:</strong> There are risks inherent with sharing
|
|
pictures on the Internet, and {{PrettyTitle}} can't guarantee that another member of the site
|
|
won't download and possibly redistribute your photos. You may mark your picture as "Friends only"
|
|
or "Private" to limit who on the website will see it, but anybody who <em>can</em> see it could potentially
|
|
save it to their computer. <a href="/faq#downloading" target="_blank">Learn more <i class="fa fa-external-link"></i></a>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="field mb-5">
|
|
<label class="label">
|
|
<span>Site Photo Gallery</span>
|
|
<span class="icon"><i class="fa fa-image"></i></span>
|
|
</label>
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="gallery"
|
|
value="true"
|
|
{{if and (or .EditPhoto.Gallery (not .EditPhoto)) (not .SiteGalleryThrottled)}}checked{{end}}
|
|
{{if .SiteGalleryThrottled}}disabled{{end}}>
|
|
{{if .SiteGalleryThrottled}}<del>{{end}}
|
|
Show this photo in the site-wide Photo Gallery
|
|
{{if .SiteGalleryThrottled}}</del>{{end}}
|
|
</label>
|
|
|
|
{{if .SiteGalleryThrottled}}
|
|
<p class="help has-text-warning-dark">
|
|
<i class="fa fa-exclamation-triangle"></i>
|
|
You have shared too many photos with the Site Gallery recently!<br><br>
|
|
We currently limit members to featuring <strong>{{.SiteGalleryThrottleLimit}} photos</strong>
|
|
on the Site Gallery per day, so that one member doesn't dominate page after
|
|
page of the gallery by uploading <em>all</em> of their pictures at once.
|
|
<a href="/faq#site-gallery-throttle" target="_blank">Learn more <i class="fa fa-external-link"></i></a>
|
|
<br><br>
|
|
You may still upload all the photos you like to <em>your</em> gallery, but new ones can not be featured
|
|
on the Site Gallery until you have waited 24 hours. You MAY "edit" your recently posted photos
|
|
and un-check the Site Gallery box if you <em>really</em> want this one to be featured now.
|
|
</p>
|
|
{{else}}
|
|
<p class="help">
|
|
Leave this box checked and your photo can appear in the site's Photo Gallery
|
|
page. Mainly your <strong class="has-text-link">Public</strong> photos will appear
|
|
on the Gallery, and your approved friends may see your
|
|
<strong class="has-text-warning-dark">Friends-only</strong> photos there as well.
|
|
<strong class="has-text-private">Private</strong> photos may appear in
|
|
the gallery to users whom you have granted access. If this is undesirable,
|
|
un-check the Gallery box to skip the Site Gallery.
|
|
</p>
|
|
{{end}}
|
|
</div>
|
|
|
|
<div class="field mb-5">
|
|
<label class="label has-text-danger">
|
|
<span>Explicit Content</span>
|
|
<span class="icon"><i class="fa fa-fire"></i></span>
|
|
</label>
|
|
{{if eq .Intent "profile_pic"}}
|
|
<span class="has-text-danger">
|
|
Your default profile picture should
|
|
<strong class="has-text-danger">NOT</strong>
|
|
contain explicit content.
|
|
</span>
|
|
|
|
<p class="help">
|
|
Your default profile picture is about your face. You can have nudity
|
|
in it, too, but not a close-up shot of your genitals or sporting an
|
|
erection or engaging in sexual conduct. You can upload pictures like
|
|
that to your page, just not as your default profile picture!
|
|
</p>
|
|
{{else}}
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="explicit"
|
|
value="true"
|
|
{{if .EditPhoto.Explicit}}checked{{end}}>
|
|
This photo contains explicit content
|
|
</label>
|
|
<p class="help">
|
|
Mark this box if this photo contains any explicit content, including an
|
|
erect penis, close-up of genitalia, or any depiction of sexual activity.
|
|
Use your best judgment. "Normal nudes" such as full body nudes in a
|
|
non-sexual context do not need to check this box.
|
|
</p>
|
|
{{end}}
|
|
</div>
|
|
|
|
{{if not .EditPhoto}}
|
|
<div class="field">
|
|
<label class="label">Confirm Upload</label>
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="confirm1"
|
|
value="true"
|
|
required>
|
|
I assert that this is a photo <strong>of myself</strong> and that I have
|
|
permission to upload this picture.
|
|
</label>
|
|
|
|
{{if eq .Intent "profile_pic"}}
|
|
<label class="checkbox">
|
|
<input type="checkbox"
|
|
name="confirm2"
|
|
value="true"
|
|
required>
|
|
I assert that this picture shows my face and is not explicit
|
|
</label>
|
|
{{else}}
|
|
<input type="hidden" name="confirm2" value="true">
|
|
{{end}}
|
|
</div>
|
|
{{end}}
|
|
|
|
<div class="field">
|
|
<button type="submit" class="button is-primary">
|
|
{{if .EditPhoto}}
|
|
Save Changes
|
|
{{else}}
|
|
Upload Photo
|
|
{{end}}
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div><!-- /columns -->
|
|
{{end}}<!-- if under quota -->
|
|
</div>
|
|
</form>
|
|
|
|
<!-- image cropper -->
|
|
<link rel="stylesheet" href="/static/js/croppr/croppr.min.css">
|
|
<script src="/static/js/croppr/croppr.js"></script>
|
|
|
|
<script type="text/javascript">
|
|
var croppr = null;
|
|
const usingCroppr = {{if .NeedsCrop}}true{{else}}false{{end}};
|
|
|
|
function resetCrop() {
|
|
if (croppr !== null) {
|
|
croppr.reset();
|
|
}
|
|
}
|
|
|
|
{{if not .EditPhoto}}
|
|
window.addEventListener("DOMContentLoaded", (event) => {
|
|
let $file = document.querySelector("#file"),
|
|
$fileName = document.querySelector("#fileName"),
|
|
$hiddenPreview = document.querySelector("#imagePreview"),
|
|
$previewBox = document.querySelector("#previewBox"),
|
|
$cropField = document.querySelector("#cropCoords"),
|
|
$dropArea = document.querySelector("#drop-modal"),
|
|
$body = document.querySelector("body");
|
|
|
|
// Common handler for file selection, either via input
|
|
// field or drag/drop onto the page.
|
|
let onFile = (file) => {
|
|
// Too large? (8 MB GIFs) - NOTE: also see config.go so this matches.
|
|
if (file.size >= 1024*1024*8) {
|
|
window.alert("That file is too large! Choose something less than 8 MB.");
|
|
return;
|
|
}
|
|
|
|
$fileName.innerHTML = file.name;
|
|
|
|
// Read the image to show the preview on-page.
|
|
const reader = new FileReader();
|
|
reader.addEventListener("load", () => {
|
|
const uploadedImg = reader.result;
|
|
$hiddenPreview.style.display = "block";
|
|
|
|
// Create a new <img> tag the first time.
|
|
if (croppr !== null) {
|
|
croppr.setImage(uploadedImg);
|
|
croppr.reset();
|
|
return;
|
|
}
|
|
|
|
// If not using croppr, flush the old img preview out.
|
|
if (!usingCroppr) {
|
|
$previewBox.innerHTML = "";
|
|
}
|
|
|
|
let img = document.createElement("img");
|
|
img.src = uploadedImg;
|
|
img.style.display = "block";
|
|
img.style.maxWidth = "100%";
|
|
img.style.height = "auto";
|
|
|
|
// Add it to the wrapper div.
|
|
$previewBox.appendChild(img);
|
|
|
|
if (usingCroppr) {
|
|
croppr = new Croppr(img, {
|
|
aspectRatio: 1,
|
|
minSize: [ 32, 32, 'px' ],
|
|
returnMode: 'real',
|
|
onCropStart: (data) => {
|
|
// console.log(data);
|
|
},
|
|
onCropMove: (data) => {
|
|
// console.log(data);
|
|
},
|
|
onCropEnd: (data) => {
|
|
// console.log(data);
|
|
$cropField.value = [
|
|
data.x, data.y, data.width, data.height
|
|
].join(",");
|
|
},
|
|
onInitialize: (inst) => {
|
|
// Populate the default crop value into the form field.
|
|
let data = inst.getValue();
|
|
$cropField.value = [
|
|
data.x, data.y, data.width, data.height
|
|
].join(",");
|
|
}
|
|
});
|
|
}
|
|
});
|
|
reader.readAsDataURL(file);
|
|
};
|
|
|
|
// Set up drag/drop file upload events.
|
|
$body.addEventListener("dragenter", function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
$dropArea.classList.add("is-active");
|
|
});
|
|
$body.addEventListener("dragover", function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
$dropArea.classList.add("is-active");
|
|
});
|
|
$body.addEventListener("dragleave", function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
$dropArea.classList.remove("is-active");
|
|
});
|
|
$body.addEventListener("drop", function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
$dropArea.classList.remove("is-active");
|
|
|
|
// Grab the file.
|
|
let dt = e.dataTransfer;
|
|
let file = dt.files[0];
|
|
|
|
// Set the file on the input field too.
|
|
$file.files = dt.files;
|
|
|
|
onFile(file);
|
|
});
|
|
|
|
// Clear the answer in case of page reload.
|
|
$cropField.value = "";
|
|
|
|
$file.addEventListener("change", function() {
|
|
let file = this.files[0];
|
|
onFile(file);
|
|
});
|
|
});
|
|
{{end}}
|
|
|
|
// EditPhoto only: a button to crop their photo to set as a profile pic.
|
|
function setProfilePhoto() {
|
|
let $begin = document.querySelector("#editphoto-begin-crop"),
|
|
$cropRow = document.querySelector("#editphoto-cropping"),
|
|
$preview = document.querySelector("#editphoto-preview")
|
|
$cropField = document.querySelector("#cropCoords"),
|
|
$intent = document.querySelector("#intent");
|
|
img = document.querySelector("#editphoto-img");
|
|
|
|
// Toggle the button display, from begin crop to the crop reset button.
|
|
$begin.style.display = 'none';
|
|
$cropRow.style.display = 'block';
|
|
|
|
// Set intent to profile-pic so when the form posts the crop coords will
|
|
// create a new profile pic for this user.
|
|
$intent.value = "profile-pic";
|
|
|
|
croppr = new Croppr(img, {
|
|
aspectRatio: 1,
|
|
minSize: [ 32, 32, 'px' ],
|
|
returnMode: 'real',
|
|
onCropStart: (data) => {
|
|
// console.log(data);
|
|
},
|
|
onCropMove: (data) => {
|
|
// console.log(data);
|
|
},
|
|
onCropEnd: (data) => {
|
|
// console.log(data);
|
|
$cropField.value = [
|
|
data.x, data.y, data.width, data.height
|
|
].join(",");
|
|
},
|
|
onInitialize: (inst) => {
|
|
// Populate the default crop value into the form field.
|
|
let data = inst.getValue();
|
|
$cropField.value = [
|
|
data.x, data.y, data.width, data.height
|
|
].join(",");
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
|
|
</div>
|
|
{{end}}
|