PageSourceSearch

https://arpnetworks.com/assets/controllers/file_input_controller-9bf04943.js

js arpnetworks.com collected 2026-09-25 19:56:03 UTC 3,165 bytes, 97 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3// Manages multi-file input: shows a live filename list and supports removing
4// individual files before submit without a page reload.
5export default class extends Controller {
6  static targets = ["input", "list", "zone"]
7  static values = { maxSize: { type: Number, default: 26214400 } }
8
9  connect() {
10    this.accumulatedFiles = []
11  }
12
13  update() {
14    this._merge(Array.from(this.inputTarget.files))
15    this._sync()
16    this.renderList()
17  }
18
19  dragover(event) {
20    event.preventDefault()
21    if (this.hasZoneTarget) this.zoneTarget.classList.add("is-dragover")
22  }
23
24  dragleave() {
25    if (this.hasZoneTarget) this.zoneTarget.classList.remove("is-dragover")
26  }
27
28  drop(event) {
29    event.preventDefault()
30    if (this.hasZoneTarget) this.zoneTarget.classList.remove("is-dragover")
31    this._merge(Array.from(event.dataTransfer.files))
32    this._sync()
33    this.renderList()
34  }
35
36  remove(event) {
37    const index = parseInt(event.currentTarget.dataset.fileIndex, 10)
38    this.accumulatedFiles = this.accumulatedFiles.filter((_, i) => i !== index)
39    this._sync()
40    this.renderList()
41  }
42
43  _merge(newFiles) {
44    for (const file of newFiles) {
45      if (file.size > this.maxSizeValue) {
46        window.Toast?.error(`${file.name} is too large (maximum is 25 MB per file)`)
47        continue
48      }
49      const key = `${file.name}|${file.size}|${file.lastModified}`
50      const isDupe = this.accumulatedFiles.some(
51        f => `${f.name}|${f.size}|${f.lastModified}` === key
52      )
53      if (!isDupe) this.accumulatedFiles.push(file)
54    }
55  }
56
57  _sync() {
58    const dt = new DataTransfer()
59    this.accumulatedFiles.forEach(f => dt.items.add(f))
60    // value="" reliably clears the native input (including CDP-set files) so
61    // any late change event reads an empty FileList and cannot re-add removed files.
62    this.inputTarget.value = ""
63    this.inputTarget.files = dt.files
64  }
65
66  renderList() {
67    this.listTarget.innerHTML = ""
68    // Read from accumulatedFiles, not inputTarget.files: CDP-set File objects
69    // (from Selenium's DOM.setFileInputFiles) can silently fail when added to
70    // a DataTransfer, leaving inputTarget.files empty after _sync().
71    this.accumulatedFiles.forEach((file, index) => {
72      const item = document.createElement("span")
73      item.className = "ticket-attachment-file-item"
74
75      const icon = document.createElement("span")
76      icon.className = "icon is-small"
77      icon.innerHTML = '<i class="fas fa-file"></i>'
78
79      const name = document.createElement("span")
80      name.className = "ticket-attachment-file-name"
81      name.textContent = file.name
82
83      const btn = document.createElement("button")
84      btn.type = "button"
85      btn.className = "ticket-attachment-file-remove"
86      btn.setAttribute("data-action", "click->file-input#remove")
87      btn.setAttribute("data-file-index", index)
88      btn.setAttribute("aria-label", `Remove ${file.name}`)
89      btn.innerHTML = '<i class="fas fa-xmark"></i>'
90
91      item.appendChild(icon)
92      item.appendChild(name)
93      item.appendChild(btn)
94      this.listTarget.appendChild(item)
95    })
96  }
97}

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.