1// @swarm_description: Opens any element carrying data-lightbox-src in a full-page image modal 2import { Controller } from "@hotwired/stimulus" 3 4// Anything with data-lightbox-src opens here â the <a> around a comment's photo tile, and the 5// <img> Action Text renders for a picture dropped into a Lexxy body. Delegated from document 6// so images that arrive later (a Turbo Stream appending a new comment, a video finishing 7// normalization) need no wiring of their own. 8const TRIGGER = "[data-lightbox-src]" 9 10// Clicks inside an editor belong to the editor. Lexxy renders the same attachment markup while 11// the comment is still being written, and swallowing that click would break dragging and 12// deleting an image mid-compose. 13const EDITOR = "[contenteditable='true'], lexxy-editor, trix-editor" 14 15export default class extends Controller { 16 static targets = ["dialog", "image", "spinner"] 17 18 connect() { 19 this.boundClick = this.handleClick.bind(this) 20 document.addEventListener("click", this.boundClick) 21 } 22 23 disconnect() { 24 document.removeEventListener("click", this.boundClick) 25 document.body.classList.remove("overflow-hidden") 26 } 27 28 handleClick(event) { 29 // Modified clicks still mean "open in a new tab" â the anchor's href is left intact for 30 // exactly that, and for anyone browsing without JavaScript. 31 if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) return 32 33 const trigger = event.target.closest(TRIGGER) 34 if (!trigger || trigger.closest(EDITOR)) return 35 36 const src = trigger.dataset.lightboxSrc 37 if (!src) return 38 39 event.preventDefault() 40 this.open(src, trigger.dataset.lightboxAlt || "") 41 } 42 43 open(src, alt) { 44 this.imageTarget.classList.add("opacity-0") 45 this.spinnerTarget.classList.remove("hidden") 46 this.imageTarget.alt = alt 47 this.imageTarget.src = src 48 this.dialogTarget.showModal() 49 document.body.classList.add("overflow-hidden") 50 } 51 52 imageLoaded() { 53 this.spinnerTarget.classList.add("hidden") 54 this.imageTarget.classList.remove("opacity-0") 55 } 56 57 close() { 58 this.dialogTarget.close() 59 } 60 61 // Escape closes a native <dialog> without going through #close, so the cleanup hangs off the 62 // dialog's own close event instead. 63 onClose() { 64 document.body.classList.remove("overflow-hidden") 65 this.imageTarget.classList.add("opacity-0") 66 this.imageTarget.removeAttribute("src") 67 } 68 69 // A click on the ::backdrop is reported as a click on the dialog element itself. 70 clickBackdrop(event) { 71 if (event.target === this.dialogTarget) this.close() 72 } 73}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.