PageSourceSearch

https://backlinkchecker.pro/assets/controllers/file_drop_controlle…7defdf4710b87344841c195f387a040f531a7c8f.js

js backlinkchecker.pro collected 2026-09-24 14:27:46 UTC 4,894 bytes, 167 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3export default class extends Controller {
4  static targets = ["input"]
5
6  connect() {
7    this.element.addEventListener("click", this.click.bind(this))
8
9    // Capture drag events on the whole page
10    this.boundPageDragover = this.pageDragover.bind(this)
11    this.boundPageDragleave = this.pageDragleave.bind(this)
12    this.boundPageDrop = this.pageDrop.bind(this)
13
14    document.addEventListener("dragover", this.boundPageDragover)
15    document.addEventListener("dragleave", this.boundPageDragleave)
16    document.addEventListener("drop", this.boundPageDrop)
17
18    // Prevent default browser behavior for drag/drop
19    document.addEventListener("dragenter", this.preventDefault)
20  }
21
22  disconnect() {
23    document.removeEventListener("dragover", this.boundPageDragover)
24    document.removeEventListener("dragleave", this.boundPageDragleave)
25    document.removeEventListener("drop", this.boundPageDrop)
26    document.removeEventListener("dragenter", this.preventDefault)
27  }
28
29  preventDefault(event) {
30    event.preventDefault()
31  }
32
33  click(event) {
34    if (event.target === this.inputTarget) return
35    this.inputTarget.click()
36  }
37
38  pageDragover(event) {
39    event.preventDefault()
40    event.stopPropagation()
41    this.element.classList.add("dragover")
42  }
43
44  pageDragleave(event) {
45    event.preventDefault()
46    // Only remove dragover if leaving the window
47    if (event.relatedTarget === null || !document.body.contains(event.relatedTarget)) {
48      this.element.classList.remove("dragover")
49    }
50  }
51
52  pageDrop(event) {
53    event.preventDefault()
54    event.stopPropagation()
55    this.element.classList.remove("dragover")
56
57    const files = event.dataTransfer.files
58    if (files.length > 0) {
59      // Check if file type is valid
60      const file = files[0]
61      const validTypes = ['.csv', '.pdf', '.xlsx', '.xls']
62      const fileName = file.name.toLowerCase()
63      const isValid = validTypes.some(ext => fileName.endsWith(ext))
64
65      if (isValid) {
66        this.inputTarget.files = files
67        this.showFileSelected(file)
68      } else {
69        this.showError("Please drop a PDF, CSV, or Excel file")
70      }
71    }
72  }
73
74  // Legacy handlers for direct drop zone events
75  dragover(event) {
76    event.preventDefault()
77    this.element.classList.add("dragover")
78  }
79
80  dragleave(event) {
81    event.preventDefault()
82    this.element.classList.remove("dragover")
83  }
84
85  drop(event) {
86    event.preventDefault()
87    this.element.classList.remove("dragover")
88
89    const files = event.dataTransfer.files
90    if (files.length > 0) {
91      this.inputTarget.files = files
92      this.showFileSelected(files[0])
93    }
94  }
95
96  fileSelected(event) {
97    const files = event.target.files
98    if (files.length > 0) {
99      this.showFileSelected(files[0])
100    }
101  }
102
103  showFileSelected(file) {
104    this.element.classList.add("has-file")
105    this.element.classList.remove("has-error")
106
107    // Update content for new drop zone layout
108    const content = this.element.querySelector(".drop-zone-content")
109    if (content) {
110      const icon = content.querySelector(".drop-zone-icon i")
111      const title = content.querySelector(".drop-zone-title")
112      const subtitle = content.querySelector(".drop-zone-subtitle")
113      const formats = content.querySelector(".drop-zone-formats")
114
115      if (icon) {
116        icon.className = "bi bi-file-earmark-check"
117      }
118      if (title) {
119        title.textContent = file.name
120      }
121      if (subtitle) {
122        subtitle.textContent = this.formatFileSize(file.size)
123      }
124      if (formats) {
125        formats.innerHTML = '<span class="text-success"><i class="bi bi-check-circle me-1"></i>Ready to upload</span>'
126      }
127    }
128
129    // Fallback for old layout
130    const textEl = this.element.querySelector(".text-slate-500")
131    if (textEl && !content) {
132      textEl.textContent = file.name
133    }
134  }
135
136  showError(message) {
137    this.element.classList.add("has-error")
138    this.element.classList.remove("has-file")
139
140    const content = this.element.querySelector(".drop-zone-content")
141    if (content) {
142      const formats = content.querySelector(".drop-zone-formats")
143      if (formats) {
144        formats.innerHTML = `<span class="text-danger"><i class="bi bi-exclamation-circle me-1"></i>${message}</span>`
145      }
146    }
147
148    // Reset after 3 seconds
149    setTimeout(() => {
150      this.element.classList.remove("has-error")
151      if (content) {
152        const formats = content.querySelector(".drop-zone-formats")
153        if (formats) {
154          formats.textContent = "Supports PDF, CSV, Excel files"
155        }
156      }
157    }, 3000)
158  }
159
160  formatFileSize(bytes) {
161    if (bytes === 0) return '0 Bytes'
162    const k = 1024
163    const sizes = ['Bytes', 'KB', 'MB', 'GB']
164    const i = Math.floor(Math.log(bytes) / Math.log(k))
165    return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
166  }
167};

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.