PageSourceSearch

https://www.sonorika.com/wp-content/plugins/wise-chat/js/wise_chat.js?ver=7.1.2

js sonorika.com collected 2026-10-02 07:18:48 UTC 29,033 bytes, 893 lines download raw bytes

1/**
2 * Wise Chat core controller.
3 *
4 * @author Kainex <[email protected]>
5 */
6function WiseChatController(options) {
7	var progressBar = new WiseChatProgressBar(options);
8	var notifier = new WiseChatNotifier(options);
9	var messagesHistory = new WiseChatMessagesHistory();
10	var imageViewer = new WiseChatImageViewer();
11	var dateFormatter = new WiseChatDateFormatter();
12	var messageAttachments = new WiseChatMessageAttachments(options, imageViewer, progressBar);
13	var dateAndTimeRenderer = new WiseChatDateAndTimeRenderer(options, dateFormatter);
14	var messages = new WiseChatMessages(options, messagesHistory, messageAttachments, dateAndTimeRenderer, notifier, progressBar);
15	var settings = new WiseChatSettings(options, messages);
16	var maintenanceExecutor = new WiseChatMaintenanceExecutor(options, messages, notifier);
17    var emoticonsPanel = new WiseChatEmoticonsPanel(options, messages);
18	
19	messages.start();
20	maintenanceExecutor.start();
21};
22
23/**
24 * WiseChatDateFormatter class. Formats dates given in UTC timezone.
25 *
26 * @author Kainex <[email protected]>
27 */
28function WiseChatDateFormatter() {
29	
30	function makeLeadZero(number) {
31		return (number < 10 ? '0' : '') + number;
32	}
33	
34	/**
35	* Parses date given in ISO format.
36	* 
37	* @param {String} isoDate Date in ISO format
38	* 
39	* @return {Date} Parsed date
40	*/
41	function parseISODate(isoDate) {
42		var s = isoDate.split(/\D/);
43		
44		return new Date(Date.UTC(s[0], --s[1]||'', s[2]||'', s[3]||'', s[4]||'', s[5]||'', s[6]||''))
45	}
46	
47	/**
48	* Determines whether two dates have equal day, month and year.
49	* 
50	* @param {Date} firstDate
51	* @param {Date} secondDate
52	* 
53	* @return {Boolean}
54	*/
55	function isSameDate(firstDate, secondDate) {
56		var dateFormatStr = 'Y-m-d';
57		
58		return formatDate(firstDate, dateFormatStr) == formatDate(secondDate, dateFormatStr);
59	}
60	
61	/**
62	* Returns formatted date.
63	* 
64	* @param {Date} date Date to format as a string
65	* @param {String} format Desired date format
66	* 
67	* @return {String} Formatted date
68	*/
69	function formatDate(date, format) {
70		format = format.replace(/Y/, date.getFullYear());
71		format = format.replace(/m/, makeLeadZero(date.getMonth() + 1));
72		format = format.replace(/d/, makeLeadZero(date.getDate()));
73		format = format.replace(/H/, makeLeadZero(date.getHours()));
74		format = format.replace(/i/, makeLeadZero(date.getMinutes()));
75		
76		return format;
77	}
78	
79	/**
80	* Returns localized time without seconds.
81	* 
82	* @param {Date} date Date to format as a string
83	* 
84	* @return {String} Localized time
85	*/
86	function getLocalizedTime(date) {
87		if (typeof (date.toLocaleTimeString) != "undefined") {
88			var timeLocale = date.toLocaleTimeString();
89			if ((timeLocale.match(/:/g) || []).length == 2) {
90				timeLocale = timeLocale.replace(/:\d\d$/, '');
91				timeLocale = timeLocale.replace(/:\d\d /, ' ');
92				timeLocale = timeLocale.replace(/[A-Z]{2,4}\-\d{1,2}/, '');
93				timeLocale = timeLocale.replace(/[A-Z]{2,4}/, '');
94			}
95			
96			return timeLocale;
97		} else {
98			return formatDate(date, 'H:i');
99		}
100	}
101	
102	/**
103	* Returns localized date.
104	* 
105	* @param {Date} date Date to format as a string
106	* 
107	* @return {String} Localized date
108	*/
109	function getLocalizedDate(date) {
110		if (typeof (date.toLocaleDateString) != "undefined") {
111			return date.toLocaleDateString();
112		} else {
113			return formatDate(date, 'Y-m-d');
114		}
115	}
116	
117	// public API:
118	this.formatDate = formatDate;
119	this.parseISODate = parseISODate;
120	this.isSameDate = isSameDate;
121	this.getLocalizedTime = getLocalizedTime;
122	this.getLocalizedDate = getLocalizedDate;
123};
124
125/**
126 * WiseChatMessageAttachments class. Attachments management.
127 *
128 * @author Kainex <[email protected]>
129 */
130function WiseChatMessageAttachments(options, imageViewer, progressBar) {
131	var IMAGE_TYPES = ['jpg', 'jpeg', 'png', 'gif'];
132	var prepareImageEndpoint = options.apiEndpointBase + '?action=wise_chat_prepare_image_endpoint';
133	var container = jQuery('#' + options.chatId);
134	var messageAttachmentsPanel = container.find('.wcMessageAttachments');
135	var imageUploadPreviewImage = container.find('.wcImageUploadPreview');
136	var imageUploadFile = container.find('.wcImageUploadFile');
137	var attachmentClearButton = container.find('.wcAttachmentClear');
138	var fileUploadFile = container.find('.wcFileUploadFile');
139	var fileUploadNamePreview = container.find('.wcFileUploadNamePreview');
140	var attachments = [];
141	
142	function showErrorMessage(message) {
143		alert(message);
144	}
145	
146	function addAttachment(type, data, name) {
147		attachments.push({ type: type, data: data, name: name });
148	}
149	
150	function showImageAttachment() {
151		if (attachments.length > 0 && attachments[0].type === 'image') {
152			imageViewer.show(attachments[0].data);
153		}
154	}
155	
156	function onImageUploadFileChange() {
157		var fileInput = imageUploadFile[0];
158		if (typeof FileReader === 'undefined' || fileInput.files.length === 0) {
159			showErrorMessage('Unsupported operation');
160			return;
161		}
162		
163		var fileDetails = fileInput.files[0];
164		if (fileDetails.size && fileDetails.size > options.imagesSizeLimit) {
165			showErrorMessage(options.messages.messageSizeLimitError);
166			return;
167		}
168		
169		if (IMAGE_TYPES.indexOf(getExtension(fileDetails)) > -1) {
170			var fileReader = new FileReader();
171			fileReader.onload = function(event) {
172				clearAttachments();
173				prepareImage(event.target.result, function(preparedImageData) {
174					if (typeof preparedImageData !== 'undefined' && preparedImageData.length > 0) {
175						addAttachment('image', preparedImageData);
176
177						imageUploadPreviewImage.show();
178						imageUploadPreviewImage.attr('src', preparedImageData);
179						messageAttachmentsPanel.show();
180						imageUploadFile.val('');
181					} else {
182						showErrorMessage('Cannot prepare image due to server error');
183					}
184				});
185			};
186			fileReader.readAsDataURL(fileDetails);
187		} else {
188			showErrorMessage(options.messages.messageUnsupportedTypeOfFile);
189		}
190	}
191	
192	function prepareImage(imageSource, successCallback) {
193		var that = this;
194		
195		progressBar.setValue(0);
196		progressBar.show();
197
198		jQuery.ajax({
199			type: "POST",
200			url: prepareImageEndpoint,
201			data: {
202				data: imageSource,
203				checksum: options.checksum
204			},
205			progressUpload: function(event) {
206				if (event.lengthComputable) {
207					var percent = parseInt(event.loaded / event.total * 100);
208					if (percent > 100) {
209						percent = 100;
210					}
211					progressBar.setValue(percent);
212				}
213			}
214		})
215		.done(function(result) {
216			progressBar.hide();
217			successCallback.apply(that, [result]);
218		})
219		.fail(function(jqXHR, textStatus, errorThrown) {
220			progressBar.hide();
221			try {
222				var response = jQuery.parseJSON(jqXHR.responseText);
223				if (typeof response.error != 'undefined') {
224					showErrorMessage(response.error);
225				} else {
226					showErrorMessage('Image preparation error occurred');
227				}
228			}
229			catch (e) {
230				showErrorMessage('Unknown image preparation error occurred');
231			}
232		});
233	}
234	
235	function onFileUploadFileChange() {
236		var fileInput = fileUploadFile[0];
237		if (typeof FileReader === 'undefined' || fileInput.files.length === 0) {
238			showErrorMessage('Unsupported operation');
239			return;
240		}
241		
242		var fileDetails = fileInput.files[0];
243		if (options.attachmentsValidFileFormats.indexOf(getExtension(fileDetails)) > -1) {
244			var fileReader = new FileReader();
245			var fileName = fileDetails.name;
246			
247			if (fileDetails.size > options.attachmentsSizeLimit) {
248				showErrorMessage(options.messages.messageSizeLimitError);
249			} else {
250				fileReader.onload = function(event) {
251					clearAttachments();
252					addAttachment('file', event.target.result, fileName);
253					fileUploadNamePreview.html(fileName);
254					fileUploadNamePreview.show();
255					messageAttachmentsPanel.show();
256				};
257				fileReader.readAsDataURL(fileDetails);
258			}
259		} else {
260			showErrorMessage(options.messages.messageUnsupportedTypeOfFile);
261		}
262	}
263	
264	function getExtension(fileDetails) {
265		if (typeof fileDetails.name !== 'undefined') {
266			var splitted = fileDetails.name.split('.');
267			if (splitted.length > 1) {
268				return splitted.pop().toLowerCase();
269			}
270		}
271		
272		return null;
273	}
274	
275	function resetInput(inputField) {
276		inputField.wrap('<form>').parent('form').trigger('reset');
277		inputField.unwrap();
278	}
279	
280	/**
281	* Returns an array of prepared attachments.
282	* 
283	* @return {Array}
284	*/
285	function getAttachments() {
286		return attachments;
287	}
288	
289	/**
290	* Clears all added attachments, resets and hides UI related to added attachments.
291	*/
292	function clearAttachments(e) {
293		if (typeof e !== 'undefined') {
294			e.preventDefault();
295		}
296
297		attachments = [];
298		messageAttachmentsPanel.hide();
299		fileUploadNamePreview.hide();
300		fileUploadNamePreview.html('');
301		imageUploadPreviewImage.hide();
302		resetInput(fileUploadFile);
303		resetInput(imageUploadFile);
304	}
305	
306	// DOM events:
307	imageUploadFile.change(onImageUploadFileChange);
308	fileUploadFile.change(onFileUploadFileChange);
309	attachmentClearButton.click(clearAttachments);
310	imageUploadPreviewImage.click(showImageAttachment);
311	
312	// public API:
313	this.getAttachments = getAttachments;
314	this.clearAttachments = clearAttachments;
315};
316
317/**
318 * WiseChatImageViewer
319 *
320 * @author Kainex <[email protected]>
321 */
322function WiseChatImageViewer() {
323	var HOURGLASS_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3wQEDB4ktAYXpwAAAb5JREFUSMe1lr9qFFEUh78rg8gWW1ikSLEWgkVq2SoYsbBIk1dYEAsxaJt3sLAIFkEEX0FSRlgMhKAPkEIQwZDChATSBLMQP5uz4bKZmZ3ZxR+cYs75nT9z7rlnJpFBfQC8B24xG/4Cz1NK38eKYoKwADxiPiwA1wnSpFUdAO+A+y0D/wBeppQ+5sqihHgAdIBRSumsSWT1bvgcNCF31Et1tWnp6mr4dCZtNw4zpXQB7AJrLdqzBuyGb6OKBuq52m3A7QZ3UGZPVW0CfgJvgc/As4r4H4CnwGvgXkrpDy36uh6VPVRPvYnTsJ2r662HWS3U/ZDH6kkW/CR0Y3sx041Re+qh+kXtq59C+qE7VHt1MWpXQkrpF7ACdIFhZhqGbiU4syX474gWHUU7FjP9YuiOprVo2iF/jUO8U3Hj94NTzJLgVYxgL0v4JqTI3rD9mEZ1v9WN7Hk7G9Pt8d5RN4LbaZPgelWE7JVctL3MXrkqqhLsqFvqbXVoNYbB2VJ32rTnMlbwptOxWbeuyxL0w/GJetUgwVVwVfuT8crGawm4AEbAi4ZdHYXPEvCtrvpl58dy3Rscx9dsnt+W41zxD60+eUN8VNiNAAAAAElFTkSuQmCC";
324	
325	var container = jQuery('body');
326	var imagePreviewFade = container.find('.wcImagePreviewFade');
327	var imagePreview = container.find('.wcImagePreview');
328	if (imagePreviewFade.length === 0) {
329		container.append('<div class="wcImagePreview"> </div><div class="wcImagePreviewFade"> </div>');
330		imagePreviewFade = container.find('.wcImagePreviewFade');
331		imagePreview = container.find('.wcImagePreview');
332	}
333	
334	function show(imageSource) {
335		clearRemnants();
336		
337		imagePreviewFade.show();
338		addAndShowHourGlass();
339		
340		var imageElement = jQuery('<img style="display:none;" />');
341		imageElement.on('load', function() {
342			removeHourGlass();
343			
344			var image = jQuery(this);
345			var additionalMargin = 20;
346			var windowWidth = jQuery(window).width();
347			var windowHeight = jQuery(window).height();
348			image.show();
349			
350			if (image.width() > windowWidth && image.height() > windowHeight) {
351				if (image.width() > image.height()) {
352					image.width(windowWidth - additionalMargin);
353				} else {
354					image.height(windowHeight - additionalMargin);
355				}
356			} else if (image.width() > windowWidth) {
357				image.width(windowWidth - additionalMargin);
358			} else if (image.height() > windowHeight) {
359				image.height(windowHeight - additionalMargin);
360			}
361
362			var topPosition = Math.max(0, ((windowHeight - image.outerHeight()) / 2) + jQuery(window).scrollTop());
363			var leftMargin = Math.max(0, (windowWidth - image.width()) / 2);
364			imagePreview.css({
365				top: topPosition + "px",
366				marginLeft: leftMargin + "px"
367			});
368		});
369		imageElement.attr('src', imageSource);
370		imageElement.appendTo(imagePreview);
371		imageElement.click(hide);
372	}
373	
374	function hide() {
375		clearRemnants();
376		imagePreview.hide();
377		imagePreviewFade.hide();
378	}
379	
380	function clearRemnants() {
381		imagePreview.find('img').remove();
382	}
383	
384	function addAndShowHourGlass() {
385		var windowHeight = jQuery(window).height();
386		var imageElement = jQuery('<img class="wcHourGlass" />');
387		var topPosition = Math.max(0, ((windowHeight - 24) / 2) + jQuery(window).scrollTop());
388		
389		imageElement.attr('src', HOURGLASS_ICON);
390		imageElement.appendTo(imagePreview);
391		imagePreview.css({
392			top: topPosition + "px",
393			marginLeft: "-12px"
394		});
395		imagePreview.show();
396	}
397	
398	function removeHourGlass() {
399		container.find('.wcHourGlass').remove();
400	}
401	
402	// DOM events:
403	imagePreviewFade.click(hide);
404	
405	// public API:
406	this.show = show;
407	this.hide = hide;
408};
409
410/**
411 * WiseChatNotifier - window title and sound notifiers.
412 *
413 * @author Kainex <[email protected]>
414 */
415function WiseChatNotifier(options) {
416	var isWindowFocused = true;
417	var isTitleNotificationVisible = false;
418	var rawTitle = document.title;
419	var notificationNumber = 0;
420	var newMessageSoundNotification = null;
421	var userLeftSoundNotification = null;
422	var userJoinedSoundNotification = null;
423	var userMentioningSoundNotification = null;
424
425	function initializeSoundFeatures(soundFile, eventID) {
426		if (soundFile != null && soundFile.length > 0) {
427			var elementId = 'wcMessagesNotificationAudio' + eventID;
428			var soundNotificationElement = jQuery('#' + elementId);
429			if (soundNotificationElement.length > 0) {
430				return soundNotificationElement;
431			}
432			
433			var soundFileURLWav = options.baseDir + 'sounds/' + soundFile + '.wav';
434			var soundFileURLMp3 = options.baseDir + 'sounds/' + soundFile + '.mp3';
435			var soundFileURLOgg = options.baseDir + 'sounds/' + soundFile + '.ogg';
436			var container = jQuery('body');
437			
438			container.append(
439				'<audio id="' + elementId + '" preload="auto" >' +
440					'<source src="' + soundFileURLWav + '" type="audio/x-wav" />' +
441					'<source src="' + soundFileURLOgg + '" type="audio/ogg" />' +
442					'<source src="' + soundFileURLMp3 + '" type="audio/mpeg" />' +
443				'</audio>'
444			);
445
446			return jQuery('#' + elementId);
447		}
448
449		return null;
450	}
451	
452	function showTitleNotification() {
453		if (!isTitleNotificationVisible) {
454			isTitleNotificationVisible = true;
455			rawTitle = document.title;
456		}
457		notificationNumber++;
458		document.title = '(' + notificationNumber + ') (!) ' + rawTitle;
459		setTimeout(function() { showTitleNotificationAnimStep1(); }, 1500);
460	}
461	
462	function showTitleNotificationAnimStep1() {
463		if (isTitleNotificationVisible) {
464			document.title = '(' + notificationNumber + ') ' + rawTitle;
465		}
466	}
467	
468	function hideTitleNotification() {
469		if (isTitleNotificationVisible) {
470			document.title = rawTitle;
471			isTitleNotificationVisible = false;
472			notificationNumber = 0;
473		}
474	}
475	
476	function onWindowBlur() {
477		isWindowFocused = false;
478	}
479	
480	function onWindowFocus() {
481		isWindowFocused = true;
482		hideTitleNotification();
483	}
484	
485	function sendNotifications() {
486		if (options.enableTitleNotifications && !isWindowFocused) {
487			showTitleNotification();
488		}
489		if (!options.userSettings.muteSounds) {
490			if (newMessageSoundNotification !== null && newMessageSoundNotification[0].play) {
491				newMessageSoundNotification[0].play();
492			}
493		}
494	}
495
496	function sendNotificationForEvent(eventName) {
497		if (!options.userSettings.muteSounds) {
498			if (eventName == 'userLeft') {
499				if (userLeftSoundNotification !== null && userLeftSoundNotification[0].play) {
500					userLeftSoundNotification[0].play();
501				}
502			} else if (eventName == 'userJoined') {
503				if (userJoinedSoundNotification !== null && userJoinedSoundNotification[0].play) {
504					userJoinedSoundNotification[0].play();
505				}
506			} else if (eventName == 'userMentioning') {
507				if (userMentioningSoundNotification !== null && userMentioningSoundNotification[0].play) {
508					userMentioningSoundNotification[0].play();
509				}
510			}
511		}
512	}
513	
514	// start-up actions:
515	newMessageSoundNotification = initializeSoundFeatures(options.soundNotification, 'NewMessage');
516	userLeftSoundNotification = initializeSoundFeatures(options.leaveSoundNotification, 'UserLeft');
517	userJoinedSoundNotification = initializeSoundFeatures(options.joinSoundNotification, 'UserJoined');
518	userMentioningSoundNotification = initializeSoundFeatures(options.mentioningSoundNotification, 'UserMentioned');
519
520	// DOM events:
521	jQuery(window).blur(onWindowBlur);
522	jQuery(window).focus(onWindowFocus);
523	
524	// public API:
525	this.sendNotifications = sendNotifications;
526	this.sendNotificationForEvent = sendNotificationForEvent;
527}
528
529/**
530 * WiseChatDateAndTimeRenderer - renders date and time next to each message according to the settings.
531 *
532 * @author Kainex <[email protected]>
533 */
534function WiseChatDateAndTimeRenderer(options, dateFormatter) {
535	var spanDate = '<span class="wcMessageTimeDate">';
536	var spanTime = '<span class="wcMessageTimeHour">';
537	var spanClose = '</span>';
538	var dateAndTimeMode = options.messagesTimeMode;
539	
540	function formatFullDateAndTime(date, nowDate, element) {
541		var timeFormatted = typeof options.messagesTimeFormat !== 'undefined' && options.messagesTimeFormat.length > 0
542			? wiseChatUtilsMomentJS.moment(date).format(options.messagesTimeFormat)
543			: dateFormatter.getLocalizedTime(date);
544
545		if (dateFormatter.isSameDate(nowDate, date)) {
546			element.html(spanTime + timeFormatted + spanClose);
547		} else {
548			var dateFormatted = typeof options.messagesDateFormat !== 'undefined' && options.messagesDateFormat.length > 0
549				? wiseChatUtilsMomentJS.moment(date).format(options.messagesDateFormat)
550				: dateFormatter.getLocalizedDate(date);
551			element.html(
552				spanDate + dateFormatted + spanClose + ' ' +
553				spanTime + timeFormatted + spanClose
554			);
555		}
556		element.attr('data-fixed', '1');
557	}
558	
559	function formatElapsedDateAndTime(date, nowDate, element) {
560		var yesterdayDate = new Date();
561		var diffSeconds = parseInt((nowDate.getTime() - date.getTime()) / 1000);
562		yesterdayDate.setDate(nowDate.getDate() - 1);
563		
564		var formattedDateAndTime = '';
565		var isFixed = false;
566		var timeFormatted = typeof options.messagesTimeFormat !== 'undefined' && options.messagesTimeFormat.length > 0
567			? wiseChatUtilsMomentJS.moment(date).format(options.messagesTimeFormat)
568			: dateFormatter.getLocalizedTime(date);
569
570		if (diffSeconds < 60) {
571			if (diffSeconds <= 0) {
572				diffSeconds = 1;
573			}
574			formattedDateAndTime = spanTime + diffSeconds + ' ' + options.messages.messageSecAgo + spanClose;
575		} else if (diffSeconds < 60 * 60) {
576			formattedDateAndTime = spanTime + parseInt(diffSeconds / 60) + ' ' + options.messages.messageMinAgo + spanClose;
577		} else if (dateFormatter.isSameDate(nowDate, date)) {
578			formattedDateAndTime = spanTime + timeFormatted + spanClose;
579			isFixed = true;
580		} else if (dateFormatter.isSameDate(yesterdayDate, date)) {
581			formattedDateAndTime = spanDate + options.messages.messageYesterday + spanClose + ' ' + spanTime + timeFormatted + spanClose;
582			isFixed = true;
583		} else {
584			var dateFormatted = typeof options.messagesDateFormat !== 'undefined' && options.messagesDateFormat.length > 0
585				? wiseChatUtilsMomentJS.moment(date).format(options.messagesDateFormat)
586				: dateFormatter.getLocalizedDate(date);
587
588			formattedDateAndTime = spanDate + dateFormatted + spanClose + ' ' + spanTime + timeFormatted + spanClose;
589			isFixed = true;
590		}
591		
592		element.html(formattedDateAndTime);
593		if (isFixed) {
594			element.attr('data-fixed', '1');
595		}
596	}
597	
598	/**
599	* Format all elements containing dates withing parent container.
600	* 
601	* @param {jQuery} date Date to format as a string
602	* @param {String} nowISODate Now date
603	* 
604	*/
605	function convertUTCMessagesTime(parentContainer, nowISODate) {
606		if (dateAndTimeMode === 'hidden') {
607			return;
608		}
609		parentContainer.find('.wcMessageTime:not([data-fixed])').each(function(index, element) {
610			element = jQuery(element);
611			
612			var date = dateFormatter.parseISODate(element.data('utc'));
613			var nowDate = dateFormatter.parseISODate(nowISODate);
614			if (dateAndTimeMode === 'elapsed') {
615				formatElapsedDateAndTime(date, nowDate, element);
616			} else {
617				formatFullDateAndTime(date, nowDate, element);
618			}
619		});
620	}
621	
622	// public API:
623	this.convertUTCMessagesTime = convertUTCMessagesTime;
624}
625
626/**
627 * WiseChatProgressBar - controls the main progress bar.
628 */
629function WiseChatProgressBar(options) {
630	var container = jQuery('#' + options.chatId);
631	var progressBar = container.find('.wcMainProgressBar');
632	
633	// provide access to progress events to AJAX requests:
634	(function addXhrProgressEvent(jQuery) {
635		var originalXhr = jQuery.ajaxSettings.xhr;
636		jQuery.ajaxSetup({
637			xhr: function() {
638				var req = originalXhr.call(jQuery.ajaxSettings), that = this;
639				if (req) {
640					if (typeof req.addEventListener == "function" && that.progress !== undefined) {
641						req.addEventListener("progress", function(evt) {
642							that.progress(evt);
643						}, false);
644					}
645					if (typeof req.upload == "object" && that.progressUpload !== undefined) {
646						req.upload.addEventListener("progress", function(evt) {
647							that.progressUpload(evt);
648						}, false);
649					}
650				}
651				return req;
652			}
653		});
654	})(jQuery);
655	
656	function show() {
657		progressBar.show();
658	}
659	
660	function hide() {
661		progressBar.hide();
662	}
663	
664	function setValue(value) {
665		progressBar.attr("value", value);
666	}
667	
668	// public API:
669	this.show = show;
670	this.hide = hide;
671	this.setValue = setValue;
672}
673
674/**
675 * WiseChatEmoticonsPanel class is responsible for displaying emoticons layer and inserting selected emoticon
676 * into message input field.
677 *
678 * @param {Object} options
679 * @param {WiseChatMessages} messages
680 * @constructor
681 */
682function WiseChatEmoticonsPanel(options, messages) {
683    var EMOTICONS_1 = [
684        'zip-it', 'blush', 'angry', 'not-one-care', 'laugh-big', 'please', 'cool', 'minishock',
685        'devil', 'silly', 'smile', 'devil-laugh', 'heart', 'not-guilty', 'hay',
686        'in-love', 'meow', 'tease', 'gift', 'kissy', 'sad', 'speechless', 'goatse',
687        'fools', 'why-thank-you', 'wink', 'angel', 'annoyed', 'flower', 'surprised',
688        'female', 'laugh', 'ill', 'total-shock', 'zzz', 'clock', 'oh', 'mail', 'crazy',
689        'cry', 'boring', 'geek'
690    ];
691    var EMOTICONS_SHORTCUTS_1 = {
692        'smile': ':)', 'wink': ';)', 'laugh': ':D', 'laugh-big': 'xD',
693        'sad': ':(', 'cry': ';(', 'kissy': ':*', 'silly': ':P',
694        'crazy': ';P', 'angry': ':[', 'devil-laugh': ':>', 'devil': ':]', 'goatse': ':|'
695    };
696	var FILES_EXTENSION_1 = 'png';
697	var SUBDIRECTORY_1 = '';
698	var LAYER_WIDTH_1 = null;
699
700	var EMOTICONS_2 = [
701		'angry', 'bulb', 'cafe', 'clap', 'clouds', 'cry', 'devil', 'gift', 'handshake',
702		'heart', 'kissy', 'laugh-big', 'no', 'ok', 'feel_peace', 'oh_please', 'rain', 'scared',
703		'silly', 'snail', 'sun', 'baloons', 'bye', 'cake', 'cleaver', 'cool', 'cry_big',
704		'drink', 'hat', 'heart_big', 'laugh', 'moon', 'offended', 'omg', 'a_phone',
705		'question', 'sad', 'shy', 'smile', 'stars', 'wine'
706	];
707	var EMOTICONS_SHORTCUTS_2 = {
708		'smile': ':)', 'wink': ';)', 'laugh': ':D', 'laugh-big': 'xD',
709		'sad': ':(', 'cry': ';(', 'kissy': ':*', 'silly': ':P',
710		'crazy': ';P', 'angry': ':[', 'devil-laugh': ':>', 'devil': ':]'
711	};
712	var FILES_EXTENSION_2 = 'gif';
713	var SUBDIRECTORY_2 = 'set_2/';
714	var LAYER_WIDTH_2 = 235;
715
716	var EMOTICONS_3 = [
717		'angel', 'confused', 'cthulhu', 'drugged', 'grinning', 'horrified', 'kawaii', 'madness',
718		'shy', 'spiteful', 'terrified', 'tongue_out', 'tongue_out_up_left', 'winking_grinning',
719		'angry', 'cool', 'cute', 'frowning', 'happy', 'hug', 'kissing', 'malicious', 'sick',
720		'stupid', 'thumbs_down', 'tongue_out_laughing', 'unsure', 'winking_tongue_out', 'aww',
721		'creepy', 'cute_winking', 'gasping', 'happy_smiling', 'irritated', 'laughing', 'naww',
722		'smiling', 'surprised', 'thumbs_up', 'tongue_out_left', 'unsure_2', 'blushing', 'crying',
723		'devil', 'greedy', 'heart', 'irritated_2', 'lips_sealed', 'i_am_pouting', 'speechless',
724		'surprised_2', 'tired', 'tongue_out_up', 'winking'
725	];
726	var EMOTICONS_SHORTCUTS_3 = {
727		'smiling': ':)', 'winking': ';)', 'laughing': ':D', 'madness': 'xD',
728		'frowning': ':(', 'crying': ';(', 'kissing': ':*', 'tongue_out': ':P',
729		'winking_tongue_out': ';P', 'angry': ':[', 'devil': ':>', 'devil': ':]', 'irritated': ':|'
730	};
731	var FILES_EXTENSION_3 = 'png';
732	var SUBDIRECTORY_3 = 'set_3/';
733	var LAYER_WIDTH_3 = 195;
734
735	var EMOTICONS_4 = [
736		'angel', 'beer', 'clock', 'crying', 'drink', 'eyeroll', 'glasses-cool', 'jump',
737		'mad-tongue', 'sad', 'sick', 'smile-big', 'thinking', 'wilt',
738		'angry', 'bomb', 'cloudy', 'cute', 'drool', 'fingers-crossed', 'go-away',
739		'kiss', 'mail', 'shock', 'silly', 'smirk', 'tongue', 'wink',
740		'arrogant', 'bye', 'coffee', 'devil', 'embarrassed', 'freaked-out', 'good',
741		'laugh', 'mean', 'shout', 'sleepy', 'star', 'vampire', 'worship',
742		'bad', 'cake', 'confused', 'disapointed', 'excruciating', 'giggle', 'in-love',
743		'love', 'neutral', 'rotfl', 'shut-mouth', 'smile', 'struggle', 'weep', 'yawn',
744		'beauty', 'hypnotized', 'island', 'quiet', 'rose', 'soccerball'
745	];
746	var EMOTICONS_SHORTCUTS_4 = {
747		'smile': ':)', 'wink': ';)', 'laugh': ':D', 'smile-big': 'xD',
748		'sad': ':(', 'crying': ';(', 'kiss': ':*', 'tongue': ':P',
749		'silly': ';P', 'angry': ':[', 'devil': ':>', 'devil': ':]', 'neutral': ':|'
750	};
751	var FILES_EXTENSION_4 = 'png';
752	var SUBDIRECTORY_4 = 'set_4/';
753	var LAYER_WIDTH_4 = 280;
754
755    var LAYER_ID = 'wcEmoticonsLayer' + options.chatId;
756    var container = jQuery('#' + options.chatId);
757    var insertEmoticonButton = container.find('.wcInsertEmoticonButton');
758    var layer = jQuery('#' + LAYER_ID);
759
760	var EMOTICONS = EMOTICONS_1;
761	var EMOTICONS_SHORTCUTS = EMOTICONS_SHORTCUTS_1;
762	var FILES_EXTENSION = FILES_EXTENSION_1;
763	var SUBDIRECTORY = SUBDIRECTORY_1;
764	var LAYER_WIDTH = null;
765	switch (options.emoticonsSet) {
766		case 2:
767			EMOTICONS = EMOTICONS_2;
768			EMOTICONS_SHORTCUTS = EMOTICONS_SHORTCUTS_2;
769			FILES_EXTENSION = FILES_EXTENSION_2;
770			SUBDIRECTORY = SUBDIRECTORY_2;
771			LAYER_WIDTH = LAYER_WIDTH_2;
772			break;
773		case 3:
774			EMOTICONS = EMOTICONS_3;
775			EMOTICONS_SHORTCUTS = EMOTICONS_SHORTCUTS_3;
776			FILES_EXTENSION = FILES_EXTENSION_3;
777			SUBDIRECTORY = SUBDIRECTORY_3;
778			LAYER_WIDTH = LAYER_WIDTH_3;
779			break;
780		case 4:
781			EMOTICONS = EMOTICONS_4;
782			EMOTICONS_SHORTCUTS = EMOTICONS_SHORTCUTS_4;
783			FILES_EXTENSION = FILES_EXTENSION_4;
784			SUBDIRECTORY = SUBDIRECTORY_4;
785			LAYER_WIDTH = LAYER_WIDTH_4;
786			break;
787	}
788
789    if (insertEmoticonButton.length > 0 && layer.length === 0) {
790        layer = jQuery('<div />')
791            .attr('id', LAYER_ID)
792            .attr('class', 'wcEmoticonsLayer')
793            .hide();
794        jQuery('body').append(layer);
795
796        // build buttons:
797        for (var i = 0; i < EMOTICONS.length; i++) {
798            var emoticon = EMOTICONS[i];
799            var imageSrc = options.emoticonsBaseURL + SUBDIRECTORY + emoticon + '.' + FILES_EXTENSION;
800            var button = jQuery('<a />')
801                .attr('href', 'javascript://')
802                .attr('title', emoticon)
803                .append(jQuery('<img />').attr('src', imageSrc))
804                .click(function (emoticon) {
805                    return function () {
806                        onEmoticonClick(emoticon);
807                    }
808                }(emoticon));
809            layer.append(button);
810        }
811	}
812
813	if (LAYER_WIDTH !== null) {
814		layer.css('width', LAYER_WIDTH + 'px');
815	}
816
817    function hideLayer() {
818        layer.hide();
819        jQuery(document).unbind("mousedown", onDocumentMouseDown);
820    }
821
822    function showLayer() {
823        layer.show();
824        jQuery(document).bind("mousedown", onDocumentMouseDown);
825    }
826
827    function onDocumentMouseDown(event) {
828        if (insertEmoticonButton[0] != event.target && layer[0] != event.target && !jQuery.contains(layer[0], event.target)) {
829            hideLayer();
830        }
831    }
832
833	function onInsertEmoticonButtonClick(e) {
834		e.preventDefault();
835
836        if (!layer.is(':visible')) {
837			var leftValue = insertEmoticonButton.offset().left - layer.outerWidth() - 5;
838			if (leftValue < 0) {
839				leftValue = container.offset().left;
840
841				if (container.outerWidth() < LAYER_WIDTH) {
842					layer.css({
843						width: container.outerWidth() + 'px'
844					});
845
846				} else {
847					layer.css('width', LAYER_WIDTH + 'px');
848				}
849			}
850			var topValue = insertEmoticonButton.offset().top - layer.outerHeight();
851
852            layer.css({
853                top: topValue,
854                left: leftValue
855            });
856            showLayer();
857        } else {
858            hideLayer();
859        }
860	}
861
862    var onEmoticonClick = function(emoticon) {
863        var emoticonCode = EMOTICONS_SHORTCUTS[emoticon] ? EMOTICONS_SHORTCUTS[emoticon] : '<' + emoticon + '>';
864        messages.appendTextToInputField(emoticonCode);
865        hideLayer();
866    };
867
868	insertEmoticonButton.click(onInsertEmoticonButtonClick);
869}
870
871// chat init script:
872jQuery(window).on('load', function() {
873	jQuery(".wcContainer[data-wc-config]").each(function(index) {
874		var config = jQuery(this).data('wc-config');
875		if (typeof config !== 'object') {
876			jQuery(this).prepend('<strong style="color:#f00;">Error: invalid Wise Chat configuration</strong>');
877			return;
878		}
879		
880		var messages = jQuery(this).find('.wcMessages');
881		if (messages.length > 0 && config.messagesOrder == 'ascending') {
882			messages.scrollTop(messages[0].scrollHeight);
883		}
884		
885		if (typeof(window['wiseChatInstances']) == 'undefined') {
886			window['wiseChatInstances'] = {};
887		}
888		
889		if (typeof(window.wiseChatInstances[config.chatId]) == 'undefined') {
890			window.wiseChatInstances[config.chatId] = new WiseChatController(config);
891		}
892	});
893});

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.