PageSourceSearch

https://akademia-excel.ru/nassets/3e6171b4/js/tus-uploader.js?v=1765633899

js akademia-excel.ru collected 2026-09-30 11:25:33 UTC 11,273 bytes, 300 lines download raw bytes

1jQuery.widget('gc.tusUploader', {
2    loadingFiles: [],
3    options: {
4        owner: null,
5        onSuccess: null,
6        progressContainer: null,
7        accept: null,
8    },
9    _create: function (callback) {
10        var self = this;
11
12        if (!tus.isSupported) {
13            console.log('Tus uploader not supported for this browser');
14            return;
15        }
16
17        const $uploaderContainer = this.element;
18        if (this.options.progressContainer === null) {
19            this.options.progressContainer = $uploaderContainer;
20        }
21
22        const $btn = $('<label class="tus-uploader-btn">' + Yii.t('common', 'Upload') + '</label>').appendTo($uploaderContainer);
23        const $input = $('<input type="file" multiple style="display: none;" />').appendTo($btn);
24        if (this.options.accept) {
25            $input.attr('accept', this.options.accept);
26        }
27
28        $input[0].addEventListener("change", function (e) {
29            /** @var {FileList} files */
30            const files = e.target.files;
31            for (let i = 0; i < files.length; i++) {
32                const file = files[i];
33                const fileKey = self.composeFileFingerprint(file);
34                if (self.loadingFiles.includes(fileKey)) {
35                    console.log('File already in upload queue');
36
37                    continue;
38                }
39                self.loadingFiles.push(fileKey);
40
41                self.uploadFile.call(self, file);
42            }
43        });
44    },
45    /** @param {File} file */
46    uploadFile: function (file) {
47        var self = this;
48
49        let $title = $('<div class="clearfix tus-uploader-title" />').html(file.name)
50            .appendTo(self.options.progressContainer);
51
52        const progress = this.createProgressBar(self.options.progressContainer);
53
54        const target = this.createUploadTarget(file);
55
56        let uploadIsActive = false;
57
58        const $resumeBtn = $('<button class="btn btn-default btn-xs">Продолжить</button>')
59            .click(function () {
60                if (!uploadIsActive) {
61                    self.resume(upload);
62                    onResume();
63                }
64            })
65            .hide().appendTo($title);
66
67        const $processBtn = $('<span class="glyphicon glyphicon-play tus-progress-icon" />')
68            .click(function () {
69                if (uploadIsActive) {
70                    upload.abort();
71                    onPause();
72                } else {
73                    self.resume(upload);
74                    onResume();
75                }
76            }).hide().appendTo(progress.progressBar);
77
78        const $alert = $('<span class="text-danger" />').hide().appendTo($title);
79
80        const onPause = function () {
81            uploadIsActive = false;
82            $resumeBtn.show();
83            $processBtn
84                .addClass('glyphicon-play')
85                .removeClass('glyphicon-pause')
86                .attr('title', 'Продолжить').show();
87            progress.pause();
88        };
89        const onResume = function () {
90            uploadIsActive = true;
91            $resumeBtn.hide();
92            $processBtn
93                .addClass('glyphicon-pause')
94                .removeClass('glyphicon-play')
95                .attr('title', 'Остановить').show();
96            $alert.hide().html('');
97            progress.active();
98        };
99
100        var hasOnlineListener = false;
101
102        var chunkUploadStart = new Date();
103        const chunkSizeDefault = 10000000;
104        const chunkSizeMultiplier = 2;
105        const chunkSizeMax = 1000000000;
106        const chunkUploadSuccessTime = 15;
107
108        // Create a new tus upload
109        const upload = new tus.Upload(file, {
110            endpoint: target,
111            chunkSize: chunkSizeDefault,
112            retryDelays: [200, 1000, 10000, 60000],
113                metadata: {
114                    filename: file.name,
115                    filetype: file.type
116                },
117                removeFingerprintOnSuccess: true,
118            fingerprint: function fingerprint(file, options, callback) {
119                // removed options.endpoint from original
120                return callback(
121                    null,
122                    self.composeFileFingerprint(file)
123                );
124            },
125            onError: function (error) {
126                this.chunkSize = chunkSizeDefault;
127                if (navigator.onLine === false) {
128                    $alert.html('Нет подключения к сети').show();
129
130                    if (hasOnlineListener === false) {
131                            hasOnlineListener = true;
132                            window.addEventListener('online', function (e) {
133                                hasOnlineListener = false;
134                                self.resume(upload);
135                                onResume();
136                            }, {
137                                once: true,
138                            });
139                    }
140                } else {
141                    $alert.html('Ошибка загрузки').show();
142                }
143
144                if (error.originalRequest) {
145                    onPause();
146                }
147
148                progress.failed();
149            },
150            onProgress: function (bytesUploaded, bytesTotal) {
151                let percentage = (bytesUploaded / bytesTotal * 100).toFixed(2);
152                progress.setPercent(percentage);
153            },
154            onChunkComplete: function (chunkSize, bytesAccepted, bytesTotal) {
155                let now = new Date();
156                let chunkUploadDuration = (now - chunkUploadStart) / 1000;
157                let uploadSpeed = Math.round(chunkSize / chunkUploadDuration / (1024 * 1024) * 100) / 100;
158                console.debug("Uploaded chunk " + chunkSize + " (" + bytesAccepted + " / " + bytesTotal + "), duration: " + chunkUploadDuration + ", speed: " + uploadSpeed);
159                chunkUploadStart = now;
160                // increase chunk size for fast upload
161                if (chunkUploadDuration < chunkUploadSuccessTime && this.chunkSize < chunkSizeMax) {
162                    this.chunkSize = Math.round(this.chunkSize * chunkSizeMultiplier);
163                    console.debug("Increase chunk size " + chunkSize + " -> " + this.chunkSize);
164                }
165                // decrease chunk size for slow upload
166                if (chunkUploadDuration > chunkUploadSuccessTime * 2 && this.chunkSize > chunkSizeDefault) {
167                    this.chunkSize = Math.round(this.chunkSize / chunkSizeMultiplier);
168                    console.debug("Decrease chunk size " + chunkSize + " -> " + this.chunkSize);
169                }
170            },
171            onSuccess: function () {
172                $processBtn.hide();
173                progress.finish();
174                if (self.options.onSuccess) {
175                    self.options.onSuccess(upload.url, upload.file);
176                }
177            },
178        });
179
180        // Start the upload
181        upload.start();
182        onResume();
183    },
184    resume: function (upload) {
185        try {
186            // update secure link
187            if (upload.url) {
188                const uploadUrl = new URL(upload.url);
189                if (uploadUrl.searchParams.get('e') - ((new Date()).getTime() / 1000) < 3600) {
190                    const newUrl = this.createUploadTarget(upload.file, uploadUrl.href, uploadUrl.host);
191                    if (newUrl) {
192                        upload.url = newUrl;
193                    }
194                }
195            }
196        } catch (e) {
197            console.log(e);
198        }
199
200        upload.start();
201    },
202    /** @param {File} file */
203    composeFileFingerprint: function (file) {
204        return ["tus-br", file.name, file.type, file.size, file.lastModified].join("-");
205    },
206    /**
207     * @param {File} fileInfo
208     * @param {String} [uri]
209     * @param {String} [host]
210     */
211    createUploadTarget: function (fileInfo, uri, host) {
212        let target;
213        var tusUri = '/fileservice/tus';
214
215        try {
216            var session = localStorage.getItem('session');
217            var requestParams = {"fs_ref": window.location.href};
218
219            if (session !== undefined && session != null){
220                var objSession = jQuery.parseJSON(session);
221
222                if (objSession.hasOwnProperty('user_id') && objSession.user_id != null) {
223                    requestParams.fs_u = objSession.user_id;
224                } else {
225                    requestParams.fs_u = -1;
226                }
227            }
228
229            tusUri += '?' + jQuery.param(requestParams);
230        } catch (err) {
231        }
232
233        $.ajax({
234            url: '/fileservice/widget/create-secret-link',
235            method: 'GET',
236            data: {
237                host: host ? host : window.fileserviceUploadHost,
238                uri: uri ? uri : tusUri,
239                expires: 12 * 3600,
240                fileInfo: {
241                    name: fileInfo.name,
242                    size: fileInfo.size,
243                    type: fileInfo.type,
244                },
245            },
246            success: function (data) {
247                if (data.link) {
248                    target = data.link;
249                }
250            },
251            error: function (http, message, exc) {sendCreateLinkError(http, message, exc);},
252            async: false
253        });
254
255        return target;
256    },
257    createProgressBar: function (uploaderContainer) {
258        const progress = {
259            progressBarContainer: null,
260            progressBar: null,
261            progressText: null,
262            init: function (container) {
263                this.progressBarContainer = $('<div class="progress" />').appendTo(container);
264                this.progressBar = $('<div class="progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" />').appendTo(this.progressBarContainer);
265                this.progressText = $('<span />').appendTo(this.progressBar);
266            },
267            setPercent: function (value) {
268                this.progressBar.attr({
269                    'aria-valuenow': value,
270                });
271                this.progressBar.css({
272                    'width': value + '%',
273                });
274                this.progressText.html(value + '%');
275            },
276            active: function () {
277                this.progressBar
278                    .addClass("progress-bar-success progress-bar-striped active")
279                    .removeClass("progress-bar-warning progress-bar-danger");
280            },
281            pause: function () {
282                this.progressBar
283                    .addClass("progress-bar-warning")
284                    .removeClass("progress-bar-danger progress-bar-success progress-bar-striped active");
285            },
286            failed: function () {
287                this.progressBar
288                    .addClass("progress-bar-danger")
289                    .removeClass("progress-bar-warning progress-bar-success progress-bar-striped active");
290            },
291            finish: function () {
292                this.progressBar
293                    .removeClass("progress-bar-striped active");
294            },
295        };
296        progress.init(uploaderContainer);
297
298        return progress;
299    },
300});

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.