1/** 2 * Prompt Box widget â frontend behavior. 3 * Mirrors src/components/PromptBox/index.tsx in landings-ssr. 4 */ 5(function () { 6 'use strict'; 7 8 var ARROW_SVG = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 19V5"/><path d="M5 12l7-7 7 7"/></svg>'; 9 var SPINNER_SVG = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="prompt_box_spinner"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>'; 10 var X_SVG = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>'; 11 12 var MAX_HEIGHT = 200; 13 var MAX_IMAGES = 4; 14 var MAX_FILE_SIZE = 10 * 1024 * 1024; 15 16 function uploadFileToCDN(file) { 17 var UPLOAD_URL = 'https://upload.picsart.com/files'; 18 var UPLOAD_URL_STAGE2 = 'https://upload-stage.picsartstage2.com/files'; 19 var hostname = window.location.hostname; 20 var isStage2 = hostname.indexOf('picsartstage2.com') !== -1 || hostname.indexOf('stage2') !== -1; 21 var url = isStage2 ? UPLOAD_URL_STAGE2 : UPLOAD_URL; 22 var formData = new FormData(); 23 formData.append('type', 'editing-temp-landings'); 24 formData.append('file', file); 25 formData.append('url', ''); 26 formData.append('metainfo', ''); 27 return fetch(url, { method: 'POST', body: formData, mode: 'cors', cache: 'no-store' }) 28 .then(function (res) { 29 return res.json().then(function (data) { 30 return { ok: res.ok, data: data }; 31 }); 32 }) 33 .then(function (out) { 34 if (out.ok && out.data && out.data.result && out.data.result.url) return out.data.result.url; 35 throw new Error('Upload failed'); 36 }); 37 } 38 39 function buildUrl(rawDeeplink, params) { 40 try { 41 var url = new URL(rawDeeplink, window.location.origin); 42 Object.keys(params).forEach(function (k) { 43 url.searchParams.set(k, params[k]); 44 }); 45 return url.href; 46 } catch (e) { 47 var pairs = Object.keys(params).map(function (k) { 48 return encodeURIComponent(k) + '=' + encodeURIComponent(params[k]); 49 }); 50 var separator = rawDeeplink.indexOf('?') !== -1 ? '&' : '?'; 51 return rawDeeplink + separator + pairs.join('&'); 52 } 53 } 54 55 function init(container) { 56 if (!container || container.dataset.promptBoxInit === '1') return; 57 container.dataset.promptBoxInit = '1'; 58 59 var deeplink = container.getAttribute('data-deeplink') || ''; 60 var textarea = container.querySelector('.prompt_box_textarea'); 61 var submitBtn = container.querySelector('[data-action="submit"]'); 62 if (!textarea || !submitBtn) return; 63 var submitIcon = submitBtn.querySelector('[data-role="submit-icon"]'); 64 var fileInput = container.querySelector('.prompt_box_file_input'); 65 var thumbsEl = container.querySelector('[data-role="thumbs"]'); 66 var openFileBtn = container.querySelector('[data-action="open-file"]'); 67 var libraryBtn = container.querySelector('[data-action="toggle-library"]'); 68 var libraryEl = container.querySelector('[data-role="library"]'); 69 var ratioBtn = container.querySelector('[data-action="toggle-ratio"]'); 70 var ratioEl = container.querySelector('[data-role="ratio"]'); 71 var ratioLabel = container.querySelector('[data-role="ratio-label"]'); 72 var micBtn = container.querySelector('[data-action="toggle-mic"]'); 73 74 var images = []; 75 var selectedRatio = 'auto'; 76 var isUploading = false; 77 78 function autoresize() { 79 textarea.style.height = 'auto'; 80 var next = Math.min(textarea.scrollHeight, MAX_HEIGHT); 81 textarea.style.height = next + 'px'; 82 textarea.style.overflowY = textarea.scrollHeight > MAX_HEIGHT ? 'auto' : 'hidden'; 83 } 84 85 function canSubmitNow() { 86 var hasText = textarea.value.trim().length > 0; 87 var hasImages = images.length > 0; 88 return (hasText || hasImages) && !isUploading; 89 } 90 91 function syncSubmitState() { 92 var canSubmit = canSubmitNow(); 93 submitBtn.classList.toggle('prompt_box_submit--active', canSubmit); 94 submitBtn.setAttribute('aria-disabled', canSubmit ? 'false' : 'true'); 95 } 96 97 // --- Reference images --- 98 function renderThumbs() { 99 if (!thumbsEl) return; 100 thumbsEl.innerHTML = ''; 101 if (!images.length) { 102 thumbsEl.classList.remove('prompt_box_thumbs--has-items'); 103 return; 104 } 105 thumbsEl.classList.add('prompt_box_thumbs--has-items');
106 images.forEach(function (img, index) { 107 var wrap = document.createElement('div'); 108 wrap.className = 'prompt_box_thumb'; 109 110 var image = document.createElement('img'); 111 image.src = img.preview; 112 image.alt = 'Reference ' + (index + 1); 113 wrap.appendChild(image); 114 115 var remove = document.createElement('button'); 116 remove.type = 'button'; 117 remove.className = 'prompt_box_thumb_remove'; 118 remove.setAttribute('aria-label', 'Remove image ' + (index + 1)); 119 remove.innerHTML = X_SVG; 120 remove.addEventListener('click', function () { 121 images.splice(index, 1); 122 renderThumbs(); 123 syncSubmitState(); 124 }); 125 wrap.appendChild(remove); 126 127 thumbsEl.appendChild(wrap); 128 }); 129 } 130 131 function readFile(file) { 132 if (!file || !file.type || file.type.indexOf('image/') !== 0) return; 133 if (file.size > MAX_FILE_SIZE) return; 134 if (images.length >= MAX_IMAGES) return; 135 var reader = new FileReader(); 136 reader.onload = function (e) { 137 if (images.length >= MAX_IMAGES) return; 138 images.push({ file: file, preview: e.target.result }); 139 renderThumbs(); 140 syncSubmitState(); 141 }; 142 reader.readAsDataURL(file); 143 } 144 145 if (openFileBtn && fileInput) { 146 openFileBtn.addEventListener('click', function () { 147 if (images.length >= MAX_IMAGES) return; 148 fileInput.click(); 149 }); 150 fileInput.addEventListener('change', function (e) { 151 if (e.target.files && e.target.files[0]) readFile(e.target.files[0]); 152 fileInput.value = ''; 153 }); 154 } 155 156 // --- Dropdowns --- 157 function closeAllDropdowns(except) { 158 if (libraryEl && libraryEl !== except) { 159 libraryEl.hidden = true; 160 if (libraryBtn) libraryBtn.setAttribute('aria-expanded', 'false'); 161 } 162 if (ratioEl && ratioEl !== except) { 163 ratioEl.hidden = true; 164 if (ratioBtn) ratioBtn.setAttribute('aria-expanded', 'false'); 165 } 166 } 167 168 if (libraryBtn && libraryEl) { 169 libraryBtn.addEventListener('click', function (e) { 170 e.stopPropagation(); 171 e.preventDefault(); 172 var willOpen = libraryEl.hidden; 173 closeAllDropdowns(willOpen ? libraryEl : null); 174 libraryEl.hidden = !willOpen; 175 libraryBtn.setAttribute('aria-expanded', willOpen ? 'true' : 'false'); 176 }); 177 178 // Stop clicks inside the popup body from bubbling to the doc-level 179 // mousedown listener, which would otherwise race with the item click. 180 libraryEl.addEventListener('mousedown', function (e) { e.stopPropagation(); }); 181 182 // Mirrors handlePromptSelect in landings-ssr/src/components/PromptBox/index.tsx: 183 // const selected = prompt.prompt || prompt.text; 184 // setPromptText(prev => prev.trim() ? `${prev.trim()} ${selected}` : selected); 185 // setSelectedPrompt(prompt.text); 186 // setShowPromptLibrary(false); 187 var items = libraryEl.querySelectorAll('.prompt_box_library_item'); 188 items.forEach(function (btn) { 189 btn.addEventListener('click', function (e) { 190 e.preventDefault(); 191 e.stopPropagation(); 192 var fullPrompt = btn.getAttribute('data-prompt-full') || btn.textContent.trim(); 193 var current = textarea.value.trim(); 194 textarea.value = current ? (current + ' ' + fullPrompt) : fullPrompt; 195 items.forEach(function (other) { 196 other.classList.toggle('prompt_box_library_item--selected', other === btn); 197 }); 198 autoresize(); 199 syncSubmitState(); 200 textarea.focus(); 201 closeAllDropdowns(); 202 }); 203 }); 204 } 205 206 if (ratioBtn && ratioEl) { 207 ratioBtn.addEventListener('click', function (e) { 208 e.stopPropagation(); 209 e.preventDefault(); 210 var willOpen = ratioEl.hidden; 211 closeAllDropdowns(willOpen ? ratioEl : null); 212 ratioEl.hidden = !willOpen; 213 ratioBtn.setAttribute('aria-expanded', willOpen ? 'true' : 'false'); 214 }); 215 216 ratioEl.addEventListener('mousedown', function (e) { e.stopPropagation(); }); 217 218 var ratioItems = ratioEl.querySelectorAll('.prompt_box_ratio_item');
219 ratioItems.forEach(function (btn) { 220 btn.addEventListener('click', function (e) { 221 e.preventDefault(); 222 e.stopPropagation(); 223 selectedRatio = btn.getAttribute('data-ratio-value') || 'auto'; 224 if (ratioLabel) ratioLabel.textContent = btn.getAttribute('data-ratio-label') || 'Auto'; 225 ratioItems.forEach(function (b) { 226 b.classList.toggle('prompt_box_ratio_item--selected', b === btn); 227 }); 228 closeAllDropdowns(); 229 }); 230 }); 231 } 232 233 document.addEventListener('mousedown', function (e) { 234 if (!container.contains(e.target)) closeAllDropdowns(); 235 }); 236 237 // --- Microphone (mirrors useSpeechToText in landings-ssr/src/hooks) --- 238 // The React version always renders the mic button; the hook bails out 239 // silently when the browser does not expose the SpeechRecognition API. 240 var recognition = null; 241 var isListening = false; 242 var finalTranscript = ''; 243 244 if (micBtn) { 245 micBtn.addEventListener('click', function () { 246 var SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; 247 if (!SpeechRecognition) { 248 if (window.console && console.warn) { 249 console.warn('Speech Recognition is not supported in this browser.'); 250 } 251 return; 252 } 253 if (isListening && recognition) { 254 try { recognition.stop(); } catch (e) {} 255 return; 256 } 257 recognition = new SpeechRecognition(); 258 recognition.lang = (document.documentElement.lang || navigator.language || 'en-US'); 259 recognition.interimResults = true; 260 recognition.continuous = true; 261 finalTranscript = textarea.value; 262 263 recognition.onresult = function (event) { 264 var interim = ''; 265 for (var i = event.resultIndex; i < event.results.length; i++) { 266 var transcript = event.results[i][0].transcript; 267 if (event.results[i].isFinal) { 268 finalTranscript += (finalTranscript ? ' ' : '') + transcript; 269 } else { 270 interim += transcript; 271 } 272 } 273 textarea.value = finalTranscript + (interim ? ' ' + interim : ''); 274 autoresize(); 275 syncSubmitState(); 276 }; 277 recognition.onerror = function () { 278 isListening = false; 279 micBtn.classList.remove('prompt_box_mic_btn--active'); 280 recognition = null; 281 }; 282 recognition.onend = function () { 283 isListening = false; 284 micBtn.classList.remove('prompt_box_mic_btn--active'); 285 recognition = null; 286 }; 287 288 try { 289 recognition.start(); 290 isListening = true; 291 micBtn.classList.add('prompt_box_mic_btn--active'); 292 } catch (e) {} 293 }); 294 } 295 296 // --- Submit --- 297 function setUploading(state) { 298 isUploading = state; 299 if (submitIcon) submitIcon.innerHTML = state ? SPINNER_SVG : ARROW_SVG; 300 submitBtn.classList.toggle('prompt_box_submit--uploading', state); 301 syncSubmitState(); 302 } 303 304 function submit() { 305 if (!canSubmitNow()) return; 306 var value = textarea.value.trim(); 307 var params = {}; 308 if (value) params['prompt'] = value; 309 if (selectedRatio) params['aspect_ratio'] = selectedRatio; 310 311 var go = function (cdnUrls) { 312 if (cdnUrls && cdnUrls.length) params['images'] = cdnUrls.join(','); 313 window.open(buildUrl(deeplink, params), '_blank', 'noopener,noreferrer'); 314 }; 315 316 if (images.length) { 317 setUploading(true); 318 Promise.all(images.map(function (img) { return uploadFileToCDN(img.file); })) 319 .then(function (urls) { setUploading(false); go(urls); }) 320 .catch(function () { setUploading(false); alert('Upload failed. Please try again.'); }); 321 } else { 322 go(); 323 } 324 } 325 326 textarea.addEventListener('input', function () { autoresize(); syncSubmitState(); }); 327 // Close any open dropdown when the user interacts with the textarea. 328 textarea.addEventListener('focus', function () { closeAllDropdowns(); }); 329 textarea.addEventListener('mousedown', function () { closeAllDropdowns(); }); 330 textarea.addEventListener('keydown', function (e) { 331 closeAllDropdowns(); 332 if (e.key === 'Enter' && !e.shiftKey) { 333 e.preventDefault(); 334 submit(); 335 } 336 }); 337 submitBtn.addEventListener('click', function (e) { 338 e.preventDefault(); 339 submit(); 340 }); 341 342 autoresize(); 343 syncSubmitState(); 344 } 345 346 function bootAll() { 347 var nodes = document.querySelectorAll('.section_prompt_box'); 348 for (var i = 0; i < nodes.length; i++) { 349 init(nodes[i]); 350 } 351 } 352 353 if (document.readyState === 'loading') { 354 document.addEventListener('DOMContentLoaded', bootAll); 355 } else { 356 bootAll(); 357 } 358 359 // Expose for late-rendered widgets (e.g. Elementor editor preview). 360 window.PicsartPromptBox = { init: init, bootAll: bootAll }; 361})();
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.