PageSourceSearch

https://littlebirdelectronics.com.au/assets/controllers/community/copy_controller-7e46f4f7.js

js littlebirdelectronics.com.au collected 2026-09-25 14:08:59 UTC 2,205 bytes, 70 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3// Copy-to-clipboard for a code block or a draft, the way every assistant does
4// it: a button on the block, and the label confirms rather than a toast.
5//
6// Two sources, because the two blocks want different things copied:
7//
8//  * a draft carries the RAW markdown in `textValue`. Copying the rendering
9//    would drop the list markers, and the bullets are the part someone
10//    pasting into an email wants kept.
11//  * a code block has no value set, so it copies the <pre> exactly as it
12//    stands — byte-for-byte what's on screen, which for code is the only
13//    acceptable answer.
14export default class extends Controller {
15  static targets = ["button"]
16  static values = { text: String }
17
18  async copy() {
19    const text = this.hasTextValue ? this.textValue : this.element.querySelector("pre")?.innerText
20    if (!text) return
21
22    try {
23      await navigator.clipboard.writeText(text)
24      this.confirm("Copied")
25    } catch {
26      // navigator.clipboard needs a secure context and permission, and neither
27      // is guaranteed. The old selection-based route works where it doesn't,
28      // and failing silently on a button someone just pressed is worse than
29      // either.
30      this.confirm(this.fallback(text) ? "Copied" : "Press ⌘C")
31    }
32  }
33
34  fallback(text) {
35    const scratch = document.createElement("textarea")
36    scratch.value = text
37    scratch.setAttribute("readonly", "")
38    // Off-screen rather than hidden: a display:none textarea can't be selected.
39    scratch.style.cssText = "position:fixed;top:-9999px;opacity:0"
40    document.body.append(scratch)
41    scratch.select()
42
43    let copied = false
44    try {
45      copied = document.execCommand("copy")
46    } catch {
47      copied = false
48    }
49    scratch.remove()
50    return copied
51  }
52
53  confirm(label) {
54    if (!this.hasButtonTarget) return
55
56    const button = this.buttonTarget
57    button.textContent = label
58    button.dataset.copied = "true"
59
60    clearTimeout(this.resetTimer)
61    this.resetTimer = setTimeout(() => {
62      button.textContent = "Copy"
63      delete button.dataset.copied
64    }, 1600)
65  }
66
67  disconnect() {
68    clearTimeout(this.resetTimer)
69  }
70}

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.