]> git.p6c8.net - jirafeau.git/blob - lib/functions.js.php
Config option to enforce "classic download" without HTML5 File Api
[jirafeau.git] / lib / functions.js.php
1 <?php
2 /*
3 * Jirafeau, your web file repository
4 * Copyright (C) 2008 Julien "axolotl" BERNARD <axolotl@magieeternelle.org>
5 * Copyright (C) 2015 Jerome Jutteau <jerome@jutteau.fr>
6 * Copyright (C) 2024 Jirafeau project <https://gitlab.com/jirafeau> (see AUTHORS.md)
7 *
8 * This program is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU Affero General Public License as
10 * published by the Free Software Foundation, either version 3 of the
11 * License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Affero General Public License for more details.
17 *
18 * You should have received a copy of the GNU Affero General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22 function template_js_preview_link()
23 {
24 ?>
25 if (!!document.getElementById('preview_link'))
26 {
27 document.getElementById('upload_finished_preview').style.display = 'none';
28 var preview_link_href = 'f.php?h=' + reference + '&p=1';
29 if (crypt_key.length > 0)
30 {
31 preview_link_href += '&k=' + crypt_key;
32 }
33
34 // Test if content can be previewed
35 type = document.getElementById('file_select').files[0].type;
36 if ((type.startsWith('image/')
37 || type.startsWith('audio')
38 || type.startsWith('text/plain')
39 || type.startsWith('video/'))
40 && !type.includes('image/svg+xml'))
41 {
42 document.getElementById('preview_link').href = preview_link_href;
43 document.getElementById('preview_link_text').innerHTML = web_root + preview_link_href;
44 document.getElementById('upload_finished_preview').style.display = '';
45 }
46 }
47 <?php
48 }
49 function template_js_download_page()
50 {
51 ?>
52 // Download page
53 var download_link_href = 'f.php?h=' + reference;
54 if (crypt_key.length > 0)
55 {
56 download_link_href += '&k=' + crypt_key;
57 }
58 if (!!document.getElementById('upload_finished_download_page'))
59 {
60 document.getElementById('upload_link').href = download_link_href;
61 document.getElementById('upload_link_text').innerHTML = web_root + download_link_href;
62 }
63 <?php
64 }
65 function template_js_email_link()
66 {
67 ?>
68 // Email link
69 var b = encodeURIComponent("<?php echo t("DL"); ?> \"" + filename + "\":") + "%0D" + "%0A";
70 b += encodeURIComponent(web_root + download_link_href) + "%0D" + "%0A";
71 if (false == isEmpty(date))
72 {
73 b += "%0D" + "%0A" + encodeURIComponent("<?php echo t("VALID_UNTIL"); ?>: " + date.format('YYYY-MM-DD hh:mm (GMT O)')) + "%0D" + "%0A";
74 document.getElementById('upload_link_email').href = "mailto:?body=" + b + "&subject=" + encodeURIComponent(filename);
75 }
76 <?php
77 }
78 function template_js_delete_link()
79 {
80 ?>
81 // Delete link
82 var delete_link_href = 'f.php?h=' + reference + '&d=' + delete_code;
83 document.getElementById('delete_link').href = delete_link_href;
84 document.getElementById('delete_link_text').innerHTML = web_root + delete_link_href;
85 <?php
86 }
87 function template_js_direct_download()
88 {
89 ?>
90 // Direct download link
91 var direct_download_link_href = 'f.php?h=' + reference + '&d=1';
92 if (crypt_key.length > 0)
93 {
94 direct_download_link_href += '&k=' + crypt_key;
95 }
96 document.getElementById('direct_link').href = direct_download_link_href;
97 document.getElementById('direct_link_text').innerHTML = web_root + direct_download_link_href;
98 <?php
99 }
100 function template_js_date_function()
101 {
102 ?>
103 // Validity date
104 if (isEmpty(date))
105 {
106 document.getElementById('date').style.display = 'none';
107 }
108 else {
109 document.getElementById('date').innerHTML =
110 '<span class="datetime"'
111 + 'data-datetime="'
112 + dateToUtcString(date) + ' (GMT"'
113 + 'title="'
114 + dateToUtcString(date) + ' (GMT)">'
115 + date.format('YYYY-MM-DD hh:mm (GMT O)')
116 + '</span>';
117 document.getElementById('date').style.display = '';
118 }
119 <?php
120 }
121
122
123
124
125 header('Content-Type: text/javascript');
126 define('JIRAFEAU_ROOT', dirname(__FILE__) . '/../');
127
128 require(JIRAFEAU_ROOT . 'lib/settings.php');
129 require(JIRAFEAU_ROOT . 'lib/functions.php');
130 require(JIRAFEAU_ROOT . 'lib/lang.php');
131
132 ?>
133 // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later
134 var web_root = "<?php echo $cfg['web_root']; ?>";
135
136 var lang_array = <?php echo json_lang_generator(null); ?>;
137 var lang_array_fallback = <?php echo json_lang_generator("en"); ?>;
138
139 function translate (expr) {
140 if (lang_array.hasOwnProperty(expr)) {
141 var e = lang_array[expr];
142 if (!isEmpty(e))
143 return e;
144 }
145 if (lang_array_fallback.hasOwnProperty(expr)) {
146 var e = lang_array_fallback[expr];
147 if (!isEmpty(e))
148 return e;
149 }
150 return "FIXME: " + expr;
151 }
152
153 function isEmpty(str) {
154 return (!str || 0 === str.length);
155 }
156
157 // Extend date object with format method
158 Date.prototype.format = function(format) {
159 format = format || 'YYYY-MM-DD hh:mm';
160
161 var zeropad = function(number, length) {
162 number = number.toString();
163 length = length || 2;
164 while(number.length < length)
165 number = '0' + number;
166 return number;
167 },
168 formats = {
169 YYYY: this.getFullYear(),
170 MM: zeropad(this.getMonth() + 1),
171 DD: zeropad(this.getDate()),
172 hh: zeropad(this.getHours()),
173 mm: zeropad(this.getMinutes()),
174 O: (function() {
175 localDate = new Date;
176 sign = (localDate.getTimezoneOffset() > 0) ? '-' : '+';
177 offset = Math.abs(localDate.getTimezoneOffset());
178 hours = zeropad(Math.floor(offset / 60));
179 minutes = zeropad(offset % 60);
180 return sign + hours + ":" + minutes;
181 })()
182 },
183 pattern = '(' + Object.keys(formats).join(')|(') + ')';
184
185 return format.replace(new RegExp(pattern, 'g'), function(match) {
186 return formats[match];
187 });
188 };
189
190 function dateFromUtcString(datestring) {
191 // matches »YYYY-MM-DD hh:mm«
192 var m = datestring.match(/(\d+)-(\d+)-(\d+)\s+(\d+):(\d+)/);
193 return new Date(Date.UTC(+m[1], +m[2] - 1, +m[3], +m[4], +m[5], 0));
194 }
195
196 function dateFromUtcTimestamp(datetimestamp) {
197 return new Date(parseInt(datetimestamp) * 1000)
198 }
199
200 function dateToUtcString(datelocal) {
201 return new Date(
202 datelocal.getUTCFullYear(),
203 datelocal.getUTCMonth(),
204 datelocal.getUTCDate(),
205 datelocal.getUTCHours(),
206 datelocal.getUTCMinutes(),
207 datelocal.getUTCSeconds()
208 ).format();
209 }
210
211 function dateToUtcTimestamp(datelocal) {
212 return (Date.UTC(
213 datelocal.getUTCFullYear(),
214 datelocal.getUTCMonth(),
215 datelocal.getUTCDate(),
216 datelocal.getUTCHours(),
217 datelocal.getUTCMinutes(),
218 datelocal.getUTCSeconds()
219 ) / 1000);
220 }
221
222 function convertAllDatetimeFields() {
223 datefields = document.getElementsByClassName('datetime')
224 for(var i=0; i<datefields.length; i++) {
225 dateUTC = datefields[i].getAttribute('data-datetime');
226 datefields[i].setAttribute('title', dateUTC + ' (GMT)');
227 datefields[i].innerHTML = dateFromUtcString(dateUTC).format('YYYY-MM-DD hh:mm (GMT O)');
228 }
229 }
230
231 // evil copy and paste from show_link - TODO refactor show link
232 function show_link_from_php (file_name,file_type, reference, delete_code, crypt_key, date)
233 {
234 <?php template_js_download_page(); ?>
235
236
237 var filename = file_name;
238 <?php template_js_email_link(); ?>
239 <?php template_js_delete_link(); ?>
240 <?php template_js_date_function(); ?>
241 var type = file_type;
242 <?php template_js_direct_download();?>
243 }
244
245
246
247 function show_link (reference, delete_code, crypt_key, date)
248 {
249 // Upload finished
250 document.getElementById('uploading').style.display = 'none';
251 document.getElementById('upload').style.display = 'none';
252 document.getElementById('upload_finished').style.display = '';
253 document.title = "100% - <?php echo empty($cfg['title']) ? 'Jirafeau' : $cfg['title']; ?>";
254
255 <?php template_js_download_page(); ?>
256 // Email link
257 var filename = document.getElementById('file_select').files[0].name;
258
259 <?php template_js_email_link(); ?>
260
261 <?php template_js_delete_link(); ?>
262 <?php template_js_date_function(); ?>
263
264
265 // Preview link (if allowed)
266
267 var type = document.getElementById('file_select').files[0].type;
268 <?php template_js_preview_link(); ?>
269
270 <?php template_js_direct_download();?>
271
272
273 // Hide preview and direct download link if password is set
274 if (document.getElementById('input_key').value.length > 0)
275 {
276 if (!!document.getElementById('preview_link'))
277 document.getElementById('upload_finished_preview').style.display = 'none';
278 document.getElementById('upload_direct_download').style.display = 'none';
279 }
280 }
281
282 function show_upload_progression (percentage, speed, time_left)
283 {
284 document.getElementById('uploaded_percentage').innerHTML = percentage;
285 document.getElementById('uploaded_speed').innerHTML = speed;
286 document.getElementById('uploaded_time').innerHTML = time_left;
287 document.title = percentage + " - <?php echo empty($cfg['title']) ? 'Jirafeau' : $cfg['title']; ?>";
288 }
289
290 function hide_upload_progression ()
291 {
292 document.getElementById('uploaded_percentage').style.display = 'none';
293 document.getElementById('uploaded_speed').style.display = 'none';
294 document.getElementById('uploaded_time').style.display = 'none';
295 document.title = "<?php echo empty($cfg['title']) ? 'Jirafeau' : $cfg['title']; ?>";
296 }
297
298 function upload_progress (e)
299 {
300 if (e == undefined || e == null || !e.lengthComputable)
301 return;
302
303 // Init time estimation if needed
304 if (upload_time_estimation_total_size == 0)
305 upload_time_estimation_total_size = e.total;
306
307 // Compute percentage
308 var p = Math.round (e.loaded * 100 / e.total);
309 var p_str = ' ';
310 if (p != 100)
311 p_str = p.toString() + '%';
312 // Update estimation speed
313 upload_time_estimation_add(e.loaded);
314 // Get speed string
315 var speed_str = upload_time_estimation_speed_string();
316 speed_str = upload_speed_refresh_limiter(speed_str);
317 // Get time string
318 var time_str = chrono_update(upload_time_estimation_time());
319
320 show_upload_progression (p_str, speed_str, time_str);
321 }
322
323 function control_selected_file_size(max_size, error_str)
324 {
325 f_size = document.getElementById('file_select').files[0].size;
326 if (max_size > 0 && f_size > max_size * 1024 * 1024)
327 {
328 pop_failure(error_str);
329 document.getElementById('send').style.display = 'none';
330 }
331 else
332 {
333 // add class to restyle upload form in next step
334 document.getElementById('upload').setAttribute('class', 'file-selected');
335 // display options
336 document.getElementById('options').style.display = 'block';
337 document.getElementById('send').style.display = 'block';
338 document.getElementById('error_pop').style.display = 'none';
339 document.getElementById('send').focus();
340 }
341 }
342
343 function XHRErrorHandler(e)
344 {
345 var text = "${e.type}: ${e.loaded} bytes transferred"
346 console.log(text)
347 }
348
349 function pop_failure (e)
350 {
351 var text = "<p>An error occured";
352 if (typeof e !== 'undefined')
353 text += ": " + e;
354 text += "</p>";
355 document.getElementById('error_pop').innerHTML = e;
356
357 document.getElementById('uploading').style.display = 'none';
358 document.getElementById('error_pop').style.display = '';
359 document.getElementById('upload').style.display = '';
360 document.getElementById('send').style.display = '';
361 }
362
363 function add_time_string_to_date(d, time)
364 {
365 if(typeof(d) != 'object' || !(d instanceof Date))
366 {
367 return false;
368 }
369
370 if (time == 'minute')
371 {
372 d.setSeconds (d.getSeconds() + 60);
373 return true;
374 }
375 if (time == 'hour')
376 {
377 d.setSeconds (d.getSeconds() + 3600);
378 return true;
379 }
380 if (time == 'day')
381 {
382 d.setSeconds (d.getSeconds() + 86400);
383 return true;
384 }
385 if (time == 'week')
386 {
387 d.setSeconds (d.getSeconds() + 604800);
388 return true;
389 }
390 if (time == 'fortnight')
391 {
392 d.setSeconds (d.getSeconds() + 1209600);
393 return true;
394 }
395 if (time == 'month')
396 {
397 d.setSeconds (d.getSeconds() + 2592000);
398 return true;
399 }
400 if (time == 'quarter')
401 {
402 d.setSeconds (d.getSeconds() + 7776000);
403 return true;
404 }
405 if (time == 'year')
406 {
407 d.setSeconds (d.getSeconds() + 31536000);
408 return true;
409 }
410 return false;
411 }
412
413 function classic_upload (file, time, password, one_time)
414 {
415 // Delay time estimation init as we can't have file size
416 upload_time_estimation_init(0);
417
418 var req = new XMLHttpRequest ();
419 req.upload.addEventListener ("progress", upload_progress, false);
420 req.addEventListener ("error", XHRErrorHandler, false);
421 req.addEventListener ("abort", XHRErrorHandler, false);
422 req.onreadystatechange = function ()
423 {
424 if (req.readyState == 4 && req.status == 200)
425 {
426 var res = req.responseText;
427
428 // if response starts with "Error" then show a failure
429 if (/^Error/.test(res))
430 {
431 pop_failure (res);
432 return;
433 }
434
435 res = res.split ("\n");
436 var expiryDate = '';
437 if (time != 'none')
438 {
439 // convert time (local time + selected expiry date)
440 var localDatetime = new Date();
441 if(!add_time_string_to_date(localDatetime, time))
442 {
443 pop_failure ('Error: Date can not be parsed');
444 return;
445 }
446 expiryDate = localDatetime;
447 }
448
449 show_link (res[0], res[1], res[2], expiryDate);
450 }
451 else
452 {
453 pop_failure ("<?php echo t("ERR_OCC"); ?>");
454 }
455 }
456 req.open ("POST", 'script.php' , true);
457
458 var form = new FormData();
459 form.append ("file", file);
460 if (time)
461 form.append ("time", time);
462 if (password)
463 form.append ("key", password);
464 if (one_time)
465 form.append ("one_time_download", '1');
466 req.send (form);
467 }
468
469 function check_html5_file_api ()
470 {
471 <?php
472 if ($cfg['debug_enforce_classic_upload']) {
473 ?>
474 return false;
475 <?php
476 } else {
477 ?>
478 return window.File && window.FileReader && window.FileList && window.Blob;
479 <?php
480 }
481 ?>
482 }
483
484 var async_global_transfered = 0;
485 var async_global_file;
486 var async_global_ref = '';
487 var async_global_max_size = 0;
488 var async_global_time;
489 var async_global_transfering = 0;
490 var async_global_last_code;
491
492 function async_upload_start (max_size, file, time, password, one_time)
493 {
494 async_global_transfered = 0;
495 async_global_file = file;
496 async_global_max_size = max_size;
497 async_global_time = time;
498
499 var req = new XMLHttpRequest ();
500 req.addEventListener ("error", XHRErrorHandler, false);
501 req.addEventListener ("abort", XHRErrorHandler, false);
502 req.onreadystatechange = function ()
503 {
504 if (req.readyState == 4 && req.status == 200)
505 {
506 var res = req.responseText;
507
508 if (/^Error/.test(res))
509 {
510 pop_failure (res);
511 return;
512 }
513
514 res = res.split ("\n");
515 async_global_ref = res[0];
516 var code = res[1];
517 async_upload_push (code);
518 }
519 }
520 req.open ("POST", 'script.php?init_async' , true);
521
522 var form = new FormData();
523 form.append ("filename", async_global_file.name);
524 form.append ("type", async_global_file.type);
525 if (time)
526 form.append ("time", time);
527 if (password)
528 form.append ("key", password);
529 if (one_time)
530 form.append ("one_time_download", '1');
531
532 // Start time estimation
533 upload_time_estimation_init(async_global_file.size);
534
535 req.send (form);
536 }
537
538 function async_upload_progress (e)
539 {
540 if (e == undefined || e == null || !e.lengthComputable && async_global_file.size != 0)
541 return;
542
543 // Compute percentage
544 var p = Math.round ((e.loaded + async_global_transfered) * 100 / (async_global_file.size));
545 var p_str = ' ';
546 if (p != 100)
547 p_str = p.toString() + '%';
548 // Update estimation speed
549 upload_time_estimation_add(e.loaded + async_global_transfered);
550 // Get speed string
551 var speed_str = upload_time_estimation_speed_string();
552 speed_str = upload_speed_refresh_limiter(speed_str);
553 // Get time string
554 var time_str = chrono_update(upload_time_estimation_time());
555
556 show_upload_progression (p_str, speed_str, time_str);
557 }
558
559 function async_upload_push (code)
560 {
561 async_global_last_code = code;
562 if (async_global_transfered == async_global_file.size)
563 {
564 hide_upload_progression ();
565 async_upload_end (code);
566 return;
567 }
568 var req = new XMLHttpRequest ();
569 req.upload.addEventListener ("progress", async_upload_progress, false);
570 req.addEventListener ("error", XHRErrorHandler, false);
571 req.addEventListener ("abort", XHRErrorHandler, false);
572 req.onreadystatechange = function ()
573 {
574 if (req.readyState == 4)
575 {
576 if (req.status == 200)
577 {
578 var res = req.responseText;
579
580 // This error may be triggered when Jirafeau does not receive any file in POST.
581 // This may be due to bad php configuration where post_max_size is too low
582 // comparing to upload_max_filesize. Let's retry with lower file size.
583 if (res === "Error 23")
584 {
585 async_global_max_size = Math.max(1, async_global_max_size - 500);
586 async_upload_push (async_global_last_code);
587 return;
588 }
589 else if (/^Error/.test(res))
590 {
591 pop_failure (res);
592 return;
593 }
594
595 res = res.split ("\n");
596 var code = res[0]
597 async_global_transfered = async_global_transfering;
598 async_upload_push (code);
599 return;
600 }
601 else
602 {
603 // lower async_global_max_size and retry
604 // This can occurs in several cases:
605 // - Request Entity Too Large (413) due to server bad configuration relative to PHP configuration
606 // - Server Error (500) which can happen when PHP's `max_execution_time` is too low comparared to sent size
607 async_global_max_size = Math.max(1, parseInt (async_global_max_size * 0.5));
608 async_upload_push (async_global_last_code);
609 return;
610 }
611 }
612 }
613 req.open ("POST", 'script.php?push_async' , true);
614
615 var start = async_global_transfered;
616 var end = start + async_global_max_size;
617 if (end >= async_global_file.size)
618 end = async_global_file.size;
619 var blob = async_global_file.slice (start, end);
620 async_global_transfering = end;
621
622 var form = new FormData();
623 form.append ("ref", async_global_ref);
624 form.append ("data", blob);
625 form.append ("code", code);
626 req.send (form);
627 }
628
629 function async_upload_end (code)
630 {
631 var req = new XMLHttpRequest ();
632 req.addEventListener ("error", XHRErrorHandler, false);
633 req.addEventListener ("abort", XHRErrorHandler, false);
634 req.onreadystatechange = function ()
635 {
636 if (req.readyState == 4 && req.status == 200)
637 {
638 var res = req.responseText;
639
640 if (/^Error/.test(res))
641 {
642 pop_failure (res);
643 return;
644 }
645
646 res = res.split ("\n");
647 var expiryDate = '';
648 if (async_global_time != 'none')
649 {
650 // convert time (local time + selected expiry date)
651 var localDatetime = new Date();
652 if(!add_time_string_to_date(localDatetime, async_global_time)) {
653 pop_failure ('Error: Date can not be parsed');
654 return;
655 }
656 expiryDate = localDatetime;
657 }
658
659 show_link (res[0], res[1], res[2], expiryDate);
660 }
661 }
662 req.open ("POST", 'script.php?end_async' , true);
663
664 var form = new FormData();
665 form.append ("ref", async_global_ref);
666 form.append ("code", code);
667 req.send (form);
668 }
669
670 function upload (max_chunk_size)
671 {
672 var one_time_checkbox = document.getElementById('one_time_download');
673 var one_time = one_time_checkbox !== null ? one_time_checkbox.checked : false;
674 if (check_html5_file_api ())
675 {
676 async_upload_start (
677 max_chunk_size,
678 document.getElementById('file_select').files[0],
679 document.getElementById('select_time').value,
680 document.getElementById('input_key').value,
681 one_time
682 );
683 }
684 else
685 {
686 classic_upload (
687 document.getElementById('file_select').files[0],
688 document.getElementById('select_time').value,
689 document.getElementById('input_key').value,
690 one_time
691 );
692 }
693 }
694
695 var upload_time_estimation_total_size = 42;
696 var upload_time_estimation_transfered_size = 42;
697 var upload_time_estimation_transfered_date = 42;
698 var upload_time_estimation_moving_average_speed = 42;
699
700 function upload_time_estimation_init(total_size)
701 {
702 upload_time_estimation_total_size = total_size;
703 upload_time_estimation_transfered_size = 0;
704 upload_time_estimation_moving_average_speed = 0;
705 var d = new Date();
706 upload_time_estimation_transfered_date = d.getTime();
707 }
708
709 function upload_time_estimation_add(total_transfered_size)
710 {
711 // Let's compute the current speed
712 var d = new Date();
713 var speed = upload_time_estimation_moving_average_speed;
714 if (d.getTime() - upload_time_estimation_transfered_date != 0) {
715 speed = (total_transfered_size - upload_time_estimation_transfered_size)
716 / (d.getTime() - upload_time_estimation_transfered_date);
717 speed = Math.max(0, speed);
718 }
719 // Let's compute moving average speed on 30 values
720 var m = (upload_time_estimation_moving_average_speed * 29 + speed) / 30;
721 // Update global values
722 upload_time_estimation_transfered_size = total_transfered_size;
723 upload_time_estimation_transfered_date = d.getTime();
724 upload_time_estimation_moving_average_speed = m;
725 }
726
727 function upload_time_estimation_speed_string()
728 {
729 // speed ms -> s
730 var s = upload_time_estimation_moving_average_speed * 1000;
731 var res = 0;
732 var scale = '';
733 if (s <= 1000)
734 {
735 res = s.toString();
736 scale = "B/s";
737 }
738 else if (s < 1000000)
739 {
740 res = Math.floor(s/100) / 10;
741 scale = "KB/s";
742 }
743 else
744 {
745 res = Math.floor(s/100000) / 10;
746 scale = "MB/s";
747 }
748 if (res == 0)
749 return '';
750 return res.toString() + ' ' + scale;
751 }
752
753 function milliseconds_to_time_string (milliseconds)
754 {
755 function numberEnding (number) {
756 return (number > 1) ? translate ('PLURAL_ENDING') : '';
757 }
758
759 var temp = Math.floor(milliseconds / 1000);
760 var years = Math.floor(temp / 31536000);
761 if (years) {
762 return years + ' ' + translate ('YEAR') + numberEnding(years);
763 }
764 var days = Math.floor((temp %= 31536000) / 86400);
765 if (days) {
766 return days + ' ' + translate ('DAY') + numberEnding(days);
767 }
768 var hours = Math.floor((temp %= 86400) / 3600);
769 if (hours) {
770 return hours + ' ' + translate ('HOUR') + numberEnding(hours);
771 }
772 var minutes = Math.floor((temp %= 3600) / 60);
773 if (minutes) {
774 return minutes + ' ' + translate ('MINUTE') + numberEnding(minutes);
775 }
776 var seconds = temp % 60;
777 if (seconds) {
778 return seconds + ' ' + translate ('SECOND') + numberEnding(seconds);
779 }
780 return translate ('LESS_1_SEC');
781 }
782
783 function upload_time_estimation_time()
784 {
785 // Estimate remaining time
786 if (upload_time_estimation_moving_average_speed <= 0)
787 return 0;
788 return (upload_time_estimation_total_size - upload_time_estimation_transfered_size)
789 / upload_time_estimation_moving_average_speed;
790 }
791
792 var chrono_last_update = 0;
793 var chrono_time_ms = 0;
794 var chrono_time_ms_last_update = 0;
795 function chrono_update(time_ms)
796 {
797 var d = new Date();
798 var chrono = 0;
799 // Don't update too often
800 if (d.getTime() - chrono_last_update < 3000 &&
801 chrono_time_ms_last_update > 0)
802 chrono = chrono_time_ms;
803 else
804 {
805 chrono_last_update = d.getTime();
806 chrono_time_ms = time_ms;
807 chrono = time_ms;
808 chrono_time_ms_last_update = d.getTime();
809 }
810
811 // Adjust chrono for smooth estimation
812 chrono = chrono - (d.getTime() - chrono_time_ms_last_update);
813
814 // Let's update chronometer
815 var time_str = '';
816 if (chrono > 0)
817 time_str = milliseconds_to_time_string (chrono);
818 return time_str;
819 }
820
821 var upload_speed_refresh_limiter_last_update = 0;
822 var upload_speed_refresh_limiter_last_value = '';
823 function upload_speed_refresh_limiter(speed_str)
824 {
825 var d = new Date();
826 if (d.getTime() - upload_speed_refresh_limiter_last_update > 1500)
827 {
828 upload_speed_refresh_limiter_last_value = speed_str;
829 upload_speed_refresh_limiter_last_update = d.getTime();
830 }
831 return upload_speed_refresh_limiter_last_value;
832 }
833
834 // document.ready()
835 document.addEventListener('DOMContentLoaded', function(event) {
836 // Search for all datetime fields and convert the time to local timezone
837 convertAllDatetimeFields();
838 });
839
840 // Add copy event listeners
841 function copyLinkToClipboard(link_id) {
842 var focus = document.activeElement;
843 var e = document.getElementById(link_id);
844
845 var tmp = document.createElement("textarea");
846 document.body.appendChild(tmp);
847 tmp.textContent = e.href;
848 tmp.focus();
849 tmp.setSelectionRange(0, tmp.value.length);
850 document.execCommand("copy");
851 document.body.removeChild(tmp);
852
853 focus.focus();
854 }
855
856 function addCopyListener(button_id, link_id) {
857 if(document.getElementById(button_id)){
858 document.getElementById(button_id)
859 .addEventListener("click", function() {
860 copyLinkToClipboard(link_id);});
861 }
862 }
863
864 function copyTextToClipboard(text_id){
865 var copyText = document.getElementById(text_id);
866 copyText.select();
867 copyText.setSelectionRange(0, 99999);
868 navigator.clipboard.writeText(copyText.value);
869 }
870
871 function addTextCopyListener(button_id, text_id) {
872 if(document.getElementById(button_id)){
873 document.getElementById(button_id)
874 .addEventListener("click", function() {
875 copyTextToClipboard(text_id);});
876 }
877 }
878
879 function set_dark_mode() {
880 let steel_sheet = "<?php echo 'media/' . $cfg['dark_style'] . '/style.css.php'; ?>";
881 let shortcut_icon = "<?php echo 'media/' . $cfg['dark_style'] . '/favicon.ico'; ?>";
882 document.getElementById('stylesheet').href = steel_sheet;
883 document.getElementById('shortcut_icon').href = shortcut_icon;
884 }
885
886 function set_light_mode() {
887 let steel_sheet = "<?php echo 'media/' . $cfg['style'] . '/style.css.php'; ?>";
888 let shortcut_icon = "<?php echo 'media/' . $cfg['style'] . '/favicon.ico'; ?>";
889 document.getElementById('stylesheet').href = steel_sheet;
890 document.getElementById('shortcut_icon').href = shortcut_icon;
891 }
892
893 function color_scheme_preferences() {
894
895 let dark_mode_steel_sheet = "<?php echo 'media/' . $cfg['dark_style'] . '/style.css.php'; ?>"
896 if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
897 set_dark_mode();
898 } else {
899 set_light_mode();
900 }
901
902 // When user change its preference
903 window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', lightMode => {
904 lightMode.matches ? set_dark_mode() : set_light_mode();
905 });
906 }
907
908 // @license-end

patrick-canterino.de