website/web/templates/photo/upload.html
Noah 6c91c67c97 More Private User Avatars
* Users who set their Profile Picture to "friends only" or "private" can have
  their avatar be private all over the website to users who are not their
  friends or not granted access.
* Users who are not your friends see a yellow placeholder avatar, and users
  not granted access to a private Profile Pic sees a purple avatar.
* Admin users see these same placeholder avatars most places too (on search,
  forums, comments, etc.) if the user did not friend or grant the admin. But
  admins ALWAYS see it on their Profile Page directly, for ability to moderate.
* Fix marking Notifications as read: clicking the link in an unread notification
  now will wait on the ajax request to finish before allowing the redirect.
* Update the FAQ
2022-09-08 21:42:20 -07:00

542 lines
26 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}}
{{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"
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">
<img src="{{PhotoURL .EditPhoto.Filename}}" id="editphoto-img">
</figure>
<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>
<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</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>
<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>
<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"
checked
{{if .EditPhoto.Gallery}}checked{{end}}>
Show this photo in the site-wide Photo Gallery
</label>
<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>
</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 -->
<!-- <script src="/static/js/jquery-3.6.0.min.js"></script> -->
<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) => {
$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}}