diff --git a/pkg/config/config.go b/pkg/config/config.go index 3c2be25..15833fa 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -94,6 +94,7 @@ var ( const ( MaxPhotoWidth = 1280 ProfilePhotoWidth = 512 + AltTextMaxLength = 5000 // Quotas for uploaded photos. PhotoQuotaUncertified = 6 diff --git a/pkg/controller/photo/edit_delete.go b/pkg/controller/photo/edit_delete.go index de925c0..01816b2 100644 --- a/pkg/controller/photo/edit_delete.go +++ b/pkg/controller/photo/edit_delete.go @@ -5,6 +5,7 @@ import ( "net/http" "path/filepath" "strconv" + "strings" "code.nonshy.com/nonshy/website/pkg/chat" "code.nonshy.com/nonshy/website/pkg/config" @@ -71,7 +72,8 @@ func Edit() http.HandlerFunc { // Are we saving the changes? if r.Method == http.MethodPost { var ( - caption = r.FormValue("caption") + caption = strings.TrimSpace(r.FormValue("caption")) + altText = strings.TrimSpace(r.FormValue("alt_text")) isExplicit = r.FormValue("explicit") == "true" isGallery = r.FormValue("gallery") == "true" visibility = models.PhotoVisibility(r.FormValue("visibility")) @@ -85,6 +87,10 @@ func Edit() http.HandlerFunc { goingCircle = visibility == models.PhotoInnerCircle && visibility != photo.Visibility ) + if len(altText) > config.AltTextMaxLength { + altText = altText[:config.AltTextMaxLength] + } + // Respect the Site Gallery throttle in case the user is messing around. if SiteGalleryThrottled { isGallery = false @@ -99,6 +105,7 @@ func Edit() http.HandlerFunc { } photo.Caption = caption + photo.AltText = altText photo.Explicit = isExplicit photo.Gallery = isGallery photo.Visibility = visibility diff --git a/pkg/controller/photo/upload.go b/pkg/controller/photo/upload.go index 71111d8..17590c4 100644 --- a/pkg/controller/photo/upload.go +++ b/pkg/controller/photo/upload.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "path/filepath" + "strings" "code.nonshy.com/nonshy/website/pkg/config" "code.nonshy.com/nonshy/website/pkg/log" @@ -60,7 +61,8 @@ func Upload() http.HandlerFunc { // Are they POSTing? if r.Method == http.MethodPost { var ( - caption = r.PostFormValue("caption") + caption = strings.TrimSpace(r.PostFormValue("caption")) + altText = strings.TrimSpace(r.PostFormValue("alt_text")) isExplicit = r.PostFormValue("explicit") == "true" visibility = r.PostFormValue("visibility") isGallery = r.PostFormValue("gallery") == "true" @@ -74,6 +76,10 @@ func Upload() http.HandlerFunc { isGallery = false } + if len(altText) > config.AltTextMaxLength { + altText = altText[:config.AltTextMaxLength] + } + // Are they at quota already? if photoCount >= photoQuota { session.FlashError(w, r, "You have too many photos to upload a new one. Please delete a photo to make room for a new one.") @@ -135,6 +141,7 @@ func Upload() http.HandlerFunc { Filename: filename, CroppedFilename: cropFilename, Caption: caption, + AltText: altText, Visibility: models.PhotoVisibility(visibility), Gallery: isGallery, Explicit: isExplicit, diff --git a/pkg/models/photo.go b/pkg/models/photo.go index c490e6d..bf69b47 100644 --- a/pkg/models/photo.go +++ b/pkg/models/photo.go @@ -19,6 +19,7 @@ type Photo struct { CroppedFilename string // if cropped, e.g. for profile photo Filesize int64 Caption string + AltText string Flagged bool // photo has been reported by the community Visibility PhotoVisibility `gorm:"index"` Gallery bool `gorm:"index"` // photo appears in the public gallery (if public) diff --git a/web/static/css/theme.css b/web/static/css/theme.css index cc05479..099d1fa 100644 --- a/web/static/css/theme.css +++ b/web/static/css/theme.css @@ -12,6 +12,10 @@ abbr { cursor: pointer; } +.cursor-default { + cursor: default; +} + img { /* https://stackoverflow.com/questions/12906789/preventing-an-image-from-being-draggable-or-selectable-without-using-js */ user-drag: none; @@ -46,10 +50,11 @@ img { /* Photo modals in addition to Bulma .modal-content */ .photo-modal { - width: calc(100vw - 40px); + max-width: calc(100vw - 40px); max-height: calc(100vh - 40px); } .photo-modal #detailImg { + position: relative; background-size: contain; background-repeat: no-repeat; background-position: center center; @@ -57,6 +62,14 @@ img { .photo-modal img { max-height: calc(100vh - 50px); } +.photo-modal .alt-text { + position: absolute; + bottom: 4px; + left: 4px; +} +.line-breakable { + white-space: pre-line; +} /* Custom bulma tag colors */ .tag:not(body).is-private.is-light { diff --git a/web/static/js/right-click.js b/web/static/js/right-click.js index 7ecfeca..05361ab 100644 --- a/web/static/js/right-click.js +++ b/web/static/js/right-click.js @@ -5,7 +5,7 @@ document.addEventListener('DOMContentLoaded', () => { cls = 'is-active'; // Disable context menu on all images. - (document.querySelectorAll('img, video') || []).forEach(node => { + (document.querySelectorAll('img, video, #detailImg') || []).forEach(node => { node.addEventListener('contextmenu', (e) => { $modal.classList.add(cls); e.preventDefault(); diff --git a/web/templates/faq.html b/web/templates/faq.html index 53ee04d..9f622c3 100644 --- a/web/templates/faq.html +++ b/web/templates/faq.html @@ -39,7 +39,7 @@
  • Can my Profile Picture be kept private?
  • What are the visibility options for my profile page?
  • How do I delete direct messages (DMs)?
  • -
  • How does blocking somebody work on nonshy? NEW Jan 5 2024
  • +
  • How does blocking somebody work on nonshy?
  • @@ -48,11 +48,12 @@
  • Do I have to post my nudes here?
  • Do I have to include my face in my nudes?
  • What appears on the Site Gallery?
  • -
  • Why can't I feature my photo on the Site Gallery? NEW Jan 5 2024
  • +
  • Why can't I feature my photo on the Site Gallery?
  • Can I include other people in my photos?
  • What is considered "explicit" in photos?
  • Are digitally altered or 'photoshopped' pictures okay?
  • -
  • Does this site prevent people from downloading my pictures? UPDATED Jan 10 2024
  • +
  • Does this site prevent people from downloading my pictures?
  • +
  • What is alt text on photos about? NEW Mar 10 2024
  • @@ -705,10 +706,6 @@

    Does this site prevent people from downloading my pictures?

    -

    - Updated Jan 10 2024 -

    -

    As of November 2023, the {{PrettyTitle}} website does discourage the downloading of pictures to the limited extent that a web page is able to. We have a right-click handler (long press @@ -763,6 +760,32 @@ report them and let us know!

    +

    What is alt text on photos about?

    + +

    + NEW: March 15 2024 +

    + +

    + When uploading a photo to your gallery, you can write an "alt text" description of the photo + to help with accessibility for the visually impaired. The alt text will appear when hovering + a mouse cursor over an image, in the lightbox modal on the Gallery page (where a photo appears + in full size over a dimmed background), and beneath the photo on its permalink or comments page. +

    + +

    + It is highly recommended to describe your pictures with alt text. Not only does it help + {{PrettyTitle}} to be more inclusive to members with disabilities, but it can also just be + a lot of fun to write text descriptions of your nude and sexy photos! +

    + +

    + If your photo includes any text that is relevant to the meaning of the photo (such as a selfie + of you standing in front of a nude beach sign), the alt text is a good place to transcribe the + text so that it is accessible to members with disabilities and it can be read aloud by their + screen reader software or similar. +

    +

    Forum FAQs

    What do the various badges on the forum mean?

    diff --git a/web/templates/photo/gallery.html b/web/templates/photo/gallery.html index 55a35fc..068371d 100644 --- a/web/templates/photo/gallery.html +++ b/web/templates/photo/gallery.html @@ -175,6 +175,11 @@ would be visible. -->
    + + +
    @@ -512,13 +517,19 @@ {{if HasSuffix .Filename ".mp4"}} {{else}} - + + + {{end}} @@ -633,6 +644,7 @@ {{if HasSuffix .Filename ".mp4"}} {{else}} - + class="js-modal-trigger" data-target="detail-modal"> + {{end}} @@ -749,21 +762,36 @@ {{end}} diff --git a/web/templates/photo/permalink.html b/web/templates/photo/permalink.html index fdeb3b3..51f8f48 100644 --- a/web/templates/photo/permalink.html +++ b/web/templates/photo/permalink.html @@ -87,6 +87,14 @@ {{.Photo.Caption}} {{else}}No caption{{end}} + + {{if .Photo.AltText}} +
    + Alt Text + {{.Photo.AltText}} +
    + {{end}} + {{if and (.CurrentUser.IsAdmin) (not .Photo.Explicit)}}
    diff --git a/web/templates/photo/upload.html b/web/templates/photo/upload.html index 09ea996..4c12307 100644 --- a/web/templates/photo/upload.html +++ b/web/templates/photo/upload.html @@ -223,10 +223,35 @@
    +
    +
    +
    + +
    +
    + 0/5000 +
    +
    + + +

    + Write a description of this photo to help people with disabilities (such as the + vision impaired) to understand the content and meaning of this photo. + Max 5,000 characters. + Learn more +

    +
    +
    @@ -568,6 +593,29 @@ }); {{end}} + // Alt Text helper. + document.addEventListener("DOMContentLoaded", () => { + let $altText = document.querySelector("#alt_text"), + $length = document.querySelector("#alt_text_length"), + maxLength = 5000; + + $altText.maxLength = maxLength; + + $altText.addEventListener("keydown", (e) => { + if ($altText.value.length >= maxLength) { + return false; + } + }); + + let update = () => { + $length.innerHTML = `${$altText.value.length}/${maxLength}`; + }; + + $altText.addEventListener("keyup", update); + $altText.addEventListener("change", update); + update(); + }); + // EditPhoto only: a button to crop their photo to set as a profile pic. function setProfilePhoto() { let $begin = document.querySelector("#editphoto-begin-crop"),