1import { Controller } from "@hotwired/stimulus" 2 3export default class extends Controller { 4 static values = { url: String } 5 static targets = ["categoryList", "category"] 6 7 connect() { 8 this.dragType = null 9 this.draggedEl = null 10 11 // Category drag via handles 12 this.element.querySelectorAll("[data-drag-handle='category']").forEach(handle => { 13 handle.addEventListener("mousedown", (e) => { 14 const cat = handle.closest("[data-faq-sortable-target='category']") 15 cat.setAttribute("draggable", "true") 16 this.pendingDragType = "category" 17 }) 18 }) 19 20 // Item drag via handles 21 this.element.querySelectorAll("[data-drag-handle='item']").forEach(handle => { 22 handle.addEventListener("mousedown", (e) => { 23 const item = handle.closest("[data-faq-id]") 24 item.setAttribute("draggable", "true") 25 this.pendingDragType = "item" 26 }) 27 }) 28 29 // Use event delegation for drag events 30 this.element.addEventListener("dragstart", this.onDragStart.bind(this)) 31 this.element.addEventListener("dragover", this.onDragOver.bind(this)) 32 this.element.addEventListener("drop", this.onDrop.bind(this)) 33 this.element.addEventListener("dragend", this.onDragEnd.bind(this)) 34 } 35 36 onDragStart(e) { 37 this.dragType = this.pendingDragType 38 this.draggedEl = this.dragType === "category" 39 ? e.target.closest("[data-faq-sortable-target='category']") 40 : e.target.closest("[data-faq-id]") 41 42 if (!this.draggedEl) return 43 44 this.draggedEl.classList.add("opacity-40") 45 e.dataTransfer.effectAllowed = "move" 46 // Firefox requires setData 47 e.dataTransfer.setData("text/plain", "") 48 } 49 50 onDragOver(e) { 51 if (!this.draggedEl) return 52 e.preventDefault() 53 e.dataTransfer.dropEffect = "move" 54 55 if (this.dragType === "category") { 56 const target = e.target.closest("[data-faq-sortable-target='category']") 57 if (!target || target === this.draggedEl) return 58 59 const rect = target.getBoundingClientRect() 60 const after = e.clientY > rect.top + rect.height / 2 61 this.categoryListTarget.insertBefore(this.draggedEl, after ? target.nextElementSibling : target) 62 } else { 63 const targetItem = e.target.closest("[data-faq-id]") 64 const targetList = e.target.closest("[data-item-list]") 65 if (!targetList) return 66 67 if (targetItem && targetItem !== this.draggedEl) { 68 const rect = targetItem.getBoundingClientRect() 69 const after = e.clientY > rect.top + rect.height / 2 70 targetList.insertBefore(this.draggedEl, after ? targetItem.nextElementSibling : targetItem) 71 } else if (!targetItem && targetList.children.length === 0) { 72 targetList.appendChild(this.draggedEl) 73 } 74 } 75 } 76 77 onDrop(e) { 78 e.preventDefault() 79 } 80 81 onDragEnd(e) { 82 if (this.draggedEl) { 83 this.draggedEl.classList.remove("opacity-40") 84 this.draggedEl.removeAttribute("draggable") 85 } 86 this.dragType = null 87 this.draggedEl = null 88 this.pendingDragType = null 89 this.save() 90 } 91 92 save() { 93 const ids = [] 94 this.categoryTargets.forEach(cat => { 95 cat.querySelectorAll("[data-faq-id]").forEach(item => { 96 ids.push(item.dataset.faqId) 97 }) 98 }) 99 100 const token = document.querySelector('meta[name="csrf-token"]')?.content 101 fetch(this.urlValue, { 102 method: "PATCH", 103 headers: { "Content-Type": "application/json", "X-CSRF-Token": token }, 104 body: JSON.stringify({ ids }) 105 }) 106 } 107}
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.