PageSourceSearch

https://www.samaterials.fr/js/download-gate-modal.js?v=1788576273

js samaterials.fr collected 2026-10-02 22:57:33 UTC 13,143 bytes, 369 lines download raw bytes

1(function () {
2    'use strict';
3
4    // 当前站点标识,写入下载记录;各语种站 JS 需改为对应 sam_xx
5    var SITE_WEBSITE = 'sam_fr';
6
7    var root = document.getElementById('sam-download-gate');
8    if (!root) {
9        return;
10    }
11
12    var form = document.getElementById('sam-download-gate-form');
13    var nameInput = document.getElementById('sam-download-gate-name');
14    var emailInput = document.getElementById('sam-download-gate-email');
15    var errorEl = document.getElementById('sam-download-gate-error');
16    var submitBtn = document.getElementById('sam-download-gate-submit');
17    var pendingUrls = [];
18    var pendingAction = null;
19    var mobileMq = window.matchMedia('(max-width: 1025px)');
20
21    function t(key, fallback) {
22        var value = root.getAttribute(key);
23        return (value && value.trim()) || fallback;
24    }
25
26    function setEmailPlaceholder() {
27        if (!emailInput) {
28            return;
29        }
30        var pc = emailInput.getAttribute('data-placeholder-pc') || '';
31        var mobile = emailInput.getAttribute('data-placeholder-mobile') || pc;
32        emailInput.placeholder = mobileMq.matches ? mobile.trim() : pc.trim();
33    }
34
35    function showError(message) {
36        if (!errorEl) {
37            return;
38        }
39        errorEl.textContent = message || '';
40        if (message) {
41            errorEl.removeAttribute('hidden');
42        } else {
43            errorEl.setAttribute('hidden', '');
44        }
45    }
46
47    function normalizeUrls(urls) {
48        if (typeof urls === 'string') {
49            urls = [urls];
50        }
51        if (!Array.isArray(urls)) {
52            return [];
53        }
54        var seen = {};
55        var result = [];
56        urls.forEach(function (url) {
57            var value = (url || '').trim();
58            if (!value || value === '#' || value.indexOf('javascript:') === 0 || seen[value]) {
59                return;
60            }
61            seen[value] = true;
62            result.push(value);
63        });
64        return result;
65    }
66
67    /**
68     * @param {string|string[]} urls 下载地址;无真实文件时可传页面标识 URL
69     * @param {{ onSuccess?: function }} [options] onSuccess:提交成功后执行(如图表导出),不再走文件下载
70     */
71    function openModal(urls, options) {
72        options = options || {};
73        pendingAction = typeof options.onSuccess === 'function' ? options.onSuccess : null;
74        pendingUrls = normalizeUrls(urls);
75        if (!pendingUrls.length && pendingAction && options.fileUrl) {
76            pendingUrls = normalizeUrls(options.fileUrl);
77        }
78        if (!pendingUrls.length) {
79            pendingAction = null;
80            return;
81        }
82        if (nameInput) {
83            nameInput.value = '';
84        }
85        if (emailInput) {
86            emailInput.value = '';
87        }
88        showError('');
89        setEmailPlaceholder();
90        root.removeAttribute('hidden');
91        root.setAttribute('aria-hidden', 'false');
92        document.body.style.overflow = 'hidden';
93        if (nameInput) {
94            nameInput.focus();
95        }
96    }
97
98    function closeModal() {
99        root.setAttribute('hidden', '');
100        root.setAttribute('aria-hidden', 'true');
101        document.body.style.overflow = '';
102        pendingUrls = [];
103        pendingAction = null;
104        showError('');
105        if (submitBtn) {
106            submitBtn.disabled = false;
107        }
108    }
109
110    function isValidEmail(value) {
111        return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
112    }
113
114    function submitDownloadLead(payload) {
115        var apiUrl = (typeof Apis !== 'undefined' && Apis.storeDownloadRecord) ? Apis.storeDownloadRecord : '';
116        if (!apiUrl) {
117            return Promise.reject(new Error(t('data-msg-submit-fail', 'Submit failed. Please try again.')));
118        }
119
120        return fetch(apiUrl, {
121            method: 'POST',
122            headers: {
123                'Content-Type': 'application/json',
124                Accept: 'application/json'
125            },
126            body: JSON.stringify({
127                username: payload.name,
128                email: payload.email,
129                website: SITE_WEBSITE,
130                file_url: payload.file_urls[0],
131                file_urls: payload.file_urls,
132                landing_page: payload.page_url
133            })
134        }).then(function (res) {
135            return res.json().then(function (data) {
136                if (!res.ok || !data || data.code != 200) {
137                    var msg = data && data.msg ? data.msg : t('data-msg-submit-fail', 'Submit failed. Please try again.');
138                    throw new Error(typeof msg === 'string' ? msg : t('data-msg-submit-fail', 'Submit failed. Please try again.'));
139                }
140                return data;
141            });
142        });
143    }
144
145    function fileNameFromUrl(url) {
146        try {
147            var path = url.split('?')[0];
148            var name = decodeURIComponent(path.substring(path.lastIndexOf('/') + 1));
149            return name || 'download.pdf';
150        } catch (e) {
151            return 'download.pdf';
152        }
153    }
154
155    function clickDownloadLink(href, filename) {
156        var link = document.createElement('a');
157        link.href = href;
158        link.setAttribute('download', filename || 'download.pdf');
159        link.style.display = 'none';
160        document.body.appendChild(link);
161        link.click();
162        document.body.removeChild(link);
163    }
164
165    function downloadFile(url) {
166        var filename = fileNameFromUrl(url);
167
168        return fetch(url, { mode: 'cors', credentials: 'omit' })
169            .then(function (response) {
170                if (!response.ok) {
171                    throw new Error('Network response was not ok');
172                }
173                return response.blob();
174            })
175            .then(function (blob) {
176                var objectUrl = URL.createObjectURL(blob);
177                clickDownloadLink(objectUrl, filename);
178                window.setTimeout(function () {
179                    URL.revokeObjectURL(objectUrl);
180                }, 1500);
181            })
182            .catch(function () {
183                var iframe = document.createElement('iframe');
184                iframe.style.cssText = 'position:fixed;left:-9999px;width:1px;height:1px;opacity:0;pointer-events:none;';
185                iframe.setAttribute('aria-hidden', 'true');
186                iframe.src = url;
187                document.body.appendChild(iframe);
188                window.setTimeout(function () {
189                    if (iframe.parentNode) {
190                        iframe.parentNode.removeChild(iframe);
191                    }
192                }, 60000);
193            });
194    }
195
196    function downloadZip(urls) {
197        var apiUrl = (typeof Apis !== 'undefined' && Apis.downloadFilesZip) ? Apis.downloadFilesZip : '';
198        if (!apiUrl) {
199            return Promise.reject(new Error('zip unavailable'));
200        }
201
202        return fetch(apiUrl, {
203            method: 'POST',
204            headers: {
205                'Content-Type': 'application/json',
206                Accept: 'application/json'
207            },
208            body: JSON.stringify({ fileList: urls })
209        }).then(function (res) {
210            var contentType = (res.headers.get('content-type') || '').toLowerCase();
211            if (!res.ok || contentType.indexOf('application/zip') === -1) {
212                throw new Error('zip failed');
213            }
214            return res.blob();
215        }).then(function (blob) {
216            var objectUrl = URL.createObjectURL(blob);
217            clickDownloadLink(objectUrl, 'Samaterials-Download.zip');
218            window.setTimeout(function () {
219                URL.revokeObjectURL(objectUrl);
220            }, 1500);
221        });
222    }
223
224    function wait(ms) {
225        return new Promise(function (resolve) {
226            window.setTimeout(resolve, ms);
227        });
228    }
229
230    function downloadFiles(urls) {
231        if (urls.length === 1) {
232            return downloadFile(urls[0]);
233        }
234
235        return downloadZip(urls).catch(function () {
236            var chain = Promise.resolve();
237            urls.forEach(function (url) {
238                chain = chain.then(function () {
239                    return downloadFile(url).then(function () {
240                        return wait(400);
241                    });
242                });
243            });
244            return chain;
245        });
246    }
247
248    document.addEventListener('click', function (event) {
249        var trigger = event.target.closest('.js-download-gate');
250        if (trigger) {
251            event.preventDefault();
252            var url = trigger.getAttribute('data-download-url') || trigger.getAttribute('href') || '';
253            openModal(url);
254            return;
255        }
256
257        if (event.target.closest('[data-download-gate-close]')) {
258            closeModal();
259        }
260    });
261
262    document.addEventListener('keydown', function (event) {
263        if (event.key === 'Escape' && !root.hasAttribute('hidden')) {
264            closeModal();
265        }
266    });
267
268    if (form) {
269        form.addEventListener('submit', function (event) {
270            event.preventDefault();
271            var name = (nameInput && nameInput.value || '').trim();
272            var email = (emailInput && emailInput.value || '').trim();
273
274            if (!name) {
275                showError(t('data-msg-name-required', 'Your Name is required'));
276                if (nameInput) {
277                    nameInput.focus();
278                }
279                return;
280            }
281            if (!email) {
282                showError(t('data-msg-email-required', 'Your Email is required'));
283                if (emailInput) {
284                    emailInput.focus();
285                }
286                return;
287            }
288            if (!isValidEmail(email)) {
289                showError(t('data-msg-email-invalid', 'Please enter a valid email address.'));
290                if (emailInput) {
291                    emailInput.focus();
292                }
293                return;
294            }
295            if (!pendingUrls.length) {
296                showError(t('data-msg-file-missing', 'Download file is missing.'));
297                return;
298            }
299
300            var payload = {
301                name: name,
302                email: email,
303                website: SITE_WEBSITE,
304                file_urls: pendingUrls.slice(),
305                page_url: window.location.href,
306                source: root.getAttribute('data-source') || ''
307            };
308
309            if (submitBtn) {
310                submitBtn.disabled = true;
311            }
312            showError('');
313
314            var urls = pendingUrls.slice();
315            var action = pendingAction;
316
317            // 图表 CSV/XLS 等需在「提交」点击的同步调用栈里触发下载,
318            // 否则 fetch 之后浏览器会丢掉 user gesture,拦截 blob 下载。
319            if (action) {
320                try {
321                    action();
322                } catch (err) {
323                    showError(err && err.message ? err.message : t('data-msg-submit-fail', 'Submit failed. Please try again.'));
324                    if (submitBtn) {
325                        submitBtn.disabled = false;
326                    }
327                    return;
328                }
329                submitDownloadLead(payload)
330                    .then(function () {
331                        closeModal();
332                    })
333                    .catch(function (err) {
334                        // 文件可能已开始下载;仍提示提交失败,便于重试留资
335                        showError(err && err.message ? err.message : t('data-msg-submit-fail', 'Submit failed. Please try again.'));
336                        if (submitBtn) {
337                            submitBtn.disabled = false;
338                        }
339                    });
340                return;
341            }
342
343            submitDownloadLead(payload)
344                .then(function () {
345                    return downloadFiles(urls);
346                })
347                .then(function () {
348                    closeModal();
349                })
350                .catch(function (err) {
351                    showError(err && err.message ? err.message : t('data-msg-submit-fail', 'Submit failed. Please try again.'));
352                    if (submitBtn) {
353                        submitBtn.disabled = false;
354                    }
355                });
356        });
357    }
358
359    window.SamDownloadGate = {
360        open: openModal
361    };
362
363    setEmailPlaceholder();
364    if (typeof mobileMq.addEventListener === 'function') {
365        mobileMq.addEventListener('change', setEmailPlaceholder);
366    } else if (typeof mobileMq.addListener === 'function') {
367        mobileMq.addListener(setEmailPlaceholder);
368    }
369})();

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.