PageSourceSearch

https://platform.uno/app/themes/hello-elementor-child/assets/js-alt/studio-prompt.js?ver=1790197790

js platform.uno collected 2026-09-24 18:19:05 UTC 9,897 bytes, 259 lines download raw bytes

1/* Uno Studio prompt + suggestion pills (home page). */
2(function () {
3    'use strict';
4
5    var STORAGE_KEY = 'uno_studio_prompt';
6    var MAX_LENGTH  = (typeof unoStudio !== 'undefined' && unoStudio.promptMaxLength) ? unoStudio.promptMaxLength : 2000;
7
8    var form     = document.getElementById('unoStudioForm');
9    var input    = document.getElementById('unoStudioInput');
10    var overlay  = document.getElementById('unoStudioOverlay');
11    var backdrop = document.getElementById('unoStudioBackdrop');
12    var closeBtn = document.getElementById('unoStudioClose');
13
14    function buildCard(sample) {
15        var sampleId   = sample.id          || '';
16        var sampleName = sample.displayName || '';
17        if (!sampleId || !sampleName) return null;
18
19        var btn = document.createElement('button');
20        btn.type = 'button';
21        btn.className = 'uno-studio-prompt__card';
22        btn.dataset.project = sampleId;
23
24        if (sample.iconUrl) {
25            var parsedIcon;
26            try { parsedIcon = new URL(sample.iconUrl); } catch (e) { parsedIcon = null; }
27            if (parsedIcon && parsedIcon.protocol === 'https:') {
28                var img = document.createElement('img');
29                img.className = 'uno-studio-prompt__card-img';
30                img.src = sample.iconUrl;
31                img.alt = sampleName;
32                img.loading = 'lazy';
33                btn.appendChild(img);
34            }
35        }
36
37        var textDiv = document.createElement('div');
38        textDiv.className = 'uno-studio-prompt__card-text';
39
40        var strong = document.createElement('strong');
41        strong.textContent = sampleName;
42        textDiv.appendChild(strong);
43
44        var span = document.createElement('span');
45        span.textContent = sample.description || '';
46        textDiv.appendChild(span);
47
48        btn.appendChild(textDiv);
49
50        btn.addEventListener('click', function () {
51            var project = btn.dataset.project;
52            if (!project) return;
53
54            // GA4: prebuilt-app selection. Label carries the dynamic project id so each
55            // featured app is measured separately even though the set comes from the API.
56            if (typeof gtag !== 'undefined') {
57                gtag('event', 'cta_click', {
58                    event_category: 'homepage',
59                    event_label: 'sample_' + project
60                });
61            }
62
63            redirectToStudioProject(project);
64        });
65
66        return btn;
67    }
68
69    function initPills() {
70        var container = document.querySelector('.uno-studio-prompt__suggestions[hidden]');
71        if (!container) return;
72        if (typeof unoStudio === 'undefined' || !unoStudio.contentServiceUrl) return;
73
74        var serviceUrl;
75        try { serviceUrl = new URL(unoStudio.contentServiceUrl); } catch (e) { return; }
76        var validOrigin = (serviceUrl.protocol === 'https:' && serviceUrl.hostname.endsWith('.platform.uno')) ||
77                          serviceUrl.hostname === 'localhost';
78        if (!validOrigin) return;
79
80        var cardList = container.querySelector('.uno-studio-prompt__cards');
81        if (!cardList) return;
82
83        var count      = parseInt(container.dataset.count, 10) || 3;
84        var baseUrl    = unoStudio.contentServiceUrl.replace(/\/+$/, '');
85        var fetchUrl   = baseUrl + '/api/v1/gallery/catalog/featured?n=' + count;
86        var controller = new AbortController();
87        var timer      = setTimeout(function () { controller.abort(); }, 5000);
88
89        fetch(fetchUrl, { signal: controller.signal })
90            .then(function (res) {
91                clearTimeout(timer);
92                if (!res.ok) return Promise.reject(new Error('HTTP ' + res.status));
93                return res.json();
94            })
95            .then(function (data) {
96                var samples = (data && Array.isArray(data.samples)) ? data.samples : [];
97                if (samples.length > count) samples = samples.slice(0, count);
98                var addedCount = 0;
99                samples.forEach(function (sample) {
100                    var card = buildCard(sample);
101                    if (card) { cardList.appendChild(card); addedCount++; }
102                });
103                if (addedCount > 0) container.removeAttribute('hidden');
104            })
105            .catch(function (err) {
106                clearTimeout(timer);
107                var isNetwork = err instanceof TypeError || (err && err.name === 'AbortError');
108                console.error('[uno-studio] Pills fetch failed (' + (isNetwork ? 'network/timeout' : err.message) + ')');
109            });
110    }
111
112    if (document.readyState === 'loading') {
113        document.addEventListener('DOMContentLoaded', initPills);
114    } else {
115        initPills();
116    }
117
118    if (!form || !input) return;
119
120    function setPromptCookie(prompt) {
121        var safe = prompt.substring(0, MAX_LENGTH);
122        document.cookie = STORAGE_KEY + '=' + encodeURIComponent(safe) + '; path=/; max-age=1800; SameSite=Lax; Secure';
123    }
124
125    function setAutoCookie() {
126        document.cookie = STORAGE_KEY + '_auto=1; path=/; max-age=1800; SameSite=Lax; Secure';
127    }
128
129    function clearPromptCookies() {
130        document.cookie = STORAGE_KEY + '=; path=/; max-age=0';
131        document.cookie = STORAGE_KEY + '_auto=; path=/; max-age=0';
132    }
133
134    function redirectToStudioProject(project) {
135        var studioBase;
136        try { studioBase = new URL(unoStudio.studioUrl); } catch (e) { return; }
137        if (studioBase.protocol !== 'https:') return;
138        studioBase.searchParams.set('project', project);
139        window.location.href = studioBase.toString();
140    }
141
142    function buildStudioUrl(prompt, autoSubmit) {
143        var studioBase;
144        try { studioBase = new URL(unoStudio.studioUrl); } catch (e) { return ''; }
145        if (studioBase.protocol !== 'https:') return '';
146        var url = unoStudio.studioUrl.replace(/\/+$/, '') + '?prompt=' + encodeURIComponent(prompt);
147        if (autoSubmit) url += '&autoSubmit=true';
148        return url;
149    }
150
151    function redirectToStudio(prompt, autoSubmit) {
152        var url = buildStudioUrl(prompt, autoSubmit);
153        if (url) window.location.href = url;
154    }
155
156    function capturePromptBeacon(prompt) {
157        if (!unoStudio.ajaxUrl || !unoStudio.captureNonce) return;
158        try {
159            var data = new FormData();
160            data.append('action', 'uno_studio_capture_prompt');
161            data.append('nonce', unoStudio.captureNonce);
162            data.append('prompt', prompt.substring(0, MAX_LENGTH));
163            if (navigator.sendBeacon) {
164                navigator.sendBeacon(unoStudio.ajaxUrl, data);
165            } else {
166                fetch(unoStudio.ajaxUrl, { method: 'POST', body: data, keepalive: true, credentials: 'same-origin' });
167            }
168        } catch (e) {}
169    }
170
171    function savePromptAndRedirect(prompt, autoSubmit) {
172        if (unoStudio.isLoggedIn) {
173            capturePromptBeacon(prompt);
174            redirectToStudio(prompt, autoSubmit);
175            return;
176        }
177        setPromptCookie(prompt);
178        if (autoSubmit) setAutoCookie();
179        openOverlay();
180    }
181
182    function openOverlay() {
183        if (!overlay) return;
184        overlay.classList.add('is-open');
185        document.body.style.overflow = 'hidden';
186        overlay.querySelector('input[name="username"], input[name="email"]')?.focus();
187    }
188
189    function closeOverlay() {
190        if (!overlay) return;
191        overlay.classList.remove('is-open');
192        document.body.style.overflow = '';
193        clearPromptCookies();
194    }
195
196    if (closeBtn) {
197        closeBtn.addEventListener('click', closeOverlay);
198    }
199
200    if (backdrop) {
201        backdrop.addEventListener('click', closeOverlay);
202    }
203
204    document.addEventListener('keydown', function (e) {
205        if (e.key === 'Escape' && overlay && overlay.classList.contains('is-open')) {
206            closeOverlay();
207        }
208    });
209
210    if (overlay) {
211        var tabs   = overlay.querySelectorAll('.ctt-auth-tab[data-panel]');
212        var panels = overlay.querySelectorAll('.ctt-auth-panel[data-panel]');
213
214        tabs.forEach(function (tab) {
215            tab.addEventListener('click', function () {
216                var target = tab.dataset.panel;
217                tabs.forEach(function (t) {
218                    t.classList.toggle('is-active', t.dataset.panel === target);
219                });
220                panels.forEach(function (p) {
221                    p.classList.toggle('is-active', p.dataset.panel === target);
222                });
223            });
224        });
225    }
226
227    // Match the Studio app prompt: Enter submits, Shift+Enter inserts a newline.
228    // Without this, Enter in the textarea only adds a line break and the user
229    // has to reach for the arrow button. Ignore Enter while an IME composition
230    // is in progress so CJK input does not submit half-typed text.
231    input.addEventListener('keydown', function (e) {
232        if (e.key !== 'Enter' || e.shiftKey) return;
233        if (e.isComposing || e.keyCode === 229) return;
234        e.preventDefault();
235        if (typeof form.requestSubmit === 'function') {
236            form.requestSubmit();
237        } else {
238            form.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
239        }
240    });
241
242    form.addEventListener('submit', function (e) {
243        e.preventDefault();
244        var prompt = input.value.trim();
245        if (!prompt) return;
246
247        // GA4: homepage prompt submission. Uses the same cta_click/label scheme as
248        // the ga-event system. Lives here (not on the arrow) to also cover Enter.
249        if (typeof gtag !== 'undefined') {
250            gtag('event', 'cta_click', {
251                event_category: 'homepage',
252                event_label: 'prompt_submit'
253            });
254        }
255
256        savePromptAndRedirect(prompt, false);
257    });
258
259})();

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.