Noah
6c91c67c97
* 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
542 lines
26 KiB
HTML
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}} |