1// /chatbot-plugin/assets/js/chatbot-ui.js 2 3document.addEventListener("DOMContentLoaded", () => { 4 const botoToggle = document.getElementById("laia-boto-toggle"); 5 const contenidorChat = document.getElementById("laia-chatbot-fullscreen"); 6 7 if (botoToggle && contenidorChat) { 8 botoToggle.addEventListener("click", () => { 9 const esObert = !contenidorChat.classList.contains("ocult"); 10 if (esObert) { 11 contenidorChat.classList.add("ocult"); 12 botoToggle.textContent = "ð¬"; 13 } else { 14 contenidorChat.classList.remove("ocult"); 15 botoToggle.textContent = "â"; 16 17 // Col·loca ABAIX-DRETA només el primer cop que s'obre 18 if (contenidorChat.classList.contains("popup")) { 19 placeInitiallyAtBottomRight(contenidorChat, { marginRight: 20, marginBottom: 80 }); 20 } 21 clampToViewport(contenidorChat); 22 } 23 }); 24 } 25 26 // Drag/resize només en mode popup 27 if (typeof interact !== "undefined" && contenidorChat && contenidorChat.classList.contains("popup")) { 28 contenidorChat.dataset.x = contenidorChat.dataset.x || "0"; 29 contenidorChat.dataset.y = contenidorChat.dataset.y || "0"; 30 31 // DRAG només pel header 32 interact("#laia-chatbot-fullscreen").draggable({ 33 allowFrom: "#laia-header", 34 ignoreFrom: "#laia-box, #laia-form, #laia-voice-interface", 35 listeners: { 36 move(event) { 37 const target = event.target; 38 let x = (parseFloat(target.getAttribute("data-x")) || 0) + event.dx; 39 let y = (parseFloat(target.getAttribute("data-y")) || 0) + event.dy; 40 41 const rect = target.getBoundingClientRect(); 42 const maxX = window.innerWidth - rect.width; 43 const maxY = window.innerHeight - rect.height; 44 x = Math.min(Math.max(0, x), Math.max(0, maxX)); 45 y = Math.min(Math.max(0, y), Math.max(0, maxY)); 46 47 target.style.transform = `translate(${x}px, ${y}px)`; 48 target.setAttribute("data-x", String(x)); 49 target.setAttribute("data-y", String(y)); 50 }, 51 }, 52 }); 53 54 // RESIZE per totes les vores (ajusta x/y si sâestira per dalt/esquerra) 55 interact("#laia-chatbot-fullscreen").resizable({ 56 edges: { left: true, right: true, bottom: true, top: true }, 57 listeners: { 58 move(event) { 59 const target = event.target; 60 let x = parseFloat(target.getAttribute("data-x")) || 0; 61 let y = parseFloat(target.getAttribute("data-y")) || 0; 62 63 const reqW = Math.round(event.rect.width); 64 const reqH = Math.round(event.rect.height); 65 66 const minW = 320; 67 const minH = 360; 68 69 const maxW = Math.min(reqW, window.innerWidth - 16 - x); 70 const maxH = Math.min(reqH, window.innerHeight - 16 - y); 71 72 const finalW = Math.max(minW, Math.min(maxW, reqW)); 73 const finalH = Math.max(minH, Math.min(maxH, reqH)); 74 75 target.style.width = `${finalW}px`; 76 target.style.height = `${finalH}px`; 77 78 if (event.deltaRect) { 79 x += event.deltaRect.left || 0; 80 y += event.deltaRect.top || 0; 81 } 82 83 const rect = target.getBoundingClientRect(); 84 const maxX = Math.max(0, window.innerWidth - rect.width); 85 const maxY = Math.max(0, window.innerHeight - rect.height); 86 if (x < 0) x = 0; 87 if (y < 0) y = 0; 88 if (x > maxX) x = maxX; 89 if (y > maxY) y = maxY; 90 91 target.style.transform = `translate(${x}px, ${y}px)`; 92 target.setAttribute("data-x", String(x)); 93 target.setAttribute("data-y", String(y)); 94 }, 95 }, 96 }); 97 98 window.addEventListener("resize", () => clampToViewport(contenidorChat)); 99 } 100 101 function clampToViewport(el) { 102 let x = parseFloat(el.getAttribute("data-x")) || 0; 103 let y = parseFloat(el.getAttribute("data-y")) || 0; 104 const rect = el.getBoundingClientRect(); 105 106 const maxX = Math.max(0, window.innerWidth - rect.width); 107 const maxY = Math.max(0, window.innerHeight - rect.height); 108 109 if (x > maxX) x = maxX; 110 if (y > maxY) y = maxY;
111 if (x < 0) x = 0; 112 if (y < 0) y = 0; 113 114 el.style.transform = `translate(${x}px, ${y}px)`; 115 el.setAttribute("data-x", String(x)); 116 el.setAttribute("data-y", String(y)); 117 } 118 119 // Abans tenies bottom/right: calculem el translate perquè surti ABAIX-DRETA igual 120 function placeInitiallyAtBottomRight(el, { marginRight = 20, marginBottom = 80 } = {}) { 121 if (el.dataset.posInit === "1") return; 122 const rect = el.getBoundingClientRect(); 123 const cs = getComputedStyle(el); 124 const leftCSS = parseFloat(cs.left || "0"); 125 const topCSS = parseFloat(cs.top || "0"); 126 127 let x = window.innerWidth - rect.width - marginRight - leftCSS; 128 let y = window.innerHeight - rect.height - marginBottom - topCSS; 129 if (x < 0) x = 0; 130 if (y < 0) y = 0; 131 132 el.style.transform = `translate(${x}px, ${y}px)`; 133 el.setAttribute("data-x", String(x)); 134 el.setAttribute("data-y", String(y)); 135 el.dataset.posInit = "1"; 136 } 137});
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.