1import { Controller } from "@hotwired/stimulus" 2 3// Tiny copy-to-clipboard helper for sysadmin hint pills and inline 4// snippets. Reads the text to copy from `data-copy-to-clipboard-text-value` 5// (preferred) or, failing that, the element's own textContent. On success, 6// briefly swaps in the "copied" slot content (marked by 7// `data-copy-to-clipboard-target="copied"`) so the admin sees feedback. 8// 9// Usage: 10// <button 11// data-controller="copy-to-clipboard" 12// data-copy-to-clipboard-text-value="bin/rake entity_hubs:seed" 13// data-action="click->copy-to-clipboard#copy"> 14// <span data-copy-to-clipboard-target="default">Copy</span> 15// <span data-copy-to-clipboard-target="copied" hidden>Copied!</span> 16// </button> 17// 18// Prefers `navigator.clipboard.writeText` (secure contexts), falls back to 19// the textarea + execCommand trick so the action still works over plain HTTP 20// in dev / on LAN boxes. 21export default class extends Controller { 22 static targets = ["default", "copied"] 23 static values = { 24 text: String, 25 // How long (ms) to keep the "Copied!" state visible before reverting. 26 timeout: { type: Number, default: 1500 } 27 } 28 29 // Private fields must be declared in the class body before first use â 30 // otherwise Chrome parses this.#revertHandle below as a syntax error 31 // ("Private field '#revertHandle' must be declared in an enclosing class"). 32 #revertHandle = null 33 34 copy(event) { 35 event?.preventDefault?.() 36 event?.stopPropagation?.() 37 38 const text = this.textValue?.length ? this.textValue : this.element.textContent.trim() 39 if (!text) return 40 41 const done = () => this.#flashCopied() 42 43 if (navigator.clipboard?.writeText) { 44 navigator.clipboard.writeText(text).then(done).catch(() => this.#fallbackCopy(text, done)) 45 } else { 46 this.#fallbackCopy(text, done) 47 } 48 } 49 50 // Legacy fallback â hidden textarea + document.execCommand('copy'). Used 51 // when navigator.clipboard isn't available (non-HTTPS, older browsers). 52 #fallbackCopy(text, done) { 53 const ta = document.createElement("textarea") 54 ta.value = text 55 ta.setAttribute("readonly", "") 56 ta.style.position = "absolute" 57 ta.style.left = "-9999px" 58 document.body.appendChild(ta) 59 ta.select() 60 try { 61 document.execCommand("copy") 62 done() 63 } finally { 64 document.body.removeChild(ta) 65 } 66 } 67 68 #flashCopied() { 69 if (this.hasCopiedTarget && this.hasDefaultTarget) { 70 this.defaultTarget.hidden = true 71 this.copiedTarget.hidden = false 72 clearTimeout(this.#revertHandle) 73 this.#revertHandle = setTimeout(() => { 74 this.defaultTarget.hidden = false 75 this.copiedTarget.hidden = true 76 }, this.timeoutValue) 77 } 78 } 79}
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.