PageSourceSearch

https://ttisi.ru/nassets/3e6171b4/js/folder.js?v=1784624046

js ttisi.ru collected 2026-09-30 14:33:17 UTC 38,496 bytes, 1,103 lines download raw bytes

1jQuery.widget( 'gc.gcFileSelectorFolder', {
2	TYPE_IMAGE: 'image',
3	TYPE_VIDEO: 'video',
4	TYPE_VIDEO_NOTE: 'video_note',
5    TYPE_AUDIO: 'audio',
6	FOLDER_TYPE_CUSTOM: 'custom',
7	ORDER_TYPE_FILENAME: 'filename',
8	ORDER_TYPE_DATE: 'date',
9    options: {
10        owner: null,
11        folder: null,
12        selectedHash: null,
13		fileSizeLimit: '6GB',
14		fileSizeLimitWarning: (typeof Yii != 'undefined') ? Yii.t('common', 'Max size {n} GB', 6) : 'Max size 6 GB',
15		fileType: this.TYPE_IMAGE,
16		accept: '',
17		onUploadComplete: null,
18		folderType: null,
19		directoryId: null,
20		label: '',
21		directories: null,
22		directoriesSelectEl: null,
23		currentSearch: null,
24		fromWebinar: false,
25		extensions: null,
26		fileDialogLabel: null,
27		forceFileType: null
28	},
29
30    _create: function() {
31        var self = this;
32
33		var isFromWebinar = this.options.fromWebinar;
34
35		this.contentEl = this.options.contentEl
36		this.options.orderBy = self.ORDER_TYPE_DATE
37
38        this.element.addClass("folder")
39		var $folderHeader = $('<div class="folder-header"></div>')
40		this.folderHeaderEl = $folderHeader
41
42        $folderHeader.appendTo(this.element)
43
44		var $title = $('<div class="folder-title" contenteditable="false" data-filename="' +
45			this.options.label + '">' + this.options.label + '</div>');
46
47		if (self.options.folderType === self.FOLDER_TYPE_CUSTOM) {
48            $title.click(function () {
49                $(this).attr('contenteditable', 'true').attr("tabindex", -1).focus()
50				$(this).css('background-color', '#d2d2d2')
51            })
52
53			$title.on('keypress', function (e) {
54                if (e.keyCode === 13) {
55                    e.preventDefault(e)
56                    self.renameDirectory($(this))
57                }
58            })
59
60            $title.on('focusout', function () {
61                self.renameDirectory($(this))
62            })
63		}
64
65        $title.appendTo(isFromWebinar ? '' : $folderHeader)
66
67		var $line = $('<div></div>');
68        $line.css('display', isFromWebinar ? 'block' :  'none');
69
70        var typeLabel;
71		if (self.options.fileDialogLabel !== null) {
72			typeLabel = self.options.fileDialogLabel;
73		} else {
74			switch (self.options.fileType) {
75				case self.TYPE_VIDEO:
76				case self.TYPE_AUDIO:
77					typeLabel = self.options.fileType
78					break;
79				case self.TYPE_IMAGE:
80					typeLabel = 'image|images'
81					break;
82			}
83		}
84
85		var $searchFileInput = $('<div class="search-file-input">'
86			+ '<input class="files-search-input" placeholder="' + window.tt('common', 'Search by name') + '">'
87			+ '</div>'
88		);
89        var $searchZoomer = $('<div class="search-zoomer glyphicon glyphicon-search"></div>')
90		var $filesNumber = $('<div class="files-number">0 ' + window.tt('common', typeLabel, 0) + '</div>')
91		var $sortForm = $('<div class="sort-form"><select id="order-by">'
92			+ '<option value="date">' + window.tt('common', 'Date added') + '</option>'
93			+ '<option value="filename">' + window.tt('common', 'name') + '</option>'
94			+ '</select></div>'
95		);
96		var $deleteFolder = $('<div class="delete-folder-btn btn btn-secondary">'
97			+ window.tt('common', 'Delete folder')
98			+ '</div>'
99		);
100
101		self.filesNumber = $filesNumber
102
103		if (self.options.folderType === self.FOLDER_TYPE_CUSTOM) {
104            $deleteFolder.appendTo(isFromWebinar ? '' : $folderHeader)
105        }
106
107		$('<br>').appendTo(isFromWebinar ? '' : $folderHeader)
108        $filesNumber.appendTo(isFromWebinar ? '' : $folderHeader)
109        $sortForm.appendTo(isFromWebinar ? '' : $folderHeader)
110        $sortForm.change(function () {
111            self.changeOrder($(this))
112        })
113
114        $searchFileInput.appendTo(isFromWebinar ? '' : $folderHeader)
115        $searchZoomer.appendTo(isFromWebinar ? '' : $folderHeader)
116
117        $searchZoomer.click(function () {
118        	if ($searchFileInput.css('display') === 'none') {
119                $searchFileInput.fadeIn(1500);
120                $searchFileInput.css('display', 'inline-block')
121                $searchFileInput.find('.files-search-input').focus()
122			}
123        })
124
125        $deleteFolder.click(function () {
126			var isAgreed = confirm(window.tt('common', 'The folder and all files in it will be deleted.'));
127
128			if (isAgreed) {
129				self.deleteFolder()
130			}
131        })
132
133        $searchFileInput.focusout(function (e) {
134            self.searchFiles(e, $searchFileInput)
135        })
136
137        $searchFileInput.on("keypress", function (e) {
138            self.searchFiles(e, $searchFileInput)
139        })
140
141        // Left part of line
142		var $leftPartWrapper = $('<div></div>');
143		$leftPartWrapper.css('display', 'inline-block');
144		$leftPartWrapper.appendTo($line);
145		var $uploader = $('<div class="uploader"></div>');
146		$uploader.appendTo($leftPartWrapper);
147
148	    var $rightPartWrapper = $('<div></div>');
149	    $rightPartWrapper.css('display', 'inline-block').css('vertical-align', 'top').css('padding', '4px 0 0 20px');
150	    $rightPartWrapper.appendTo($line);
151
152		var $clear = $("<span class='clear-link'>" + window.tt('common', 'Reset') + "</span>")
153			.appendTo( $rightPartWrapper )
154			.click(function() {
155				self.selectFile( null );
156			});
157
158
159	    // Right part of line
160		if (self.options.fileType === self.TYPE_VIDEO) {
161			var $rightPart = self.buildUploadByLink();
162			$rightPart.appendTo($rightPartWrapper);
163		}
164
165		$line.appendTo($folderHeader);
166
167		var queueId = "queue" + this.options.folder;
168		var $queue = $("<div id='" + queueId + "' class='queues'></div>")
169        $queue.css('display', 'contents')
170		$queue.appendTo($folderHeader)
171		if (self.options.fileType === self.TYPE_VIDEO || self.options.fileType === self.TYPE_AUDIO) {
172            $queue.css('height', '5px')
173		}
174
175		this.filesEl = $('<div class="files-list"></div>')
176		this.filesEl.appendTo(this.element)
177
178		if (window.fileserviceTusFeatureEnabled && tus.isSupported) {
179			$('<div class="tus-uploader" />').appendTo($uploader).tusUploader({
180				'accept': self.options.accept,
181				'progressContainer': $queue,
182				'onSuccess': function (url, file) {
183					$.ajax({
184						url: url,
185						method: 'PUT',
186						success: function (data) {
187							self.fileUploaded(data.hash, data.filename);
188                            $queue.text('')
189							if (self.options.onUploadComplete) {
190								self.options.onUploadComplete(data.hash);
191							}
192						},
193					});
194				},
195			});
196
197			if ( self.options.fileSizeLimit && self.options.fileSizeLimitWarning ) {
198				$("<p class='text-muted'>" + self.options.fileSizeLimitWarning + "</p>")
199					.appendTo($uploader);
200			}
201
202			return;
203		}
204
205		var uploadifiveParams = {
206			auto: true,
207			buttonText: window.tt('common', 'Upload'),
208			width: 120,
209			id: "folderUpload" + self.options.folder,
210			queueID: queueId,
211			dnd: false,
212			removeCompleted: true,
213			multi: true,
214			fileSizeLimit: self.options.fileSizeLimit,
215			uploadScript: '/fileservice/widget/upload?deprecated=12'
216				+ '&secure=' + window.isEnabledSecureUpload
217				+ '&host=' + window.fileserviceUploadHost,
218			formData: {fullAnswer: true},
219			onUploadError: function(file, errorCode, errorMsg) {
220				alert("ERROR");
221			},
222			onUploadComplete: function(e, res) {
223				res = JSON.parse(res);
224				self.fileUploaded(res.hash, res.filename);
225                $queue.text('')
226
227				if (self.options.onUploadComplete) {
228					self.options.onUploadComplete(res.hash);
229				}
230			}
231		};
232
233
234		if (window.isEnabledSecureUpload && window.fileserviceUploadHost) {
235			uploadifiveParams.onUpload = getUploadifySecretLink;
236		}
237
238		if (this.options.accept) {
239			// Потому что fileType параметр uploadifive как-то не так работает.
240			// При указании video/* кучу видео форматов не позволяет выбрать
241			// Если перечислить конкретные расширения файлов (или mime-type'ы), то тоже не все их воспринимает
242			uploadifiveParams.onInit = function() {
243				var $fileInput = $('input[type=file]', self.element);
244				$fileInput.attr('accept', self.options.accept);
245			};
246			// При добавлении очередного файла создаются еще input'ы и по этому событию их можно обработать
247			uploadifiveParams.onUpload = function(filesToUpload, settings) {
248				var $fileInput = $('input[type=file]', self.element);
249				$fileInput.attr('accept', self.options.accept);
250
251				var secureDirectUploadUri = '/fileservice/widget/secure-direct-upload';
252				try {
253					var session = localStorage.getItem('session');
254					var requestParams = {"fs_ref": window.location.href};
255
256					if (session !== undefined && session != null){
257						var objSession = jQuery.parseJSON(session);
258
259						if (objSession.hasOwnProperty('user_id') && objSession.user_id != null) {
260							requestParams.fs_u = objSession.user_id;
261						} else {
262							requestParams.fs_u = -1;
263						}
264					}
265
266					secureDirectUploadUri += '?' + jQuery.param(requestParams);
267				} catch (err) {
268				}
269
270				$.ajax({
271					url: '/fileservice/widget/create-secret-link',
272					method: 'GET',
273					data: {
274						host: window.fileserviceUploadHost,
275						uri: secureDirectUploadUri,
276						expires: 600,
277						is_video: 1
278					},
279					success: function (data) {
280						if (data.link) {
281							settings.uploadScript = data.link;
282						}
283					},
284					error: function (http, message, exc) {sendCreateLinkError(http, message, exc);},
285					async: false
286				});
287			};
288		}
289
290        setTimeout( function() {
291			$uploader.uploadifive(uploadifiveParams);
292
293            if ( self.options.fileSizeLimit && self.options.fileSizeLimitWarning ) {
294                var warning = $("<p class='text-muted'>" + self.options.fileSizeLimitWarning + "</p>");
295                $uploader.parent().after( warning );
296            }
297
298        }, 100 )
299
300    },
301	deleteFolder: function() {
302		var self = this
303		var dirId = self.options.directoryId
304
305        ajaxCall("/pl/fileservice/directory/delete",
306            {directory_id: dirId}, {}, function() {
307                var $menuFolder = self.options.directories.find('.custom-directory[data-id=' + dirId + ']')
308                var $recentFolder = self.options.directories.find('.folder-picker-recent')
309                $menuFolder.remove()
310                $recentFolder.click()
311            })
312	},
313	searchFiles: function(e, el) {
314		var input = el.find('.files-search-input')[0].value.replace(/[`!#$%&*()|+\-=?:'<>\{\}\[\]\\\/]/gi, '');
315		var self = this
316
317        self.currentSearch = input
318
319        if (e.type === 'keypress' && e.keyCode !== 13) {
320        	return
321		}
322
323		if ((e.type === 'focusout' || (e.type === 'keypress' && e.keyCode === 13)) && input.trim() === '') {
324            self.loadFiles(this.options.folder, this.filesEl, null, '')
325
326            el.fadeOut()
327			return
328		}
329
330        self.loadFiles(this.options.folder, this.filesEl, null, input)
331	},
332	changeOrder: function(orderEl) {
333		var self = this
334        var orderBy = orderEl.find('#order-by')[0].value
335
336        self.options.orderBy = orderBy
337
338        self.loadFiles(this.options.folder, this.filesEl, null, this.currentSearch)
339    },
340	renameDirectory: function($title) {
341        var text = $title.text().trim().replace(/[`!#$%&*()|+\-=?:'<>\{\}\[\]\\\/]/gi, '');
342        var self = this
343
344        if (!text) {
345            $title.text($title.data('filename'));
346            $.toast(window.tt('common', 'Incorrect folder name'), {type: "danger"});
347        } else {
348            ajaxCall("/pl/fileservice/directory/rename",
349				{directory_id: self.options.directoryId, name: text}, {}, function() {
350             		var $menuFolder = self.options.directories.find('#folder-text-' + self.options.directoryId)
351
352					$menuFolder.text(text)
353					$menuFolder.attr('title', text)
354
355					$title.attr('contenteditable', 'false')
356                    $title.css('background-color', '')
357                    $title.text(text)
358			})
359        }
360	},
361	buildUploadByLink: function() {
362		var self = this;
363
364		var $content = $('<div></div>');
365
366		if (window.file_upload_blocking) {
367			return $content;
368		}
369
370		var $uploadByLink = $('<a href="javascript:void(0);">' + window.tt('common', 'Upload file by link') + '</a>');
371		$uploadByLink.appendTo($content);
372
373		var $input = $('<input placeholder="' + window.tt('common', 'Link from file storage') + '" type="url"/>');
374		$input.css('min-width', '300px');
375		$input.hide();
376		$input.appendTo($content);
377
378		var $btn = $('<a href="javascript:void(0);">' + window.tt('common', 'Upload') + '</a>');
379		$btn.css('padding-left', '10px');
380		$btn.hide();
381		$btn.appendTo($content);
382
383		var $loading = $('<img src="/public/img/loading.gif">');
384		$loading.hide();
385		$loading.appendTo($content);
386
387		$uploadByLink.click(function() {
388			$uploadByLink.hide();
389			$input.show();
390			$btn.show();
391		});
392
393		$btn.click(function() {
394			var url = $input.val();
395			if (url.length === 0) {
396				$.toast(window.tt('common', 'Enter a link'), {type: "danger"});
397				return;
398			}
399
400			var hashRegExp = /(?:AB\.)?[a-f\d]{32}\.[\w\d]+/gi;
401			var entries = url.match(hashRegExp);
402			if (!entries || entries.length === 0) {
403				$.toast(Yii.t('common', 'Only links from file storage can be inserted'), {type: "danger"});
404				return;
405			}
406
407
408			var hash = entries[entries.length - 1].toLowerCase();
409			var hashParts = hash.split('.');
410			var ext = hashParts[hashParts.length-1].toLowerCase();
411
412			var $existFileEl = self.getFileElByHash(hash);
413			if ($existFileEl.length > 0) {
414				$.toast(window.tt('common', 'This file is already in the current folder'), {type: "danger"});
415				return;
416			}
417
418			if (self.options.accept) {
419				var acceptExts = self.options.accept.toLowerCase().match(/[\w\d]+/g);
420				if (acceptExts && acceptExts.length > 0) {
421					if (!acceptExts.includes(ext)) {
422						$.toast(window.tt('common', 'The following formats are allowed') + ': ' + self.options.accept, {type: "danger"});
423						return;
424					}
425				}
426			}
427
428			$loading.show();
429			$btn.hide();
430			ajaxCall('/pl/fileservice/video/exists', {
431				'file-hash': hash
432			}, {}, function (response) {
433				if (response.exists) {
434					self.fileUploaded(hash, hash);
435					$input.val('');
436					$.toast(window.tt('common', 'File uploaded successfully'), {type: "success"});
437
438					if (self.options.onUploadComplete) {
439						self.options.onUploadComplete(hash);
440					}
441				} else {
442					$.toast(window.tt('common', 'File not found'), {type: "danger"});
443				}
444			}, function() {
445				$loading.hide();
446				$btn.show();
447			});
448		});
449
450		return $content;
451	},
452    fileUploaded: function( hash, filename ) {
453        var self = this;
454        var date = new Date().toISOString().split('T')[0] + ' ' + new Date().toISOString().split('T')[1].slice(0, 8)
455        var $fileEl = this.createFileEl( hash, false, filename, date, true)
456
457		// Если такой уже был, удаляем его перед добавлением такого же
458		var $existFileEl = self.getFileElByHash(hash);
459		$existFileEl.remove();
460
461        $fileEl.prependTo( this.filesEl );
462
463        self.markFile( hash, this.options.folder, true );
464
465        self.numberOfFiles = ++self.numberOfFiles
466        self.filesNumber.text(self.numberOfFiles + ' ' + self.getTypeLabel(self.numberOfFiles))
467
468        if ( this.options.owner.options.isMulti ) {
469            self.selectFile( hash )
470        }
471    },
472	getFileElByHash: function(hash) {
473		return $('.' + this.getFileClass(hash), this.filesEl);
474	},
475	getFileKey: function(hash) {
476		return hash.replace("\.", "");
477	},
478	getFileClass: function(hash) {
479		return 'file-' + this.getFileKey(hash);
480	},
481	initFileActionTooltip: function($el, title) {
482		$el.attr('data-toggle', 'tooltip');
483		$el.attr('title', title);
484		$el.tooltip({
485			container: '.files-list',
486			placement: 'auto',
487		});
488	},
489    createFileEl: function( hash, inFavorites, filename, datetime, can ) {
490		if (can === undefined) {
491			can = true;
492		}
493        var $el = $('<div class="file-item"></div>')
494        var self = this;
495        self.hash = hash;
496        filename = filename || hash
497        $el.draggable({
498            cursor: "move",
499            cursorAt: { top: -12, left: -20 },
500            helper: function(e) {
501                return $("<div class='ui-widget-header'>" + filename + "</div>");
502            },
503            start: function(e) {
504            },
505            drag: function(e) {
506				var dragger = $(this)
507				var $hoverEl = $(e.toElement)
508
509                if ($hoverEl.attr('class')) {
510                	var classes = $hoverEl.attr('class')
511                    var parentClasses = $($hoverEl.parentElement).attr('class')
512
513                    if (classes.includes('custom-directory') && self.options.directoryId !== $hoverEl.data('id')) {
514					}
515                }
516            },
517            stop: function(e) {
518                var dragger = $(this)
519                var $hoverEl = $(e.toElement)
520
521                if ($hoverEl.attr('class')) {
522                    var classes = $hoverEl.attr('class')
523
524                    if (classes.includes('custom-directory') && self.options.directoryId !== $hoverEl.data('id')) {
525						self.moveFile(hash, $hoverEl.data('id'), $el)
526                    }
527                }
528            }
529        });
530
531		var fileKey = self.getFileKey(hash);
532		var fileClass = self.getFileClass(hash);
533		$el.css('position', 'relative');
534		$el.data('key', fileKey);
535        $el.data('hash', hash)
536
537		$el.addClass(fileClass);
538		if (self.options.fileType === self.TYPE_VIDEO) {
539			$el.addClass('video-preview');
540		}
541        /*if ( this.options.owner.isFileSelected( fileKey ) ) {
542            $el.addClass("selected")
543        }*/
544
545        var $img = $('<img>')
546
547		var src = self.isVideo(this.options.fileType)
548			? getVideoThumbnailUrl(hash, 200, 150)
549			: getThumbnailUrl(hash, 200, 200);
550		$img.attr("src", src);
551		$img.data('hash', hash);
552        $img.appendTo($el)
553        var $titles = $('<div data-filename="' + filename + '" contenteditable="false" class="titles-text" id="' +
554			hash + '">' + filename + '</div>');
555
556        $titles.appendTo($el);
557
558        var $createDate = $('<div class="file-create-data">' + datetime + '</div>')
559        $createDate.appendTo($el)
560
561        var $bottomPanel = $('<div class="bottom-panel" style="display: flex; align-items: center;"></div>')
562        $bottomPanel.appendTo($el)
563        //$selectBtn = $('<button class="btn btn-select btn-primary">Выбрать</button>')
564        //$selectBtn.appendTo( $el )
565
566        var $hideBtn = $('<button class="btn btn-cancel text-left btn-sm btn-link file-action"><span class="glyphicon glyphicon-eye-close"></span></button>')
567		$hideBtn.appendTo($bottomPanel)
568		self.initFileActionTooltip($hideBtn, window.tt('common', 'Скрыть'));
569
570		let $textFileRemove = window.tt('common', 'Удаление через файловое хранилище недоступно без прав доступа');
571		let $colorFileRemove = 'gray';
572		if (window && window.canFileStorage === true) {
573			$textFileRemove = window.tt('common', 'Удалить в файловом хранилище');
574			$colorFileRemove = 'red';
575		}
576		if (can === true) {
577			var $cancelBtn = $('<a class="btn btn-remove text-left btn-sm btn-link file-action"><span style="color: ' + $colorFileRemove + ' " class="glyphicon glyphicon-trash"></span></a>')
578			$cancelBtn.appendTo($bottomPanel);
579			self.initFileActionTooltip($cancelBtn, window.tt('common', $textFileRemove));
580		}
581
582		var $downloadBtn = $('<button class="btn btn-link btn-download file-action"><span class="from-favorites glyphicon glyphicon-download-alt"></span></button>')
583		$downloadBtn.appendTo($bottomPanel)
584		self.initFileActionTooltip($downloadBtn, window.tt('common', 'Скачать'));
585        self.downloadBtn = $downloadBtn
586
587        var $cropBtn = $('<button class="btn btn-link btn-crop file-action"><span class="to-favorites glyphicon glyphicon-scissors"></span></button>');
588        $cropBtn.appendTo($bottomPanel);
589		self.initFileActionTooltip($cropBtn, window.tt('common', 'Обрезать'));
590        self.cropBtn = $cropBtn;
591
592		var $changeVideoPreviewBtn = '';
593		if (this.options.fileType === this.TYPE_VIDEO) {
594			$cropBtn.hide();
595
596			$changeVideoPreviewBtn = $('<button class="btn btn-link btn-change-video-preview file-action"><span class="to-favorites glyphicon glyphicon-edit"></span></button>');
597			$changeVideoPreviewBtn.appendTo($bottomPanel);
598			self.initFileActionTooltip($changeVideoPreviewBtn, window.tt('common', 'Сменить обложку'));
599			self.setVideoPreviewBtn = $changeVideoPreviewBtn;
600		}
601
602
603		var $allowRenameBtn = $('<button class="btn btn-sm btn-link btn-allow-rename file-action"><span class="glyphicon glyphicon-pencil"></span></button>')
604		$allowRenameBtn.appendTo($bottomPanel)
605		self.initFileActionTooltip($allowRenameBtn, window.tt('common', 'Переименовать'));
606		self.allowRename = $allowRenameBtn
607
608
609		var $applyRenameBtn = $('<button class="btn btn-sm btn-success-outline btn-apply-rename file-action"><span class="glyphicon glyphicon-ok"></span></button>')
610		$applyRenameBtn.css('display', 'none')
611		$applyRenameBtn.appendTo($bottomPanel)
612		self.initFileActionTooltip($applyRenameBtn, window.tt('common', 'Применить изменения'));
613		self.applyRenameBtn = $applyRenameBtn
614
615        var $moveFileBtn = $('<button class="btn btn-sm btn-link btn-move-file file-action"><span class="glyphicon glyphicon-copy"></span></button>')
616		$moveFileBtn.appendTo($bottomPanel)
617		self.initFileActionTooltip($moveFileBtn, window.tt('common', 'Переместить'));
618
619		var $favBtn = $('<button class="btn btn-link btn-favorites file-action"><span class="to-favorites glyphicon glyphicon-star-empty"></span><span class="from-favorites glyphicon glyphicon-star"></span></button>')
620		$favBtn.appendTo($bottomPanel)
621		self.initFileActionTooltip($favBtn, window.tt('common', 'Добавить в избранное'));
622		self.favBtn = $favBtn
623
624        //$coverBtn = $('<button class="btn btn-link btn-cover hidden"><span class="glyphicon glyphicon-picture"></span></button>')
625        //$coverBtn.appendTo( $el )
626
627        $titles.on("keypress", function (e) {
628            if (e.keyCode === 13) {
629                e.preventDefault(e)
630                var text = $titles.text().trim().replace(/[`!#$%&*()|+\-=?:'<>\{\}\[\]\\\/]/gi, '');
631                if (!text) {
632                    $(this).text($(this).data('filename'));
633					$.toast(window.tt('common', 'Incorrect file name'), {type: "danger"});
634                } else {
635                    self.renameFile(hash, text)
636                    $titles.attr('contenteditable', 'false');
637                    $titles.text(text);
638					$.toast(window.tt('common', 'File name updated'), {type: "success"});
639                    $applyRenameBtn.hide()
640                    $allowRenameBtn.show()
641                }
642            }
643        })
644
645        $titles.focusout(function () {
646            var text = $titles.text().trim().replace(/[`!#$%&*()|+\-=?:'<>\{\}\[\]\\\/]/gi, '');
647            if (!text) {
648                $(this).text($(this).data('filename'));
649				$.toast(window.tt('common', 'Incorrect file name'), {type: "danger"});
650            } else {
651                self.renameFile(hash, text)
652                $titles.attr('contenteditable', 'false');
653                $titles.text(text);
654				$.toast(window.tt('common', 'File name updated'), {type: "success"});
655                $applyRenameBtn.hide()
656                $allowRenameBtn.show()
657            }
658        })
659
660        if ( inFavorites ) {
661            self.favBtn.addClass('in-favorites');
662        }
663
664		var handleButtons = function( hash, $el, filename, $titles, self ) {
665            $favBtn = $el.find('.btn-favorites')
666            $cancelBtn = $el.find('.btn-cancel')
667			let $removeBtn = $el.find('.btn-remove')
668            //$coverBtn = $el.find('.btn-cover')
669			$allowRenameBtn = $el.find('.btn-allow-rename')
670			$applyRenameBtn = $el.find('.btn-apply-rename')
671			$moveFileBtn = $el.find('.btn-move-file')
672
673			var $btnSelect = $el.find('img')
674
675            $btnSelect.click( function() {
676                self.selectFile( hash )
677            })
678
679            function addBtnHandler( btn, handler ) {
680                btn.click( handler )
681            }
682			if (window && window.canFileStorage === true) {
683				$removeBtn.attr('href', '/pl/fileindex/file/view?hash=' + hash).attr('target', '_blank');
684			}
685
686            addBtnHandler( $favBtn, function() {
687                $favBtn = $(this)
688                var needPositive = ! $favBtn.hasClass("in-favorites");
689                self.markFile( hash, "favorites", needPositive, function( needPositive ) {
690                    if ( needPositive ) {
691                        $favBtn.addClass('in-favorites')
692                    }
693                    else {
694                        $favBtn.removeClass('in-favorites')
695                    }
696                })
697            });
698
699            addBtnHandler($cancelBtn, function() {
700				if (confirm(window.tt('common', 'Are you sure?'))) {
701                    self.markFile( hash, self.options.folder, false );
702                    $(this).parents('.file-item').remove()
703				}
704            })
705
706            addBtnHandler($downloadBtn, function() {
707                window.open( getDownloadUrl( hash ) );
708            })
709
710			addBtnHandler($allowRenameBtn, function() {
711				$allowRenameBtn = $(this)
712				$allowRenameBtn.hide()
713				$applyRenameBtn.show()
714
715				$titles.attr('contenteditable', 'true').attr("tabindex",-1).focus();
716			})
717
718			addBtnHandler($applyRenameBtn, function() {
719				$applyRenameBtn = $(this)
720                var text = $titles.text().trim().replace(/[`!#$%&*()|+\-=?:'<>\{\}\[\]\\\/]/gi, '');
721                if (!text) {
722                    $(this).text($(this).data('filename'));
723					$.toast(window.tt('common', 'Incorrect file name'), {type: "danger"});
724                } else {
725                    self.renameFile(hash, text)
726                    $titles.attr('contenteditable', 'false');
727                    $titles.text(text);
728					$.toast(window.tt('common', 'File name updated'), {type: "success"});
729                    $applyRenameBtn.hide()
730                    $allowRenameBtn.show()
731                }
732			})
733
734            addBtnHandler($cropBtn, function () {
735                var jcrop, data = {};
736
737                var active = window.gcModalActive();
738                active && active.hide();
739                var modal = window.gcModalFactory.create({
740                    onHide: function () {
741                        jcrop.destroy();
742                        active && active.show();
743                    }
744                });
745
746                var fixCrop = function(c)
747                {
748                    // console.log('x = '+c.x);
749                    // console.log('y = '+c.y);
750                    // console.log('x2 = '+c.x2);
751                    // console.log('y2 = '+c.y2);
752                    // console.log('w = '+c.w);
753                    // console.log('h = '+c.h);
754                    data.crop = c;
755                    data.image = {
756                        w : modal.getContentEl().find('img').width(),
757                        h : modal.getContentEl().find('img').height()
758                    };
759                    var $btn = modal.getModalEl().find('.btn-success');
760                    if (c.w && c.h) {
761                        $btn.removeAttr('disabled');
762                    }
763                    else {
764                        $btn.attr('disabled', 'disabled');
765                    }
766                    // console.log(data);
767                };
768
769
770                var $imgToEdit = $('<img>');
771                $imgToEdit.attr("src", getThumbnailUrl(hash, 550, ''));
772
773                var $editContent = $('<div style="width: 550px;margin: 0 auto;">');
774                $editContent.append($imgToEdit);
775				var $saveEditImageBtn = $('<button class="btn btn-success" style="margin: 5px;" disabled>'
776					+ window.tt('common', 'Save')
777					+ '</button>'
778				);
779				var $cancelEditImageBtn = $('<button class="btn" style="margin: 5px;">'+window.tt('common', 'Cancel')+'</button>');
780
781                $saveEditImageBtn.click(function () {
782                    ajaxCall('/pl/fileservice/widget/user-crop', {
783                        hash : hash,
784                        data : data
785                    }, {btn : $saveEditImageBtn}, function(response) {
786                        modal.hide();
787                        self.fileUploaded(response.hash, response.filename);
788                    });
789                });
790
791                $cancelEditImageBtn.click(function () {
792                    modal.hide();
793                });
794                var $editButtons = $('<div style="text-align: center">');
795                $editButtons.append($saveEditImageBtn).append($cancelEditImageBtn);
796                $editContent.append($editButtons);
797
798                modal.getModalEl().find('.modal-dialog').css('max-width', '600px');
799                modal.getContentEl().addClass("row-container");
800				modal.setTitle(window.tt('common', 'Crop image'));
801                modal.setContent($editContent);
802                modal.getContentEl().find('img').Jcrop({
803                    onChange: fixCrop,
804                    onSelect: fixCrop
805                }, function() {jcrop = this;});
806
807                modal.show();
808            });
809
810            addBtnHandler($moveFileBtn, function (e) {
811                var $existedIcon = $($moveFileBtn[0].parentElement).find('.move-directories')
812
813                if ($existedIcon.length > 0) {
814                    $existedIcon.remove()
815					return
816                }
817
818                var directoriesFilteredByType = $(this).parents('.file-dialog-layout').find('span.custom-directory')
819
820				var $directoriesSelectEl = $('<div class="move-directories"><select class="dir-select">'
821					+ '<option value="0">'+window.tt('common', 'Select folder')+'</option>'
822					+ '</select></div>'
823				);
824                var $selectEl = $directoriesSelectEl.find('.dir-select')
825
826				$.each(directoriesFilteredByType, function (i, element) {
827                    $selectEl.append('<option value="' +
828						$(element).data('id') + '">' +
829						$(element).text() + '</option>')
830                })
831
832
833                $directoriesSelectEl.css('display', 'none')
834                $directoriesSelectEl.css('z-index', '1')
835                $directoriesSelectEl.css('max-height', '100px')
836                $directoriesSelectEl.css('overflow-y', 'auto')
837
838                self.directoriesSelectEl = $directoriesSelectEl
839
840                $selectEl.change(function () {
841                    var selectedOption = $(this).val()
842                    var hash = $($directoriesSelectEl[0].parentNode.parentNode).data('hash')
843                    var fileEl = $($directoriesSelectEl[0].parentNode.parentNode)
844
845                    self.moveFile(hash, selectedOption, fileEl)
846
847                    $directoriesSelectEl.remove()
848                })
849
850                var $directoriesSelect = self.directoriesSelectEl
851
852                $directoriesSelect.appendTo($($moveFileBtn[0].parentElement))
853
854                $directoriesSelect.css({
855                        'left': '170px',
856						'display': 'block',
857						'position': 'absolute'
858				})
859
860                $directoriesSelect.find('.dir-select').val(0)
861
862                $directoriesSelect.focusout(function () {
863                    $directoriesSelectEl.remove()
864                })
865
866            })
867
868			if ($changeVideoPreviewBtn) {
869				$changeVideoPreviewBtn.click(function() {
870					window.gcSelectVideoFrame({
871						hash: hash,
872						callback: function (hash) {
873							self.fileUploaded(hash);
874						},
875						overrideDefaultFolder: 'frames'
876					});
877				});
878			}
879
880            /*addBtnHandler($coverBtn, function() {
881                if (confirm("Вы уверены?")) {
882                    self.markFile( hash, "cover", true );
883                }
884            })*/
885        }
886
887        handleButtons( hash, $el, filename, $titles, self  )
888
889		$titles.click(function () {
890			$allowRenameBtn.hide()
891			$applyRenameBtn.show()
892
893			$(this).attr('contenteditable', 'true').attr("tabindex", -1).focus();
894		})
895
896        return $el;
897    },
898    markFile: function( hash, mark, positive, callback ) {
899		var self = this
900        var data = {
901            hash: hash,
902            mark: mark,
903            positive: positive ? 1 : 0,
904			fileType: self.options.fileType,
905			forceFileType: self.options.forceFileType
906        }
907
908        if (mark !== 'favorites' && this.options.folderType === 'custom') {
909            data.directory_id = this.options.directoryId;
910            data.mark = 'custom';
911        }
912
913        if (!positive) {
914            var newFileNumber = self.numberOfFiles - 1
915            self.filesNumber.text(newFileNumber + ' ' + self.getTypeLabel(newFileNumber))
916		}
917
918        ajaxCall( "/pl/fileservice/widget/mark", data, {}, function() {
919            if ( callback ) {
920                callback( positive );
921            }
922        } )
923    },
924	renameFile: function(hash, filename, callback) {
925		var data = {
926			hash: hash,
927			filename: filename,
928			positive: 1,
929		}
930
931		ajaxCall("/pl/fileservice/widget/rename", data, {}, function() {
932			if (callback) {
933				callback();
934			}
935		})
936	},
937    setSelectedHash: function( selectedHash ) {
938    	this.loadFiles( this.options.folder, this.filesEl, selectedHash )
939    },
940    loadFiles: function(mark, $toElement, selectedHash, substring) {
941    	let key = mark + selectedHash;
942    	if ( this.loading && this.loading == key ) {
943    		return;
944	    }
945        var self = this;
946        $toElement.empty();
947
948        if ( this.options.folder != "recent" ) {
949            selectedHash = null;
950        }
951
952        if (this.options.folderType === 'custom') {
953            mark = this.options.folderType;
954		}
955
956        this.loading = key;
957
958		var params = {
959			mark: mark,
960			selectedHash: selectedHash,
961			fileType: this.options.fileType ? this.options.fileType : this.TYPE_IMAGE,
962			directoryId: self.options.directoryId,
963			orderBy: self.options.orderBy,
964			extensions: self.options.extensions
965		};
966
967		ajaxCall( '/pl/fileservice/widget/get-by-mark', params, {}, function( response ) {
968            var filesNumber = 0;
969
970            for ( key in response.data.files ) {
971                var file = response.data.files[key];
972
973                if (substring && !file.filename.toLowerCase().includes(substring.toLowerCase())) {
974					continue
975                }
976
977                if (!file.created_at) {
978					file.created_at = new Date().toISOString().split('T')[0] + ' ' +
979						new Date().toISOString().split('T')[1].slice(0, 8)
980				}
981
982                var $fileEl = self.createFileEl(file.hash, file.inFavorites, file.filename, file.created_at, file.can)
983                $fileEl.appendTo( $toElement )
984                filesNumber++
985            }
986
987			self.numberOfFiles = filesNumber
988            self.filesNumber.text(filesNumber + ' ' + self.getTypeLabel(filesNumber))
989            self.loading = null;
990        } )
991    },
992	getTypeLabel: function(filesNumber) {
993		var self = this
994		var typeLabel;
995		if (self.options.fileDialogLabel !== null) {
996			typeLabel = self.options.fileDialogLabel;
997		} else {
998			switch (self.options.fileType) {
999				case self.TYPE_VIDEO:
1000					typeLabel = window.tt('common', 'video')
1001					break;
1002				case self.TYPE_AUDIO:
1003					typeLabel = window.tt('common', 'audio')
1004					break;
1005				case self.TYPE_IMAGE:
1006					typeLabel = window.tt('common', 'image|images', filesNumber)
1007					break;
1008			}
1009		}
1010
1011        return typeLabel
1012    },
1013    selectFile: function( hash ) {
1014        this.options.owner.fileSelected( hash )
1015    },
1016    init: function( selectedHash ) {
1017	    this.loadFiles(this.options.folder, this.filesEl, selectedHash, '')
1018    },
1019	checkNeedToRefresh: function() {
1020		var self = this;
1021		$('.file-item img', this.filesEl).each(function() {
1022			var $img = $(this);
1023			var hash = $img.data('hash');
1024			var src = self.isVideo(self.options.fileType)
1025				? getVideoThumbnailUrl(hash, 200, 150)
1026				: getThumbnailUrl(hash, 200, 200);
1027			if ($img.attr('src') !== src) {
1028				$img.attr('src', src);
1029			}
1030		});
1031	},
1032	numberCases: function(number, one, two, five) {
1033        const cases = [2, 0, 1, 1, 1, 2];
1034        const titles = [one, two, five];
1035        return titles[(number % 100 > 4 && number % 100 < 20) ? 2 : cases[(number % 10 < 5) ? number % 10 : 5]];
1036	},
1037	moveFile: function (hash, newDirectoryId, $el) {
1038		var self = this
1039
1040        ajaxCall("/pl/fileservice/widget/move-file",
1041            {hash: hash, directory_id: newDirectoryId}, {}, function() {
1042                if (self.options.folderType === 'custom' && response.success) {
1043                    $el.fadeOut()
1044                    var newFileNumber = self.numberOfFiles - 1
1045                    self.filesNumber.text(newFileNumber + ' ' + self.getTypeLabel(newFileNumber))
1046                }
1047		})
1048    },
1049    markFileById: function(id, mark, positive, callback, type) {
1050        var self = this
1051        var data = {
1052            id: id,
1053            mark: mark,
1054            positive: positive ? 1 : 0,
1055			type: type,
1056			forceFileType: self.options.forceFileType
1057        }
1058
1059        if (mark !== 'favorites' && this.options.folderType === 'custom') {
1060            data.directory_id = this.options.directoryId;
1061            data.mark = 'custom';
1062        }
1063
1064        if (!positive) {
1065            var newFileNumber = self.numberOfFiles - 1
1066            self.filesNumber.text(newFileNumber + ' ' + self.getTypeLabel(newFileNumber))
1067        }
1068
1069        ajaxCall( "/pl/fileservice/widget/mark-by-id", data, {}, function(response) {
1070            if (response.success) {
1071                var hash = response.hash;
1072                var filename = response.filename;
1073
1074                var date = new Date().toISOString().split('T')[0] + ' ' + new Date().toISOString().split('T')[1].slice(0, 8)
1075                var $fileEl = self.createFileEl(hash, false, filename, date)
1076
1077                // Если такой уже был, удаляем его перед добавлением такого же
1078                var $existFileEl = self.getFileElByHash(hash);
1079                $existFileEl.remove();
1080
1081                $fileEl.prependTo(self.filesEl);
1082
1083                self.numberOfFiles = ++self.numberOfFiles
1084                self.filesNumber.text(self.numberOfFiles + ' ' + self.getTypeLabel(self.numberOfFiles))
1085
1086            }
1087        } )
1088    },
1089	isVideo: function(type) {
1090		return type === this.TYPE_VIDEO || type === this.TYPE_VIDEO_NOTE;
1091	}
1092    /*
1093    showFileSelected: function( fileKey, isSelected ) {
1094        var $files = this.filesEl.find( '.file-' + fileKey )
1095        if ( isSelected ) {
1096            $files.addClass( "selected" )
1097        }
1098        else {
1099            $files.removeClass( "selected" )
1100        }
1101    }*/
1102
1103} );

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.