PageSourceSearch

https://ohnet.ru/nassets/e8dff584/js/comments.js?v=1742806460

js ohnet.ru collected 2026-09-26 18:54:30 UTC 41,150 bytes, 1,263 lines download raw bytes

1$(function() {
2	var CHANNELS = {
3		NEW_COMMENT : 'new_comment'
4		,DELETE_COMMENT : 'delete_comment'
5		,PAGE_USERS : 'page_users'
6		,TYPING : 'typing'
7	};
8	var CONSTANTS = {
9		TYPING_INTERVAL : 3000
10		,MAX_SCROLL : 9007199254740991
11		,MAX_SHOW_TYPING_USERS : 3
12		,MAX_COMMENTS_PER_PAGE_DIRECTION_DESC : 100
13	};
14	var SHARED_SELECTORS = {
15		SCROLL_TO : '.comments-container'
16	};
17
18	var DomHelper = {};
19	DomHelper.findForms = function($el) {
20		return $el.find('.gc-comment-form');
21	};
22
23	DomHelper.removeCommentBlock = function($block) {
24		$block.hide(500);
25		setTimeout(function(){$block.remove()}, 500);
26	};
27
28	var TreeWidget = function($tree) {
29		var that = this;
30
31		runOneTimeOnElement($tree, 'gc-comments-tree-widget', function($el) {
32			var typingWidget = new TypingBlockWidget($el);
33
34			that.getTypingWidget = function() {
35				return typingWidget;
36			};
37
38			that.init($el);
39
40			return that;
41		});
42
43	};
44
45	TreeWidget.create = function($tree) {
46		return new this($tree)
47	};
48
49	TreeWidget.applyAccess = function (val, role, $html) {
50		if (!val) {// todo проверка валидности window.userInfo.isAdmin вынесение таких проверок в какое то одно место
51			$('[data-access-content-type=link]', $html).each(function(){
52				var $el = $(this);
53				$el.replaceWith($el.html());
54			});
55			$('[data-access*='+role+']', $html).each(function(){
56				var $el = $(this);
57				$el.remove();
58			});
59		}
60		else {
61			$('[data-access*='+role+']', $html).removeClass('hide');
62		}
63	};
64
65	/**
66	 * Показывает спрятанные комментарии (для isCollapsible)
67	 * @param obj
68	 * @returns {boolean}
69	 */
70	TreeWidget.moreComments = function(obj) {
71		var hiddenComments = $(obj).parent().nextAll('.comment-level.hide');
72		if ( hiddenComments.length ) {
73			hiddenComments
74				.slice(Math.max(hiddenComments.length - 10, 0), hiddenComments.length)
75				.removeClass('hide');
76		}
77		var length = $(obj).parent().nextAll('.comment-level.hide').length;
78
79		if (length < 1) {
80			$(obj).hide();
81			$(obj).parents('.comments-container:first').find('.more:first').removeClass('hide');
82		} else {
83			var hiddenCount = length;
84			if ($(obj).attr('data-current-count')) {
85				hiddenCount = parseInt($(obj).attr('data-current-count')) - length;
86			}
87			$(obj).find('span.count').html(hiddenCount);
88		}
89
90		return false;
91	};
92
93	TreeWidget.resize = function($tree) {
94		var scrollToBottomSelector = $tree.data('scroll-to-bottom');
95		if (scrollToBottomSelector) {
96			// debugger;
97			var $container = $tree.find('.comments-container:first');
98			var formOuterHeight = $tree.find('.gc-comment-form').outerHeight(true);
99			var height = $container.parent().height();
100			//console.log(height, formOuterHeight);
101			if ( $( window ).width() > 767 ) {
102				$container.css('height', (height - formOuterHeight) + 'px');
103			} else if ( $( window ).height() < ( $container.outerHeight(true) + 44 ) ) {
104				$container.css('height', ($( window ).height() - formOuterHeight - 44 ) + 'px');
105			}
106			else {
107				height = $container.parent().parent().height();
108				$container.css('height', (height - formOuterHeight) + 'px');
109			}
110
111
112			var first = $tree.find(SHARED_SELECTORS.SCROLL_TO).get(0);
113			first.scrollTop = first.scrollHeight;
114		}
115	};
116
117	function validateUrl(value) {
118		return /^\/\/.*fs.*\.(?:getcourse|gcrc).*\/fileservice\/.*\/thumbn
118ail\/.*$/i.test(value)
119			|| value === "/public/img/default_profile_50.png";
120	}
121
122	function makeThumbnail(thumbnail) {
123		let isValid = false;
124		const srcMatches = thumbnail.match(/src=["'](.*?)["']/);
125		if (srcMatches !== null) {
126			if (validateUrl(srcMatches[1])) {
127				isValid = true;
128			}
129		}
130		let src = isValid ? srcMatches[1] : "/public/img/default_profile_50.png";
131		return '<img loading="lazy" class="user-profile-image user-default-profile-image" src="'
132			+ src
133			+ '" width="50">';
134	}
135
136	TreeWidget.prototype = {
137		init : function($tree) {
138			this.initScroll($tree);
139			this.initForms($tree);
140			this.initComments($tree);
141			this.initCommentsQueue($tree);
142			this.initMoreLinks($tree);
143			this.initDownloadComments($tree);
144			this.initHelpdesk($tree);
145			this.initWebSocket($tree, window.accountUserWebSocketConnection);
146		}
147		,initScroll : function($tree) {
148			TreeWidget.resize($tree);
149			$(window).resize(function () {
150				TreeWidget.resize($tree);
151			});
152		}
153		,initForms : function($tree) {
154			var that = this;
155			DomHelper.findForms($tree).each(function (index, el) {
156				CommentFormWidget.create($(el));
157			});
158		}
159		,initComments : function($tree) {
160			var that = this;
161			$tree.find('.gc-comment').each(function (index, el) {
162				CommentWidget.create($(el));
163			});
164		}
165		,initCommentsQueue : function($tree) {
166			$tree.data('queue', []);
167			$tree.data('paused', 0);
168			var $button = $tree.find('.pause');
169			$tree.data('pause-button', $button);
170			$button.click(function() {
171				if (!$tree.data('paused')) {
172					$tree.data('paused', 1);
173					$(this).find('.text').html('<span class="fa fa-play"></span> Включить обновление');
174				}
175				else {
176					$tree.data('paused', 0);
177					var l = $tree.data('queue').length;
178					for(var i = 0; i < l; i++) {
179						var comment = $tree.data('queue').shift();
180						addComment(comment.objectId, comment.objectTypeId, comment.settings, comment.html);
181					}
182					$(this).find('.text').html('<span class="glyphicon glyphicon-pause"></span> ' + ( window.language == 'en' ? "Disable autoupdate" : "Отключить обновление" ));
183					$(this).find('.value').html('');
184				}
185			});
186
187			var chatCollapsed = window.gcGetCookie("chatCollapsed");
188
189			$tree.parent().find('.chat-collapse-btn').click( function(e ) {
190				$tree.parent().addClass('chat-collapsed')
191				window.gcSetCookie("chatCollapsed", 1)
192				$('.gc-both-main-content').removeClass('container-auto-width');
193
194				if ( $tree.parent().find('.chat-expand-btn').length == 0 ) {
195					$expandBtn = $('<div class="chat-expand-btn"><span class="glyphicon glyphicon-triangle-left"></span></div>')
196					$tree.parent().click(function () {
197						$(this).removeClass('chat-collapsed');
198						$('.gc-both-main-content').addClass('container-auto-width');
199						window.gcSetCookie("chatCollapsed", 0)
200					});
201
202					$expandBtn.appendTo($tree.parent());
203				}
204				e.stopPropagation();
205				e.preventDefault();
206			})
207			if ( chatCollapsed == 1 ) {
208				$tree.parent().find('.chat-collapse-btn').click();
209			}
210		}
211		,initMoreLinks : function($tree) {
212			$tree.find('.more a').click(function() {
213				var that = this;
214				$(this).parent().append('<i class="fa fa-spinner fa-spin"></i>');
215				var $container = $($(this).parents('.comments-container').get(0));
216				var settings = $($container.find('.settings-level-1').get(0)).find('textarea');
217				var asChat = $(this).data('as-chat');
218				var direction = $(this).data('direction');
219
220				ajaxCall('/cms/comment/getTree', {
221					settings : settings.html()
222					,page : $(this).data('page') + 1
223					,as_chat : asChat
224				}, {}, function(response) {
225					if (!response.hasNext) {
226						$(that).hide();
227					}
228					$(that).parent().find('.fa-spinner').remove();
229					$(that).data('page', $(that).data('page') + 1);
230					$(that).data('clicked', 1);
231					var $container = $($(that).parents('.comments-container').get(0));
232					var $commentsBlock = $($container.find('.comments').get(0));
233					var html = [];
234					$.each(response.comments, function(index, comment) {
235						html.push(comment.html);
236					});
237					$(that).html(response.linkText);
238					if (asChat) {
239						var currentTopElement = $commentsBlock.find('.gc-comment').first();
240						var currentBlockHeight = $commentsBlock.outerHeight();
241						$commentsBlock.prepend(html.join(''));
242						// скроллим окно сообщений к последнему прочитанному
243						$container.scrollTop( currentBlockHeight + currentTopElement.offset().top + 120 - 40 );
244					}
245					else {
246						if (direction === 'asc') {
247							$commentsBlock.prepend(html.reverse().join(''));
248						}
249						else {
250							$commentsBlock.append(html.join(''));
251						}
252					}
253				}, function() {
254					if ( typeof window.checkSelectedComment == 'function' ) {
255						window.checkSelectedComment();
256					}
257				});
258				return false;
259			})
260		}
261		,initWebSocket : function($tree, connection) {
262			if (!connection) {
263				return;
264			}
265			this.initWebSocketPageUsers($tree, connection);
266			this.initWebSocketTreeUsers($tree, connection);
267			this.initWebSocketNewComments($tree, connection);
268			this.initWebSocketUserTyping($tree, connection);
269		}
270		,initWebSocketPageUsers: function ($tree, connection) {
271            var treeChannelSuffix = $tree.data('tree-channel-suffix') || $tree.data('tree-id');
272            connection.subscribeChannel(CHANNELS.PAGE_USERS + connection.pageKey + '_' + treeChannelSuffix, function (message, json) {
273                $tree.find('.online-users-count').html(json.stat.connections.unique + (window.language == 'en' ? ' online' : ' онлайн'));
274			});
275		}
276		,initWebSocketTreeUsers: function ($tree, connection) {
277			var treeId = $tree.data('tree-id');
278			var treeChannelSuffix = $tree.data('tree-channel-suffix') || treeId;
279			connection.subscribeChannel(
280				CHANNELS.PAGE_USERS + '_' + treeChannelSuffix,
281				function (message, json) {
282					$('.' + treeId).each(function (index, el) {
283						var cnt = json.stat.connections.unique;
284						var $el = $(el);
285						var html = '';
286						if (cnt > 0) {
287							html = cnt + ' ' + Yii.t('common','online');
288						}
289						$el.find('.online-users-count').html(html);
290					});
291				}
292			);
293		}
294		,initWebSocketNewComments : function($tree, connection, oldTreeId) {
295			var that = this;
296			if (!connection) {
297				console.log('no socket connection');
298				return;
299			}
300			var userChannelSuffix = $tree.data('user-channel-suffix') || window.userChannelSuffix || window.accountUserId;
301			var treeChannelSuffix = $tree.data('tree-channel-suffix') || $tree.data('tree-id');
302			if (oldTreeId) {
303				var oldTreeChannelSuffix = $tree.data('old-tree-channel-suffix') || oldTreeId;
304				connection.unsubscribeChannel(CHANNELS.NEW_COMMENT+oldTreeChannelSuffix);
305				connection.unsubscribeChannel(CHANNELS.NEW_COMMENT+oldTreeChannelSuffix+userChannelSuffix);
306				connection.unsubscribeChannel(CHANNELS.DELETE_COMMENT+oldTreeChannelSuffix);
307			}
308			connection.subscribeChannel(CHANNELS.NEW_COMMENT+treeChannelSuffix, function(message, json){newComment(json)});
309			connection.subscribeChannel(CHANNELS.NEW_COMMENT+treeChannelSuffix+userChannelSuffix, function(message, json){newComment(json)});
310			connection.subscribeChannel(CHANNELS.DELETE_COMMENT+treeChannelSuffix, function(message, json){
311				$.each(json.comment_ids, function (index, id) {
312					DomHelper.removeCommentBlock($('.comment'+id+'top'));
313				});
314			});
315			function newComment(json) {
316				if ('string' === (typeof json)) {
317					try {
318						json = JSON.parse(json);
319					} catch (e) {
320						console.log(e, json);
321					}
322				}
323				var commentId = json.comment_id;
324				if (!commentId) {
325					console.log("empty comment id");
326					return;
327				}
328				that.getTypingWidget().removeUser(json.user_id);
329				setTimeout(
330					function () {
331						addComment(json.object_id, json.object_type_id, json.settings, json.html, true);
332					},
333					//( Math.random() * 2000 ) + 2000
334					0
335				);
336			}
337		}
338		,initWebSocketUserTyping : function($tree, connection, oldTreeId) {
339			if (!connection || $tree.data('tree-id').match(/need_create/)) {
340				return;
341			}
342			if ($tree.hasClass('comments-tree-show-typing')) {
343				var that = this;
344				var treeChannelSuffix = $tree.data('tree-channel-suffix') || $tree.data('tree-id');
345				if (oldTreeId) {
346					var oldTreeChannelSuffix = $tree.data('old-tree-channel-suffix') || oldTreeId;
347					connection.unsubscribeChannel(CHANNELS.TYPING+'_'+oldTreeChannelSuffix);
348				}
349				connection.subscribeChannel(CHANNELS.TYPING + '_' + treeChannelSuffix, function(message, json){
350					that.getTypingWidget().addUser(json.user_id, json.user_name, makeThumbnail(json.user_user_thumbnail));
351				});
352			}
353		},
354		initDownloadComments: function ($tree) {
355			$tree.find('a.download-csv').click(function() {
356				var that = this;
357				var $container = $($tree.find('.comments-container').get(0));
358				var settings = $($container.find('.settings-level-1').get(0)).find('textarea');
359				if ( settings.html() ) {
360					window.location = '/cms/comment/downloadTree?settings=' + settings.html();
361				} else {
362					alert('Ошибка экспорта');
363				}
364				return false;
365			})
366		},
367		initHelpdesk: function ($tree) {
368			$tree.find('a.helpdesk-btn').click(function() {
369				var confirmText = $(this).data('confirm-text') ?? Yii.t('helpdesk', 'Вы уверены?');
370                if (!confirm(confirmText)) {
371                    return;
372                }
373				$(this).attr('disabled', 'disabled');
374				ajaxCall($(this).data('href'), {}, {}, function(response) {
375					window.location.reload();
376				});
377			});
378		},
379	};
380
381	var CommentFormWidget = function($form) {
382		var that = this;
383
384		runOneTimeOnElement($form, 'gc-comment-form-widget', function($el) {
385			that.init($el);
386
387			return that;
388		});
389	};
390
391	CommentFormWidget.create = function($form) {
392		return new this($form)
393	};
394
395	CommentFormWidget.THREAD_COMMENT_HEIGHT = 28;
396	CommentFormWidget.REPLY_COMMENT_HEIGHT = 28;
397
398	CommentFormWidget.uploadButtonIndex = 0;
399
400	CommentFormWidget.prototype = {
401		textAreaHeight : undefined
402		,init : function($form) {
403			this.initForm($form);
404			this.initTextArea($form, window.accountUserWebSocketConnection);
405			this.initUploaders($form);
406			this.initModalWindow($form);
407			this.initTemplates($form);
408
409			if (window.GcEmojiHelper) {
410				window.GcEmojiHelper.initEmojiTextAreas($form);
411			}
412		}
413		,initForm: function ($form) {
414			$form.find('form').submit(function (event) {
415
416				var buttonTextGo = 'Отправляется';
417
418				if (typeof Yii != 'undefined') {
419					buttonTextGo = Yii.t( "common", "Go" );
420				}
421
422				var $this = $(this);
423				var $button = $this.find('button');
424				$button.attr('disabled', 'disabled');
425				var $buttonText = $button.find('.text');
426				$buttonText.data('old', $buttonText.html());
427				$buttonText.html(buttonTextGo);
428				var data = $this.serialize();
429				commentAddAjax($this, data, null, function () {
430					$this.find('button').removeAttr('disabled');
431					$buttonText.html($buttonText.data('old'));
432				});
433				return false;
434			});
435		}
436		,initTextArea : function($form, webSocketConnection) {
437			var $f = $form;
438			var that = this;
439			var $tree = $form.parents('.comments-tree');
440
441			if (!!window.presaveInLocalStorageComments) {
442				var answerId = $form.closest('.user-answer').data('id'),
443					localStorageKey = 'new-comment-for-answer-' + answerId,
444					localStorageValue = localStorage.getItem(localStorageKey);
445
446				if (!!answerId && !!localStorageValue) {
447					$form.find('.new-comment-textarea').val(localStorageValue);
448				}
449			}
450
451			$form.find('.new-comment-textarea')
452				.css('max-height', 200 + 'px')
453				.autosize()
454				.css('min-height', CommentFormWidget.REPLY_COMMENT_HEIGHT + 'px')
455				.css('height', CommentFormWidget.REPLY_COMMENT_HEIGHT + 'px')
456				.keyup(function(event) {
457					var $that = $(this);
458					if ($that.val().length > 0) {
459						$that.parents('.new-comment').addClass('active');
460						var $form = $that.parents('form');
461						if( event.keyCode == 13 && ( event.ctrlKey ) && ( ( $f.data('submit-on-enter') && ! event.shiftKey )  || event.ctrlKey ) ) {
462							$form.submit();
463						}
464					}
465					else {
466						$that.parents('.new-comment').removeClass('active');
467					}
468
469					if (that.textAreaHeight != $f.height()) {
470						TreeWidget.resize($tree);
471						that.textAreaHeight = $f.height();
472					}
473					if (webSocketConnection && $tree.hasClass('comments-tree-show-typing')) {
474						var treeChannelSuffix = $tree.data('tree-channel-suffix') || $tree.data('tree-id');
475						var channel = CHANNELS.TYPING+'_'+treeChannelSuffix;
476						if (!$that.data('typing')) {
477							$that.data('typing', 1);
478							if (webSocketConnection && webSocketConnection.getSocket()) {
479								webSocketConnection.getSocket().emit('message', {
480									json: {
481										channel: channel,
482										user_id: window.accountUserId,
483										user_name: window.accountSafeUserName,
484										user_user_thumbnail: window.accountUserThumbnail
485									}
486								});
487							}
488							setTimeout(function() {
489								$that.data('typing', 0)
490							}, CONSTANTS.TYPING_INTERVAL);
491						}
492					}
493				}).change(function(event) {
494					var value = $(this).val();
495
496					if (value.length > 0) {
497						if (!!window.presaveInLocalStorageComments) {
498							localStorage.setItem(localStorageKey, value);
499						}
500						$(this).parents('.new-comment').addClass('active');
501					} else {
502						if (!!window.presaveInLocalStorageComments) {
503							localStorage.removeItem(localStorageKey);
504						}
505						$(this).parents('.new-comment').removeClass('active');
506					}
507				}).focus(function(event){
508					$tree.trigger('tree:textarea:focused');
509				});
510
511			$form.find('.new-comment-textarea-level-1')
512				.autosize()
513				.css('min-height', CommentFormWidget.THREAD_COMMENT_HEIGHT + 'px')
514				.css('height', CommentFormWidget.THREAD_COMMENT_HEIGHT + 'px')
515			;
516			TreeWidget.resize($form.parents('.comments-tree'));
517			this.textAreaHeight = $form.height();
518		}
519		,initModalWindow : function($form) {
520			$form.find('.auth-link').click(function(e) {
521				var active = window.gcModalActive();
522				active && active.hide();
523				window.gcGetDefaultModalAuth().show();
524
525				return false;
526			});
527		}
528		,initUploaders : function(form) {
529			if (!window.userInfo.isInAccount) {
530				//return;
531			}
532			this.initUploader(
533				form.find('.comment-attach-file-link'), function($el, $uploader) {
534					$uploader.insertAfter($el.parents('.new-comment').find('.btn-send'));
535				});
536			this.initUploader(
537				form.find('.comment-attach-file-for-edit-link'),
538				function($el, $uploader) {
539					$el.append($uploader);
540				}
541			);
542		}
543		,initUploader : function($el, insertCallback, afterCreateCallback) {
544			var $uploader = $('<div class="file-uploader" style="max-height: 100px; overflow-y: auto;">');
545			$uploader.append('<div class="upload-button"></div>');
546			$uploader.append('<input type="hidden" class="upload-value" name="GetCourseComment[files]">');
547			insertCallback($el, $uploader);
548			var $button = $uploader.find('.upload-button');
549			$button.attr('id', 'comment-upload-button' + (CommentFormWidget.uploadButtonIndex++));
550			var useFileTypes = $el.parents('.comment-form-wrapper').attr('data-use-file-types');
551
552			// в зависимости от useFileTypes меняется поведение виджета (выбор типов аттачей)
553			var options = {
554				valueElemSelector: $uploader.find('.upload-value'),
555				userId: window.accountUserId,
556				buttonText: '<i class="glyphicon glyphicon-paperclip"></i>',
557				width: 145,
558				removeCompleted: true,
559				other : {
560					onClearQueue : function() {
561						setTimeout(function() {
562							TreeWidget.resize($el.parents('.comments-tree'));
563						}, 1000);
564					},
565				}
566			};
567			if (useFileTypes === '1') {
568				options = {
569					valueElemSelector: $uploader.find('.upload-value'),
570					userId: window.accountUserId,
571					buttonText: '<i class="glyphicon glyphicon-paperclip"></i>',
572					width: 145,
573					itemTemplate : '<div class="uploadifive-queue-item"><span class="filename"></span><div class="close"></div>' +
574						'<p class="type_block"></p>' +
575						'<span class="fileinfo"></span>' +
576						'<div class="progress" <div class="progress-bar"></div></div></div>',
577					removeCompleted: true,
578					other : {
579						onClearQueue : function() {
580							setTimeout(function() {
581								TreeWidget.resize($el.parents('.comments-tree'));
582							}, 1000);
583						},
584						onCancel : function(file) {
585							let hash = file.hash;
586							$hiddenInputEl = $(options.valueElemSelector);
587							let value = $hiddenInputEl.val();
588							if (value) {
589								let object = JSON.parse(value);
590								object.forEach((item, index, array) => {
591									if (item.hash === hash) {
592										object.splice(index, 1)
593									}
594								});
595								$hiddenInputEl.val(JSON.stringify(object));
596							}
597						},
598
599						onUploadComplete:  function (file , res) {
600							var hash = res;
601							file.hash = hash;
602							$hiddenInputEl = $(options.valueElemSelector);
603                            let ajaxUrl = '/pl/telegram/control/file/check-file';
604                            if (window.maxEnabledFeature === true) {
605                                ajaxUrl = '/pl/inbox-file/check-file';
606                            }
607
608							ajaxCall(ajaxUrl,
609								{hash: hash, validate: false},
610								{method: 'get'},
611								function (response) {
612									if (response.success === true) {
613										let newContent = response.data.content;
614										var fileinfo_text = $(newContent).find('.fileinfo').html();
615
616										file.queueItem.find('.fileinfo').html(fileinfo_text);
617
618										var select = $(newContent).find('.change-upload-type');
619										select.attr('attr_hash', hash);
620										var type_block =
621											$('<span>'+Yii.t('widgets', 'Отправить файл как')+': ' +
622												'<span class="type_select"></span></span>');
623										type_block.find('.type_select').append(select);
624										file.queueItem.find('.type_block').html(type_block);
625
626										let newValue = {"attach_type": select.val(), "hash": hash};
627										let hiddenInputValue = $hiddenInputEl.val();
628										let hiddenInputValueJson = JSON.parse('[]');
629										if (hiddenInputValue !== '') {
630											hiddenInputValueJson = JSON.parse(hiddenInputValue);
631										}
632										hiddenInputValueJson.push(newValue);
633										$hiddenInputEl.val(JSON.stringify(hiddenInputValueJson));
634									}
635								});
636						},
637					}
638				};
639			}
640
641			if (window.isEnabledSecureUpload && window.fileserviceUploadHost) {
642				options.onUpload = function (filesToUpload, settings) {
643					var secureDirectUploadUri = '/fileservice/widget/secure-direct-upload';
644					try {
645						var session = localStorage.getItem('session');
646						var requestParams = {"fs_ref": window.location.href};
647
648						if (session !== undefined && session != null){
649							var objSession = jQuery.parseJSON(session);
650
651							if (objSession.hasOwnProperty('user_id') && objSession.user_id != null) {
652								requestParams.fs_u = objSession.user_id;
653							} else {
654								requestParams.fs_u = -1;
655							}
656						}
657
658						secureDirectUploadUri += '?' + jQuery.param(requestParams);
659					} catch (err) {
660					}
661
662					$.ajax({
663						url: '/fileservice/widget/create-secret-link',
664						method: 'GET',
665						data: {
666							host: window.fileserviceUploadHost,
667							uri: secureDirectUploadUri,
668							expires: 600
669						},
670						success: function (data, textStatus, jqXHR) {
671							if (data.link) {
672								settings.uploadScript = data.link;
673							}
674						},
675						error: function (http, message, exc) {sendCreateLinkError(http, message, exc);},
676						async: false // to make it synchronous
677					});
678				};
679			}
680
681			initUploadify($button, options);
682
683			var $uploadifiveButton = $uploader.find('.uploadifive-button');
684
685			var $container = $el.parents('.contains-uploadify').find('.attach-file-button-container');
686
687			$uploadifiveButton.appendTo($container);
688			$uploadifiveButton.css('line-height', '20px');
689			$uploadifiveButton.height('20px');
690			$uploadifiveButton.width('15px');
691			$uploadifiveButton.css('background', 'transparent');
692			$uploadifiveButton.css('color', '#999');
693			$uploadifiveButton.css('text-align', 'center');
694			$uploadifiveButton.css('padding', '0');
695			$uploadifiveButton.css('font-size', '1em');
696			$el.data('uploader', $uploader);
697
698			afterCreateCallback && afterCreateCallback($el, $uploader);
699		},
700		initTemplates: function($el) {
701			$el.find('.template-link').click( function() {
702				var id = $(this).data('template-id');
703
704				let userId = $el.parents('.answer-content').find('a[data-user-id]').data('user-id');
705
706
707				ajaxCall("/pl/notifications/skill-question/get-template?id=" + id, { userId: userId }, {method: "get"}, function(response) {
708					var val = $el.find('.new-comment-textarea').val();
709					if ( val == "" ) {
710						val = response.data.answer;
711					}
712					else {
713						val = val + " " + response.data.answer;
714					}
715					$el.find('.new-comment-textarea').val( val );
716					$el.find('.new-comment-textarea').focus();
717					$('.new-comment-textarea', $el).trigger('keyup');
718					$('.new-comment-textarea', $el).css('min-height', 100);
719				})
720			})
721
722		}
723	};
724
725	var CommentWidget = function($comment) {
726		var that = this;
727
728		runOneTimeOnElement($comment, 'gc-comment-widget', function($el) {
729			that.init($el);
730
731			return that;
732		});
733	};
734
735	CommentWidget.create = function($comment) {
736		return new this($comment)
737	};
738
739	CommentWidget.prototype = {
740		init : function($comment) {
741			this.initForms($comment);
742			this.initActions($comment);
743			this.initFilesFrontPopup($comment);
744
745			if (window.GcEmojiHelper) {
746				window.GcEmojiHelper.initEmojies($comment);
747			}
748		}
749		,initForms : function($comment) {
750			DomHelper.findForms($comment).each(function (index, el) {
751				CommentFormWidget.create($(el));
752			});
753		}
754		,initActions : function($comment) {
755			this.initActionsVisual($comment);
756			this.initEdit($comment);
757			this.initDelete($comment);
758			this.initTemplateLink($comment);
759		}
760		,initActionsVisual : function($comment) {
761			window.eachAndRunOneTimeOnElement(
762				$comment.find('.comment-edit-link, .comment-delete-link, .comment-template-link'),
763				'comment-actions-hidden',
764				function ($el) {
765					$el.addClass('hide');
766					return $el;
767				}
768			);
769			window.eachAndRunOneTimeOnElement(
770				$comment.find('.comment-edit-link, .comment-delete-link, .comment-template-link'),
771				'comment-mouseout-handler',
772				function ($el) {
773					$comment.find('.comment')
774						.mouseover(function() {
775							//alert( JSON.stringify(window.userInfo) );
776							$(this).find('.comment-edit-link').removeClass('hide');
777							if (window.userInfo.isAdmin) {
778								$(this).find('.comment-delete-link').removeClass('hide');
779							}
780							if(window.userInfo.isHumanDesign == "1" && $(this).find('.comment-delete-link').data( "delete" ) == "1"){
781								$(this).find('.comment-delete-link').removeClass('hide');
782							}
783							if ( window.userInfo.isTeacher ) {
784								$(this).find('.comment-template-link').removeClass('hide');
785							}
786						})
787						.mouseout(function() {
788							$(this).find('.comment-edit-link, .comment-template-link, .comment-delete-link').addClass('hide');
789						})
790					;
791					return $el;
792				}
793			);
794		}
795		,initEdit : function($comment) {
796			window.eachAndRunOneTimeOnElement(
797				$comment.find('.comment-edit-link'),
798				'comment-edit-link-handler',
799				function ($el) {
800					$el.click(function() {
801						var $comment = $(this).parents('.comment');
802						$comment.find('.edit-comment-active-element').removeClass('hide');
803						$comment.find('.edit-comment-inactive-element').addClass('hide');
804						var $textarea = $comment.find('.edit-comment-active-element textarea');
805						$textarea.css('min-height', 50 + 'px').autosize();
806						return false;
807					});
808					return $el;
809				}
810			);
811
812			window.eachAndRunOneTimeOnElement(
813				$comment.find('.comment-cancel-edit-link'),
814				'comment-cancel-edit-link-handler',
815				function ($el) {
816					$el.click(function() {
817						var $comment = $(this).parents('.comment');
818						$comment.find('.edit-comment-active-element').addClass('hide');
819						$comment.find('.edit-comment-inactive-element').removeClass('hide');
820					});
821					return $el;
822				}
823			);
824
825			window.eachAndRunOneTimeOnElement(
826				$comment.find('.save-edited-comment-btn'),
827				'comment-save-edited-btn-handler',
828				function ($el) {
829					$el.click(function() {
830						var $btn = $(this);
831						var $commentEl = $(this).parents('.comment');
832						var toDelete = [];
833						$commentEl.find('.delete-file-checkbox:checked').each(function(index, el) {
834							toDelete.push($(el).val());
835						});
836						ajaxCall($commentEl.find('.comment-edit-link').data('href'), {
837							text : $commentEl.find('.edited-comment-text').val(),
838							files_to_delete : toDelete.join(','),
839							files_to_add : $commentEl.find('.upload-value').val()
840						}, {
841							btn : $btn
842						}, function(response) {
843							$commentEl.find('.emoji-text').html($.emoji(response.html.text));
844							var $files = $commentEl.find('.files');
845							$files.html(response.html.files);
846							if (response.html.files.length > 0) {
847								$files.addClass('active');
848							}
849							else {
850								$files.removeClass('active');
851							}
852							$commentEl.find('.edit-comment-active-element').addClass('hide');
853							$commentEl.find('.edit-comment-inactive-element').removeClass('hide');
854							$commentEl.find('.upload-value').val('');
855							$commentEl.find('.upload-button').uploadifive('clearQueue');
856							$(document).trigger('gc:comment:edited');
857						});
858					});
859					return $el;
860				}
861			);
862
863		}
864		,initDelete : function($comment) {
865			window.eachAndRunOneTimeOnElement(
866				$comment.find('.comment-delete-link'),
867				'comment-delete-link-handler',
868				function ($el) {
869					$el.click(function() {
870						var $this = $(this);
871						var id = $this.data('id');
872						var ids = [
873								id
874							]
875							,blocks = [
876								$($this.parents('.gc-comment').get(0))
877							]
878						;
879						if (confirm('Комментарии будут удалены. Вы уверены?')) {
880							ajaxCall('/cms/comment/delete', {
881								GetCourseCommentIds : ids
882							}, {}, function() {
883								$.each(blocks, function (index, block) {
884									DomHelper.removeCommentBlock(block);
885								});
886							});
887						}
888						return false;
889					});
890					return $el;
891				}
892			);
893		},
894		initTemplateLink : function($comment) {
895			window.eachAndRunOneTimeOnElement(
896				$comment.find('.comment-template-link'),
897				'comment-template-link-handler',
898				function ($el) {
899					$el.click(function() {
900						var $this = $(this);
901						var id = $this.data('id');
902
903						var questionDialog = new CommentTemplateDialog();
904						questionDialog.open( id );
905
906						return false;
907					});
908					return $el;
909				}
910			);
911		}
912		,initFilesFrontPopup : function($comment) {
913			window.eachAndRunOneTimeOnElement(
914				$comment.find('.files a'),
915				'comment-object-front-popup-handler',
916				function ($el) {
917					var $this = $el;
918					var selector = '.' + [
919						'gc-object-front-popup',
920						$this.data('object-id'),
921						$this.data('object-type-id')
922					].join('-');
923
924					var $all = $(selector);
925					var $tile = $all.filter('.gc-object-front-popup-clickable-tile');
926
927					if ($tile.length > 0) {
928						$this.removeClass('gc-object-front-popup-clickable');
929						$this.click(function() {
930							$tile.trigger('click');
931							return false;
932						});
933					}
934					return $el;
935				}
936			);
937		}
938	};
939
940	var TypingBlockWidget = function ($tree) {
941		var typingUsers = {};
942		var timeouts = {};
943		var $block = $tree.find('.typing-text');
944
945		var refresh = function() {
946			var messages = [];
947			var lastTypingUser = null;
948			for (var i in typingUsers) {
949				lastTypingUser = typingUsers[i]
950				messages.push(typingUsers[i].user_name);
951			}
952			if (messages.length === 0) {
953				$block.html('');
954			}
955			else {
956				var maxLen = CONSTANTS.MAX_SHOW_TYPING_USERS;
957				if (messages.length > maxLen) {
958					var len = messages.length;
959					messages = messages.slice(0, maxLen);
960					messages.push('еще '+(len - maxLen))
961				}
962
963				if ( messages.length > 1 ) {
964					$block.html(messages.join(' и ')+' '+(messages.length > 1 ? 'пишут' : 'пишет'));
965				} else {
966					const darkTheme = gcGetCookie('gc_inc_dark');
967
968					if (parseInt(darkTheme)) {
969						$block.html( "<div class='comment-wrapper'> <div class='comment'>" +
970							" <div class='user-image-wrapper'> " + lastTypingUser.user_user_thumbnail + '</div>' +
971							' <div class="text-content"><img src="/public/img/type.svg"></div>' + "</div></div>");
972					} else {
973						$block.html( "<div class='comment-wrapper'> <div class='comment'>" +
974							" <div class='user-image-wrapper'> " + lastTypingUser.user_user_thumbnail + '</div>' +
975							' <div class="text-content"><img src="/public/img/typing.gif"></div>' + "</div></div>");
976					}
977				}
978			}
979		};
980
981		this.addUser = function(userId, userName, userThumbnail) {
982			if (window.accountUserId != userId) {
983				clearTimeout(timeouts[userId]);
984				if (!typingUsers.hasOwnProperty(userId)) {
985					typingUsers[userId] = {
986						user_id : userId
987						,user_name : userName
988						,user_user_thumbnail : userThumbnail
989					};
990				}
991
992				timeouts[userId] = setTimeout(function() {
993					delete typingUsers[userId];
994					var len = 0;
995					for (var i in typingUsers) {
996						len++;
997					}
998					if (len === 0) {
999						$block.html('');
1000					}
1001				}, CONSTANTS.TYPING_INTERVAL + 1000);
1002			}
1003			refresh();
1004		};
1005
1006		this.removeUser = function(userId) {
1007			delete typingUsers[userId];
1008			clearTimeout(timeouts[userId]);
1009			refresh();
1010		};
1011	};
1012	var addComment = function(objectId, objectTypeId, settings, html, isNode ) {
1013		var groupId = settings.values.group > 0 ? settings.values.group : 0;
1014		var blockClass = 'comments-' + [
1015			settings.values.object_type_id,
1016			settings.values.object_id,
1017			groupId
1018		].join('-');
1019		var $commentsBlocks = $('.'+blockClass);
1020		if ( $commentsBlocks.length == 0 ) {
1021			return;
1022		}
1023		$commentsBlocks.each(function(index, el) {
1024			var $commentsBlock = $(el);
1025			var $tree = $($commentsBlock.parents('.comments-tree').get(0));
1026			if (isNode && $tree.data('paused')) {
1027				$tree.data('queue').push({
1028					objectId : objectId
1029					, objectTypeId : objectTypeId
1030					, settings : settings
1031					, html : html
1032				});
1033				if ($tree.data('queue').length > CONSTANTS.MAX_COMMENTS_PER_PAGE_DIRECTION_DESC) {
1034					$tree.data('queue').shift();
1035				}
1036				$tree.data('pause-button').find('.value').html('(' + $tree.data('queue').length + ')');
1037				return;
1038			}
1039			var $html = $( html );
1040			var commentId = $html.find('.comment').data('object-id');
1041			var $commentExist = $commentsBlock.find('#comment' + commentId);
1042			if ($commentExist.length > 0) {
1043				var $commentExistFiles = $commentExist.find('.files');
1044				var $commentNewFiles = $html.find('.files');
1045				if ( ! $commentExistFiles.hasClass('active') && $commentNewFiles.hasClass('active') ) {
1046					$commentExistFiles.replaceWith($commentNewFiles);
1047				}
1048				return;
1049			}
1050			if (window.accountUserId == settings.accountUserId) {
1051				$html.addClass('my-comment');
1052			}
1053			else {
1054				$html.removeClass('my-comment');
1055			}
1056
1057			var invertOwnUser = $commentsBlock.parents('.invert-own-comment').length > 0;
1058			if ( (settings.accountUserId == settings.values.own_user_id) != invertOwnUser ) {
1059				//$html.addClass('own-user-comment');
1060			}
1061			else {
1062				$html.removeClass('own-user-comment');
1063			}
1064
1065
1066			TreeWidget.applyAccess(window.userInfo.isAdmin, 'admin', $html);
1067			TreeWidget.applyAccess(window.userInfo.isManager, 'manager', $html);
1068			TreeWidget.applyAccess(window.userInfo.isInAccount, 'account', $html);
1069			TreeWidget.applyAccess(window.userInfo.isTeacher, 'teacher', $html);
1070
1071			$html.find('.owner').each(function(index, el) {
1072				var $el = $(el);
1073				if($el.hasClass('admin') && window.userInfo.isAdmin) {
1074					return true;
1075				}
1076				if (!$el.hasClass('owner_'+window.accountUserId)) {
1077					$el.remove();
1078				}
1079			});
1080
1081			var size = CommentFormWidget.REPLY_COMMENT_HEIGHT;
1082			var isDesc = $html.data('direction') == 'desc';
1083			if (isDesc) {
1084
1085				$commentsBlock.prepend($html);
1086			}
1087			else {
1088				$commentsBlock.append($html);
1089			}
1090
1091			if (isDesc || $tree.hasClass('more-link')) {
1092				//а todo хотелось бы выводить только видимую облать комментов - динамический page size и валидация, знать комменты перед этим и после
1093				var $commentsContainer = $($commentsBlock.parents('.comments-container').get(0));
1094				var commentsLevel1 = $commentsContainer.find('.level-1');
1095				var $more = $commentsContainer.find('.more');
1096				var pageSize = settings.values.page_size;
1097				if ($more.length == 0 || $more.find('a').data('clicked') == 0) {
1098					var max = settings.values.page_size;
1099					if (commentsLevel1.length > max) {
1100						var $lastComment = commentsLevel1.get(isDesc ? commentsLevel1.length - 1 : 0);
1101						$lastComment.remove();
1102						if ($more.length == 0) {
1103							var moreHtml = '<div class="more"> <a href="javascript:void(0);"></a>';
1104							if (isDesc) {
1105								$commentsContainer.append(moreHtml);
1106							}
1107							else {
1108								$commentsContainer.prepend(moreHtml);
1109							}
1110						}
1111						var $a = $more.find('a');
1112						$a.html('Еще комментарии');
1113						$a.data('page', max/pageSize);
1114					}
1115				}
1116			}
1117
1118			if (objectId != settings.values.object_id) {
1119				$commentsBlock.removeClass(blockClass);
1120				$commentsBlock.addClass('comments-' + [
1121					settings.values.object_type_id,
1122					objectId,
1123					groupId
1124				].join('-'));
1125				var $settingsValue = $tree.find('.settings-value');
1126				var oldSettings = JSON.parse($settingsValue.html());
1127				oldSettings.values.object_id = objectId;
1128				$settingsValue.html(JSON.stringify(oldSettings));
1129				var oldTreeId = $tree.data('tree-id');
1130				$tree.data('tree-id', [
1131					'comments'
1132					,objectId
1133					,settings.values.object_type_id
1134					,groupId ? groupId : ''
1135				].join('-'));
1136				var widget = $tree.data('gc-comments-tree-widget');
1137				widget.initWebSocketUserTyping(
1138					$tree,
1139					window.accountUserWebSocketConnection,
1140					oldTreeId
1141				);
1142				widget.initWebSocketNewComments(
1143					$tree,
1144					window.accountUserWebSocketConnection,
1145					oldTreeId
1146				);
1147				$($tree).trigger('tree:comment:created_object', [objectId, objectTypeId, settings.accountUserId]);
1148			}
1149
1150			CommentWidget.create($html);
1151
1152			$html.find('textarea').autosize().css('min-height', size + 'px');
1153			$html.find('.new-comment-textarea').css('height', size + 'px');
1154
1155			const darkTheme = window.gcGetCookie('gc_inc_dark');
1156			if (parseInt(darkTheme)) {
1157				$html.find('.comment-wrapper').effect('highlight', {color: "#3a3a3a", duration: 1000 });
1158			} else {
1159				$html.find('.comment-wrapper').effect('highlight', { duration: 1000 });
1160			}
1161
1162			$(document).trigger('gc:comment:added');
1163			$($tree).trigger('tree:comment:added', [
1164				$html.find('.comment:first').data('id'),
1165				objectId,
1166				objectTypeId,
1167				settings.accountUserId,
1168				window.gcIsActiveTab
1169			]);
1170			TreeWidget.resize($tree);
1171		});
1172	};
1173
1174	var commentAddAjax = function($form, data, callback, completeAjaxCallback)
1175	{
1176		ajaxCall('/cms/comment/add', data, {}, function( response ) {
1177			if (response.settings && response.html) {
1178				addComment( response.object_id, response.object_type_id, response.settings, response.html );
1179			}
1180			var uploader, uploadButton, audioUploader;
1181
1182			if ($form) {
1183				if (!!window.presaveInLocalStorageComments) {
1184					var answerId = $('.new-comment-textarea', $form).closest('.user-answer').data('id'),
1185						localStorageKey = 'new-comment-for-answer-' + answerId;
1186					localStorage.removeItem(localStorageKey);
1187				}
1188
1189				$('.new-comment-textarea', $form).val('').css({height: '28px'}).trigger('keyup');
1190				$('.new-comment-textarea', $form).siblings('.emoji-wysiwyg-editor').empty();
1191				uploader = $form.parents('.new-comment').find('.comment-attach-file-link').data('uploader');
1192				uploadButton = $form.parents('.new-comment').find('.upload-button');
1193
1194				audioUploader = $form.parents('.new-comment').find('.audio-upload');
1195			}
1196			if (audioUploader) {
1197				audioUploader.html('');
1198			}
1199
1200			if ( uploader ) {
1201				uploader.find('.upload-value').val('');
1202			}
1203
1204			if (uploadButton) {
1205				uploadButton.uploadifive('clearQueue');
1206			}
1207
1208			// Очистить все подсказки
1209			$('.gc-comment .qna-text-element').html('');
1210
1211			callback && callback();
1212		}, completeAjaxCallback);
1213	};
1214
1215	$(document).on('gc:comment:add:ajax', function(event, $form, data, callback, completeAjaxCallback) {
1216		commentAddAjax($form, data, callback, completeAjaxCallback);
1217	});
1218
1219	$(document).on('gc:comment:submit:form', function(event, $form, callback, completeAjaxCallback) {
1220		commentAddAjax($form, $form.serialize(), callback, completeAjaxCallback);
1221	});
1222
1223	$(document).on('gc:comment:tree:resize', function(event, $tree) {
1224		TreeWidget.resize($tree);
1225	});
1226
1227	window.GcCommentsTreeWidget = TreeWidget;
1228	window.GcCommentWidget = CommentWidget;
1229
1230	$(document).on('change', '.change-upload-type', function () {
1231		let newType = $(this).val();
1232		let hash = $(this).attr('attr_hash');
1233		let $hiddenInputEl = $('input.upload-value');
1234		let object = JSON.parse($hiddenInputEl.val());
1235		object.forEach((item, index, array) => {
1236			if (item.hash === hash.trim()) {
1237				item.attach_type = newType;
1238			}
1239		});
1240		$hiddenInputEl.val(JSON.stringify(object));
1241	});
1242
1243	if (!!window.presaveInLocalStorageComments) {
1244		$(window).on('beforeunload', function (e) {
1245			var e = e || window.event,
1246				confirm = false;
1247
1248			if ($('.answers-list').length) {
1249				$('.new-comment-textarea:focus').change();
1250				$('.new-comment-textarea:visible').each(function () {
1251					if ($(this).val()) {
1252						confirm = true;
1253					}
1254				});
1255			}
1256
1257			if (confirm) {
1258				e.returnValue = 'Leaving the page';
1259				return 'Leaving the page';
1260			}
1261		});
1262	}
1263});

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.