Completed
Push — master ( 6b294e...ca4897 )
by Rain
02:05
created

Utils.js ➔ getRealHeight   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 7
cc 1
rs 9.4285
1
2
import window from 'window';
3
import $ from '$';
4
import _ from '_';
5
import ko from 'ko';
6
import Autolinker from 'Autolinker';
7
8
import {$win, $div, $hcont, dropdownVisibility, data as GlobalsData} from 'Common/Globals';
9
import {ComposeType, EventKeyCode, SaveSettingsStep, FolderType} from 'Common/Enums';
10
import {Mime} from 'Common/Mime';
11
import {jassl} from 'Common/Jassl';
12
13
const trim = $.trim;
14
const inArray = $.inArray;
15
const isArray = _.isArray;
16
const isObject = _.isObject;
17
const isFunc = _.isFunction;
18
const isUnd = _.isUndefined;
19
const isNull = _.isNull;
20
const has = _.has;
21
const bind = _.bind;
22
const noop = () => {}; // eslint-disable-line no-empty-function
23
const noopTrue = () => true;
24
const noopFalse = () => false;
25
26
export {trim, inArray, isArray, isObject, isFunc, isUnd, isNull, has, bind, noop, noopTrue, noopFalse, jassl};
27
28
/**
29
 * @param {Function} func
30
 */
31
export function silentTryCatch(func)
32
{
33
	try {
34
		func();
35
	}
36
	catch (e) {} // eslint-disable-line no-empty
37
}
38
39
/**
40
 * @param {*} value
41
 * @returns {boolean}
42
 */
43
export function isNormal(value)
44
{
45
	return !isUnd(value) && !isNull(value);
46
}
47
48
/**
49
 * @param {(string|number)} value
50
 * @param {boolean=} includeZero = true
51
 * @returns {boolean}
52
 */
53
export function isPosNumeric(value, includeZero = true)
54
{
55
	return !isNormal(value) ? false :
56
		(includeZero ? (/^[0-9]*$/).test(value.toString()) : (/^[1-9]+[0-9]*$/).test(value.toString()));
57
}
58
59
/**
60
 * @param {*} value
61
 * @param {number=} defaultValur = 0
62
 * @returns {number}
63
 */
64
export function pInt(value, defaultValur = 0)
65
{
66
	const result = isNormal(value) && '' !== value ? window.parseInt(value, 10) : defaultValur;
67
	return window.isNaN(result) ? defaultValur : result;
68
}
69
70
/**
71
 * @param {*} value
72
 * @returns {string}
73
 */
74
export function pString(value)
75
{
76
	return isNormal(value) ? '' + value : '';
77
}
78
79
/**
80
 * @param {*} value
81
 * @returns {boolean}
82
 */
83
export function pBool(value)
84
{
85
	return !!value;
86
}
87
88
/**
89
 * @param {*} value
90
 * @returns {string}
91
 */
92
export function boolToAjax(value)
93
{
94
	return value ? '1' : '0';
95
}
96
97
/**
98
 * @param {*} values
99
 * @returns {boolean}
100
 */
101
export function isNonEmptyArray(values)
102
{
103
	return isArray(values) && 0 < values.length;
104
}
105
106
/**
107
 * @param {string} component
108
 * @returns {string}
109
 */
110
export function encodeURIComponent(component)
111
{
112
	return window.encodeURIComponent(component);
113
}
114
115
/**
116
 * @param {string} component
117
 * @returns {string}
118
 */
119
export function decodeURIComponent(component)
120
{
121
	return window.decodeURIComponent(component);
122
}
123
124
/**
125
 * @param {string} url
126
 * @returns {string}
127
 */
128
export function decodeURI(url)
129
{
130
	return window.decodeURI(url);
131
}
132
133
/**
134
 * @param {string} url
135
 * @returns {string}
136
 */
137
export function encodeURI(url)
138
{
139
	return window.encodeURI(url);
140
}
141
142
/**
143
 * @param {string} queryString
144
 * @returns {Object}
145
 */
146
export function simpleQueryParser(queryString)
147
{
148
	let
149
		index = 0,
150
		len = 0,
151
		temp = null;
0 ignored issues
show
Unused Code introduced by
The assignment to temp seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
152
153
	const
154
		queries = queryString.split('&'),
155
		params = {};
156
157
	for (len = queries.length; index < len; index++)
158
	{
159
		temp = queries[index].split('=');
160
		params[decodeURIComponent(temp[0])] = decodeURIComponent(temp[1]);
161
	}
162
163
	return params;
164
}
165
166
/**
167
 * @param {number=} len = 32
168
 * @returns {string}
169
 */
170
export function fakeMd5(len = 32)
171
{
172
	const
173
		line = '0123456789abcdefghijklmnopqrstuvwxyz',
174
		lineLen = line.length;
175
176
	len = pInt(len);
177
178
	let result = '';
179
	while (result.length < len)
180
	{
181
		result += line.substr(window.Math.round(window.Math.random() * lineLen), 1);
182
	}
183
184
	return result;
185
}
186
187
/**
188
 * @param {string} text
189
 * @returns {string}
190
 */
191
export function encodeHtml(text)
192
{
193
	return isNormal(text) ? _.escape(text.toString()) : '';
194
}
195
196
/**
197
 * @param {string} text
198
 * @param {number=} len = 100
199
 * @returns {string}
200
 */
201
export function splitPlainText(text, len = 100)
202
{
203
	let
204
		prefix = '',
205
		subText = '',
206
		result = text,
207
		spacePos = 0,
208
		newLinePos = 0;
209
210
	while (result.length > len)
211
	{
212
		subText = result.substring(0, len);
213
		spacePos = subText.lastIndexOf(' ');
214
		newLinePos = subText.lastIndexOf('\n');
215
216
		if (-1 !== newLinePos)
217
		{
218
			spacePos = newLinePos;
219
		}
220
221
		if (-1 === spacePos)
222
		{
223
			spacePos = len;
224
		}
225
226
		prefix += subText.substring(0, spacePos) + '\n';
227
		result = result.substring(spacePos + 1);
228
	}
229
230
	return prefix + result;
231
}
232
233
const timeOutAction = (function() {
234
	const timeOuts = {};
235
	return (action, fFunction, timeOut) => {
236
		timeOuts[action] = isUnd(timeOuts[action]) ? 0 : timeOuts[action];
237
		window.clearTimeout(timeOuts[action]);
238
		timeOuts[action] = window.setTimeout(fFunction, timeOut);
239
	};
240
}());
241
242
const timeOutActionSecond = (function() {
243
	const timeOuts = {};
244
	return (action, fFunction, timeOut) => {
245
		if (!timeOuts[action])
246
		{
247
			timeOuts[action] = window.setTimeout(() => {
248
				fFunction();
249
				timeOuts[action] = 0;
250
			}, timeOut);
251
		}
252
	};
253
}());
254
255
export {timeOutAction, timeOutActionSecond};
256
257
/**
258
 * @returns {boolean}
259
 */
260
export function inFocus()
261
{
262
	try {
263
		if (window.document.activeElement)
264
		{
265
			if (isUnd(window.document.activeElement.__inFocusCache))
266
			{
267
				window.document.activeElement.__inFocusCache = $(window.document.activeElement).is('input,textarea,iframe,.cke_editable');
268
			}
269
270
			return !!window.document.activeElement.__inFocusCache;
271
		}
272
	}
273
	catch (e) {} // eslint-disable-line no-empty
274
275
	return false;
276
}
277
278
/**
279
 * @param {boolean} force
280
 * @returns {void}
281
 */
282
export function removeInFocus(force)
283
{
284
	if (window.document && window.document.activeElement && window.document.activeElement.blur)
285
	{
286
		try {
287
			const activeEl = $(window.document.activeElement);
288
			if (activeEl && activeEl.is('input,textarea'))
289
			{
290
				window.document.activeElement.blur();
291
			}
292
			else if (force)
293
			{
294
				window.document.activeElement.blur();
295
			}
296
		}
297
		catch (e) {} // eslint-disable-line no-empty
298
	}
299
}
300
301
/**
302
 * @returns {void}
303
 */
304
export function removeSelection()
305
{
306
	try {
307
		if (window && window.getSelection)
308
		{
309
			const sel = window.getSelection();
310
			if (sel && sel.removeAllRanges)
311
			{
312
				sel.removeAllRanges();
313
			}
314
		}
315
		else if (window.document && window.document.selection && window.document.selection.empty)
316
		{
317
			window.document.selection.empty();
318
		}
319
	}
320
	catch (e) {} // eslint-disable-line no-empty
321
}
322
323
/**
324
 * @param {string} prefix
325
 * @param {string} subject
326
 * @returns {string}
327
 */
328
export function replySubjectAdd(prefix, subject)
329
{
330
	prefix = trim(prefix.toUpperCase());
331
	subject = trim(subject.replace(/[\s]+/g, ' '));
332
333
	let drop = false,
334
		re = 'RE' === prefix,
335
		fwd = 'FWD' === prefix;
336
337
	const
338
		parts = [],
339
		prefixIsRe = !fwd;
340
341
	if ('' !== subject)
342
	{
343
		_.each(subject.split(':'), (part) => {
344
			const trimmedPart = trim(part);
345
			if (!drop && ((/^(RE|FWD)$/i).test(trimmedPart) || (/^(RE|FWD)[\[\(][\d]+[\]\)]$/i).test(trimmedPart)))
346
			{
347
				if (!re)
348
				{
349
					re = !!(/^RE/i).test(trimmedPart);
350
				}
351
352
				if (!fwd)
353
				{
354
					fwd = !!(/^FWD/i).test(trimmedPart);
355
				}
356
			}
357
			else
358
			{
359
				parts.push(part);
360
				drop = true;
361
			}
362
		});
363
	}
364
365
	if (prefixIsRe)
366
	{
367
		re = false;
368
	}
369
	else
370
	{
371
		fwd = false;
372
	}
373
374
	return trim(
375
		(prefixIsRe ? 'Re: ' : 'Fwd: ') +
376
		(re ? 'Re: ' : '') +
377
		(fwd ? 'Fwd: ' : '') +
378
		trim(parts.join(':'))
379
	);
380
}
381
382
/**
383
 * @param {number} num
384
 * @param {number} dec
385
 * @returns {number}
386
 */
387
export function roundNumber(num, dec)
388
{
389
	return window.Math.round(num * window.Math.pow(10, dec)) / window.Math.pow(10, dec);
390
}
391
392
/**
393
 * @param {(number|string)} sizeInBytes
394
 * @returns {string}
395
 */
396
export function friendlySize(sizeInBytes)
397
{
398
	sizeInBytes = pInt(sizeInBytes);
399
400
	switch (true)
401
	{
402
		case 1073741824 <= sizeInBytes:
403
			return roundNumber(sizeInBytes / 1073741824, 1) + 'GB';
404
		case 1048576 <= sizeInBytes:
405
			return roundNumber(sizeInBytes / 1048576, 1) + 'MB';
406
		case 1024 <= sizeInBytes:
407
			return roundNumber(sizeInBytes / 1024, 0) + 'KB';
408
		// no default
409
	}
410
411
	return sizeInBytes + 'B';
412
}
413
414
/**
415
 * @param {string} desc
416
 */
417
export function log(desc)
418
{
419
	if (window.console && window.console.log)
420
	{
421
		window.console.log(desc);
422
	}
423
}
424
425
/**
426
 * @param {?} object
427
 * @param {string} methodName
428
 * @param {Array=} params
429
 * @param {number=} delay = 0
430
 */
431
export function delegateRun(object, methodName, params, delay = 0)
432
{
433
	if (object && object[methodName])
434
	{
435
		delay = pInt(delay);
436
		params = isArray(params) ? params : [];
437
438
		if (0 >= delay)
439
		{
440
			object[methodName](...params);
441
		}
442
		else
443
		{
444
			_.delay(() => {
445
				object[methodName](...params);
446
			}, delay);
447
		}
448
	}
449
}
450
451
/**
452
 * @param {?} event
453
 */
454
export function killCtrlACtrlS(event)
455
{
456
	event = event || window.event;
457
	if (event && event.ctrlKey && !event.shiftKey && !event.altKey)
458
	{
459
		const key = event.keyCode || event.which;
460
		if (key === EventKeyCode.S)
461
		{
462
			event.preventDefault();
463
			return;
0 ignored issues
show
Unused Code introduced by
This return has no effect and can be removed.
Loading history...
464
		}
465
		else if (key === EventKeyCode.A)
466
		{
467
			const sender = event.target || event.srcElement;
468
			if (sender && ('true' === '' + sender.contentEditable ||
469
				(sender.tagName && sender.tagName.match(/INPUT|TEXTAREA/i))))
470
			{
471
				return;
472
			}
473
474
			if (window.getSelection)
475
			{
476
				window.getSelection().removeAllRanges();
477
			}
478
			else if (window.document.selection && window.document.selection.clear)
479
			{
480
				window.document.selection.clear();
481
			}
482
483
			event.preventDefault();
484
		}
485
	}
486
}
487
488
/**
489
 * @param {(Object|null|undefined)} context
490
 * @param {Function} fExecute
491
 * @param {(Function|boolean|null)=} fCanExecute = true
492
 * @returns {Function}
493
 */
494
export function createCommandLegacy(context, fExecute, fCanExecute = true)
495
{
496
	let fResult = null;
497
	const fNonEmpty = (...args) => {
498
		if (fResult && fResult.canExecute && fResult.canExecute())
499
		{
500
			fExecute.apply(context, args);
501
		}
502
		return false;
503
	};
504
505
	fResult = fExecute ? fNonEmpty : noop;
506
	fResult.enabled = ko.observable(true);
507
	fResult.isCommand = true;
508
509
	if (isFunc(fCanExecute))
510
	{
511
		fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && fCanExecute.call(context));
512
	}
513
	else
514
	{
515
		fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && !!fCanExecute);
516
	}
517
518
	return fResult;
519
}
520
521
/**
522
 * @param {string} theme
523
 * @returns {string}
524
 */
525
export const convertThemeName = _.memoize((theme) => {
526
527
	if ('@custom' === theme.substr(-7))
528
	{
529
		theme = trim(theme.substring(0, theme.length - 7));
530
	}
531
532
	return trim(theme.replace(/[^a-zA-Z0-9]+/g, ' ').replace(/([A-Z])/g, ' $1').replace(/[\s]+/g, ' '));
533
});
534
535
/**
536
 * @param {string} name
537
 * @returns {string}
538
 */
539
export function quoteName(name)
540
{
541
	return name.replace(/["]/g, '\\"');
542
}
543
544
/**
545
 * @returns {number}
546
 */
547
export function microtime()
548
{
549
	return (new window.Date()).getTime();
550
}
551
552
/**
553
 * @returns {number}
554
 */
555
export function timestamp()
556
{
557
	return window.Math.round(microtime() / 1000);
558
}
559
560
/**
561
 *
562
 * @param {string} language
563
 * @param {boolean=} isEng = false
564
 * @returns {string}
565
 */
566
export function convertLangName(language, isEng = false)
567
{
568
	return require('Common/Translator').i18n('LANGS_NAMES' + (true === isEng ? '_EN' : '') + '/LANG_' +
569
		language.toUpperCase().replace(/[^a-zA-Z0-9]+/g, '_'), null, language);
570
}
571
572
/**
573
 * @returns {object}
574
 */
575
export function draggablePlace()
576
{
577
	return $('<div class="draggablePlace">' +
578
		'<span class="text"></span>&nbsp;' +
579
		'<i class="icon-copy icon-white visible-on-ctrl"></i>' +
580
		'<i class="icon-mail icon-white hidden-on-ctrl"></i>' +
581
		'</div>'
582
	).appendTo('#rl-hidden');
583
}
584
585
/**
586
 * @param {object} domOption
0 ignored issues
show
Documentation introduced by
The parameter domOption does not exist. Did you maybe forget to remove this comment?
Loading history...
587
 * @param {object} item
588
 * @returns {void}
589
 */
590
export function defautOptionsAfterRender(domItem, item)
591
{
592
	if (item && !isUnd(item.disabled) && domItem)
593
	{
594
		$(domItem)
595
			.toggleClass('disabled', item.disabled)
596
			.prop('disabled', item.disabled);
597
	}
598
}
599
600
/**
601
 * @param {string} title
0 ignored issues
show
Documentation introduced by
The parameter title does not exist. Did you maybe forget to remove this comment?
Loading history...
602
 * @param {Object} body
603
 * @param {boolean} isHtml
0 ignored issues
show
Documentation introduced by
The parameter isHtml does not exist. Did you maybe forget to remove this comment?
Loading history...
604
 * @param {boolean} print
0 ignored issues
show
Documentation introduced by
The parameter print does not exist. Did you maybe forget to remove this comment?
Loading history...
605
 */
606
export function clearBqSwitcher(body)
607
{
608
	body.find('blockquote.rl-bq-switcher').removeClass('rl-bq-switcher hidden-bq');
609
	body.find('.rlBlockquoteSwitcher').off('.rlBlockquoteSwitcher').remove();
610
	body.find('[data-html-editor-font-wrapper]').removeAttr('data-html-editor-font-wrapper');
611
}
612
613
/**
614
 * @param {object} messageData
0 ignored issues
show
Documentation introduced by
The parameter messageData does not exist. Did you maybe forget to remove this comment?
Loading history...
615
 * @param {Object} body
616
 * @param {boolean} isHtml
617
 * @param {boolean} print
618
 * @returns {void}
619
 */
620
export function previewMessage({title, subject, date, fromCreds, toCreds, toLabel}, body, isHtml, print)
621
{
622
	const
623
		win = window.open(''),
624
		doc = win.document,
625
		bodyClone = body.clone(),
626
		bodyClass = isHtml ? 'html' : 'plain';
627
628
	clearBqSwitcher(bodyClone);
629
630
	const html = bodyClone ? bodyClone.html() : '';
631
632
	doc.write(require('Html/PreviewMessage.html')
633
		.replace('{{title}}', encodeHtml(title))
634
		.replace('{{subject}}', encodeHtml(subject))
635
		.replace('{{date}}', encodeHtml(date))
636
		.replace('{{fromCreds}}', encodeHtml(fromCreds))
637
		.replace('{{toCreds}}', encodeHtml(toCreds))
638
		.replace('{{toLabel}}', encodeHtml(toLabel))
639
		.replace('{{bodyClass}}', bodyClass)
640
		.replace('{{html}}', html)
641
	);
642
643
	doc.close();
644
645
	if (print)
646
	{
647
		window.setTimeout(() => win.print(), 100);
648
	}
649
}
650
651
/**
652
 * @param {Function} fCallback
653
 * @param {?} koTrigger
654
 * @param {?} context = null
655
 * @param {number=} timer = 1000
656
 * @returns {Function}
657
 */
658
export function settingsSaveHelperFunction(fCallback, koTrigger, context = null, timer = 1000)
659
{
660
	timer = pInt(timer);
661
	return (type, data, cached, requestAction, requestParameters) => {
662
		koTrigger.call(context, data && data.Result ? SaveSettingsStep.TrueResult : SaveSettingsStep.FalseResult);
663
		if (fCallback)
664
		{
665
			fCallback.call(context, type, data, cached, requestAction, requestParameters);
666
		}
667
		_.delay(() => {
668
			koTrigger.call(context, SaveSettingsStep.Idle);
669
		}, timer);
670
	};
671
}
672
673
/**
674
 * @param {object} koTrigger
675
 * @param {mixed} context
676
 * @returns {mixed}
677
 */
678
export function settingsSaveHelperSimpleFunction(koTrigger, context)
679
{
680
	return settingsSaveHelperFunction(null, koTrigger, context, 1000);
681
}
682
683
/**
684
 * @param {object} remote
685
 * @param {string} settingName
686
 * @param {string} type
687
 * @param {function} fTriggerFunction
688
 * @returns {function}
689
 */
690
export function settingsSaveHelperSubscribeFunction(remote, settingName, type, fTriggerFunction)
691
{
692
	return (value) => {
693
694
		if (remote)
695
		{
696
			switch (type)
697
			{
698
				case 'bool':
699
				case 'boolean':
700
					value = value ? '1' : '0';
701
					break;
702
				case 'int':
703
				case 'integer':
704
				case 'number':
705
					value = pInt(value);
706
					break;
707
				case 'trim':
708
					value = trim(value);
709
					break;
710
				default:
711
					value = pString(value);
712
					break;
713
			}
714
715
			const data = {};
716
			data[settingName] = value;
717
718
			if (remote.saveAdminConfig)
719
			{
720
				remote.saveAdminConfig(fTriggerFunction || null, data);
721
			}
722
			else if (remote.saveSettings)
723
			{
724
				remote.saveSettings(fTriggerFunction || null, data);
725
			}
726
		}
727
	};
728
}
729
730
/**
731
 * @param {string} html
732
 * @returns {string}
733
 */
734
export function findEmailAndLinks(html)
735
{
736
//	return html;
737
	return Autolinker ? Autolinker.link(html, {
738
		newWindow: true,
739
		stripPrefix: false,
740
		urls: true,
741
		email: true,
742
		mention: false,
743
		phone: false,
744
		hashtag: false,
745
		replaceFn: function(match) {
746
			return !(match && 'url' === match.getType() && match.matchedText && 0 !== match.matchedText.indexOf('http'));
747
		}
748
	}) : html;
749
}
750
751
/**
752
 * @param {string} html
753
 * @returns {string}
754
 */
755
export function htmlToPlain(html)
756
{
757
	let
758
		pos = 0,
759
		limit = 0,
760
		iP1 = 0,
761
		iP2 = 0,
762
		iP3 = 0,
763
764
		text = '';
765
766
	const
767
		convertBlockquote = (blockquoteText) => {
768
			blockquoteText = '> ' + trim(blockquoteText).replace(/\n/gm, '\n> ');
769
			return blockquoteText.replace(/(^|\n)([> ]+)/gm,
770
				(...args) => (args && 2 < args.length ? args[1] + trim(args[2].replace(/[\s]/g, '')) + ' ' : ''));
771
		},
772
		convertDivs = (...args) => {
773
			if (args && 1 < args.length)
774
			{
775
				let divText = trim(args[1]);
776
				if (0 < divText.length)
777
				{
778
					divText = divText.replace(/<div[^>]*>([\s\S\r\n]*)<\/div>/gmi, convertDivs);
779
					divText = '\n' + trim(divText) + '\n';
780
				}
781
782
				return divText;
783
			}
784
785
			return '';
786
		},
787
		convertPre = (...args) => (args && 1 < args.length ? args[1].toString().replace(/[\n]/gm, '<br />').replace(/[\r]/gm, '') : ''),
788
		fixAttibuteValue = (...args) => (args && 1 < args.length ? '' + args[1] + _.escape(args[2]) : ''),
789
		convertLinks = (...args) => (args && 1 < args.length ? trim(args[1]) : '');
790
791
	text = html
792
		.replace(/<p[^>]*><\/p>/gi, '')
793
		.replace(/<pre[^>]*>([\s\S\r\n\t]*)<\/pre>/gmi, convertPre)
794
		.replace(/[\s]+/gm, ' ')
795
		.replace(/((?:href|data)\s?=\s?)("[^"]+?"|'[^']+?')/gmi, fixAttibuteValue)
796
		.replace(/<br[^>]*>/gmi, '\n')
797
		.replace(/<\/h[\d]>/gi, '\n')
798
		.replace(/<\/p>/gi, '\n\n')
799
		.replace(/<ul[^>]*>/gmi, '\n')
800
		.replace(/<\/ul>/gi, '\n')
801
		.replace(/<li[^>]*>/gmi, ' * ')
802
		.replace(/<\/li>/gi, '\n')
803
		.replace(/<\/td>/gi, '\n')
804
		.replace(/<\/tr>/gi, '\n')
805
		.replace(/<hr[^>]*>/gmi, '\n_______________________________\n\n')
806
		.replace(/<div[^>]*>([\s\S\r\n]*)<\/div>/gmi, convertDivs)
807
		.replace(/<blockquote[^>]*>/gmi, '\n__bq__start__\n')
808
		.replace(/<\/blockquote>/gmi, '\n__bq__end__\n')
809
		.replace(/<a [^>]*>([\s\S\r\n]*?)<\/a>/gmi, convertLinks)
810
		.replace(/<\/div>/gi, '\n')
811
		.replace(/&nbsp;/gi, ' ')
812
		.replace(/&quot;/gi, '"')
813
		.replace(/<[^>]*>/gm, '');
814
815
	text = $div.html(text).text();
816
817
	text = text
818
		.replace(/\n[ \t]+/gm, '\n')
819
		.replace(/[\n]{3,}/gm, '\n\n')
820
		.replace(/&gt;/gi, '>')
821
		.replace(/&lt;/gi, '<')
822
		.replace(/&amp;/gi, '&');
823
824
	text = splitPlainText(trim(text));
825
826
	pos = 0;
827
	limit = 800;
828
829
	while (0 < limit)
830
	{
831
		limit -= 1;
832
		iP1 = text.indexOf('__bq__start__', pos);
833
		if (-1 < iP1)
834
		{
835
			iP2 = text.indexOf('__bq__start__', iP1 + 5);
836
			iP3 = text.indexOf('__bq__end__', iP1 + 5);
837
838
			if ((-1 === iP2 || iP3 < iP2) && iP1 < iP3)
839
			{
840
				text = text.substring(0, iP1) +
841
					convertBlockquote(text.substring(iP1 + 13, iP3)) +
842
					text.substring(iP3 + 11);
843
844
				pos = 0;
845
			}
846
			else if (-1 < iP2 && iP2 < iP3)
847
			{
848
				pos = iP2 - 1;
849
			}
850
			else
851
			{
852
				pos = 0;
853
			}
854
		}
855
		else
856
		{
857
			break;
858
		}
859
	}
860
861
	text = text
862
		.replace(/__bq__start__/gm, '')
863
		.replace(/__bq__end__/gm, '');
864
865
	return text;
866
}
867
868
/**
869
 * @param {string} plain
870
 * @param {boolean} findEmailAndLinksInText = false
871
 * @returns {string}
872
 */
873
export function plainToHtml(plain, findEmailAndLinksInText = false)
874
{
875
	plain = plain.toString().replace(/\r/g, '');
876
	plain = plain.replace(/^>[> ]>+/gm, ([match]) => (match ? match.replace(/[ ]+/g, '') : match));
877
878
	let
879
		bIn = false,
880
		bDo = true,
0 ignored issues
show
Unused Code introduced by
The assignment to variable bDo seems to be never used. Consider removing it.
Loading history...
881
		bStart = true,
0 ignored issues
show
Unused Code introduced by
The assignment to variable bStart seems to be never used. Consider removing it.
Loading history...
882
		aNextText = [],
0 ignored issues
show
Unused Code introduced by
The assignment to variable aNextText seems to be never used. Consider removing it.
Loading history...
883
		sLine = '',
884
		iIndex = 0,
885
		aText = plain.split('\n');
886
887
	do
888
	{
889
		bDo = false;
890
		aNextText = [];
891
		for (iIndex = 0; iIndex < aText.length; iIndex++)
892
		{
893
			sLine = aText[iIndex];
894
			bStart = '>' === sLine.substr(0, 1);
895
			if (bStart && !bIn)
896
			{
897
				bDo = true;
898
				bIn = true;
899
				aNextText.push('~~~blockquote~~~');
900
				aNextText.push(sLine.substr(1));
901
			}
902
			else if (!bStart && bIn)
903
			{
904
				if ('' !== sLine)
905
				{
906
					bIn = false;
907
					aNextText.push('~~~/blockquote~~~');
908
					aNextText.push(sLine);
909
				}
910
				else
911
				{
912
					aNextText.push(sLine);
913
				}
914
			}
915
			else if (bStart && bIn)
916
			{
917
				aNextText.push(sLine.substr(1));
918
			}
919
			else
920
			{
921
				aNextText.push(sLine);
922
			}
923
		}
924
925
		if (bIn)
926
		{
927
			bIn = false;
928
			aNextText.push('~~~/blockquote~~~');
929
		}
930
931
		aText = aNextText;
932
	}
933
	while (bDo);
934
935
	plain = aText.join('\n');
936
937
	plain = plain
938
//			.replace(/~~~\/blockquote~~~\n~~~blockquote~~~/g, '\n')
939
		.replace(/&/g, '&amp;')
940
		.replace(/>/g, '&gt;').replace(/</g, '&lt;')
941
		.replace(/~~~blockquote~~~[\s]*/g, '<blockquote>')
942
		.replace(/[\s]*~~~\/blockquote~~~/g, '</blockquote>')
943
		.replace(/\n/g, '<br />');
944
945
	return findEmailAndLinksInText ? findEmailAndLinks(plain) : plain;
946
}
947
948
window['rainloop_Utils_htmlToPlain'] = htmlToPlain; // eslint-disable-line dot-notation
949
window['rainloop_Utils_plainToHtml'] = plainToHtml; // eslint-disable-line dot-notation
950
951
/**
952
 * @param {Array} aSystem
953
 * @param {Array} aList
954
 * @param {Array=} aDisabled
955
 * @param {Array=} aHeaderLines
956
 * @param {?number=} iUnDeep
957
 * @param {Function=} fDisableCallback
958
 * @param {Function=} fVisibleCallback
959
 * @param {Function=} fRenameCallback
960
 * @param {boolean=} bSystem
961
 * @param {boolean=} bBuildUnvisible
962
 * @returns {Array}
963
 */
964
export function folderListOptionsBuilder(aSystem, aList, aDisabled, aHeaderLines,
965
	iUnDeep, fDisableCallback, fVisibleCallback, fRenameCallback, bSystem, bBuildUnvisible)
966
{
967
	let
968
		/**
969
		 * @type {?FolderModel}
970
		 */
971
		oItem = null,
0 ignored issues
show
Unused Code introduced by
The assignment to oItem seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
972
		bSep = false,
0 ignored issues
show
Unused Code introduced by
The assignment to variable bSep seems to be never used. Consider removing it.
Loading history...
973
		iIndex = 0,
974
		iLen = 0,
975
		aResult = [];
976
977
	const sDeepPrefix = '\u00A0\u00A0\u00A0';
978
979
	bBuildUnvisible = isUnd(bBuildUnvisible) ? false : !!bBuildUnvisible;
980
	bSystem = !isNormal(bSystem) ? 0 < aSystem.length : bSystem;
981
	iUnDeep = !isNormal(iUnDeep) ? 0 : iUnDeep;
982
	fDisableCallback = isNormal(fDisableCallback) ? fDisableCallback : null;
983
	fVisibleCallback = isNormal(fVisibleCallback) ? fVisibleCallback : null;
984
	fRenameCallback = isNormal(fRenameCallback) ? fRenameCallback : null;
985
986
	if (!isArray(aDisabled))
987
	{
988
		aDisabled = [];
989
	}
990
991
	if (!isArray(aHeaderLines))
992
	{
993
		aHeaderLines = [];
994
	}
995
996
	for (iIndex = 0, iLen = aHeaderLines.length; iIndex < iLen; iIndex++)
997
	{
998
		aResult.push({
999
			id: aHeaderLines[iIndex][0],
1000
			name: aHeaderLines[iIndex][1],
1001
			system: false,
1002
			seporator: false,
1003
			disabled: false
1004
		});
1005
	}
1006
1007
	bSep = true;
1008
	for (iIndex = 0, iLen = aSystem.length; iIndex < iLen; iIndex++)
1009
	{
1010
		oItem = aSystem[iIndex];
1011
		if (fVisibleCallback ? fVisibleCallback(oItem) : true)
1012
		{
1013
			if (bSep && 0 < aResult.length)
1014
			{
1015
				aResult.push({
1016
					id: '---',
1017
					name: '---',
1018
					system: false,
1019
					seporator: true,
1020
					disabled: true
1021
				});
1022
			}
1023
1024
			bSep = false;
1025
			aResult.push({
1026
				id: oItem.fullNameRaw,
1027
				name: fRenameCallback ? fRenameCallback(oItem) : oItem.name(),
1028
				system: true,
1029
				seporator: false,
1030
				disabled: !oItem.selectable || -1 < inArray(oItem.fullNameRaw, aDisabled) ||
1031
					(fDisableCallback ? fDisableCallback(oItem) : false)
1032
			});
1033
		}
1034
	}
1035
1036
	bSep = true;
1037
	for (iIndex = 0, iLen = aList.length; iIndex < iLen; iIndex++)
1038
	{
1039
		oItem = aList[iIndex];
1040
//			if (oItem.subScribed() || !oItem.existen || bBuildUnvisible)
1041
		if ((oItem.subScribed() || !oItem.existen || bBuildUnvisible) && (oItem.selectable || oItem.hasSubScribedSubfolders()))
1042
		{
1043
			if (fVisibleCallback ? fVisibleCallback(oItem) : true)
1044
			{
1045
				if (FolderType.User === oItem.type() || !bSystem || oItem.hasSubScribedSubfolders())
1046
				{
1047
					if (bSep && 0 < aResult.length)
1048
					{
1049
						aResult.push({
1050
							id: '---',
1051
							name: '---',
1052
							system: false,
1053
							seporator: true,
1054
							disabled: true
1055
						});
1056
					}
1057
1058
					bSep = false;
1059
					aResult.push({
1060
						id: oItem.fullNameRaw,
1061
						name: (new window.Array(oItem.deep + 1 - iUnDeep)).join(sDeepPrefix) +
1062
							(fRenameCallback ? fRenameCallback(oItem) : oItem.name()),
1063
						system: false,
1064
						seporator: false,
1065
						disabled: !oItem.selectable || -1 < inArray(oItem.fullNameRaw, aDisabled) ||
1066
							(fDisableCallback ? fDisableCallback(oItem) : false)
1067
					});
1068
				}
1069
			}
1070
		}
1071
1072
		if (oItem.subScribed() && 0 < oItem.subFolders().length)
1073
		{
1074
			aResult = aResult.concat(folderListOptionsBuilder([], oItem.subFolders(), aDisabled, [],
1075
				iUnDeep, fDisableCallback, fVisibleCallback, fRenameCallback, bSystem, bBuildUnvisible));
1076
		}
1077
	}
1078
1079
	return aResult;
1080
}
1081
1082
/**
1083
 * @param {object} element
1084
 * @returns {void}
1085
 */
1086
export function selectElement(element)
1087
{
1088
	let
1089
		sel = null,
0 ignored issues
show
Unused Code introduced by
The assignment to sel seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
1090
		range = null;
0 ignored issues
show
Unused Code introduced by
The assignment to range seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
1091
1092
	if (window.getSelection)
1093
	{
1094
		sel = window.getSelection();
1095
		sel.removeAllRanges();
1096
		range = window.document.createRange();
1097
		range.selectNodeContents(element);
1098
		sel.addRange(range);
1099
	}
1100
	else if (window.document.selection)
1101
	{
1102
		range = window.document.body.createTextRange();
1103
		range.moveToElementText(element);
1104
		range.select();
1105
	}
1106
}
1107
1108
export const detectDropdownVisibility = _.debounce(() => {
1109
	dropdownVisibility(!!_.find(GlobalsData.aBootstrapDropdowns, (item) => item.hasClass('open')));
1110
}, 50);
1111
1112
/**
1113
 * @param {boolean=} delay = false
1114
 */
1115
export function triggerAutocompleteInputChange(delay = false) {
1116
1117
	const fFunc = () => {
1118
		$('.checkAutocomplete').trigger('change');
1119
	};
1120
1121
	if (delay)
1122
	{
1123
		_.delay(fFunc, 100);
1124
	}
1125
	else
1126
	{
1127
		fFunc();
1128
	}
1129
}
1130
1131
const configurationScriptTagCache = {};
1132
1133
/**
1134
 * @param {string} configuration
1135
 * @returns {object}
1136
 */
1137
export function getConfigurationFromScriptTag(configuration)
1138
{
1139
	if (!configurationScriptTagCache[configuration])
1140
	{
1141
		configurationScriptTagCache[configuration] = $('script[type="application/json"][data-configuration="' + configuration + '"]');
1142
	}
1143
1144
	try
1145
	{
1146
		return JSON.parse(configurationScriptTagCache[configuration].text());
1147
	}
1148
	catch (e) {} // eslint-disable-line no-empty
1149
1150
	return {};
1151
}
1152
1153
/**
1154
 * @param {mixed} mPropOrValue
0 ignored issues
show
Documentation introduced by
The parameter mPropOrValue does not exist. Did you maybe forget to remove this comment?
Loading history...
1155
 * @param {mixed} value
1156
 */
1157
export function disposeOne(propOrValue, value)
1158
{
1159
	const disposable = value || propOrValue;
1160
	if (disposable && 'function' === typeof disposable.dispose)
1161
	{
1162
		disposable.dispose();
1163
	}
1164
}
1165
1166
/**
1167
 * @param {Object} object
1168
 */
1169
export function disposeObject(object)
1170
{
1171
	if (object)
1172
	{
1173
		if (isArray(object.disposables))
1174
		{
1175
			_.each(object.disposables, disposeOne);
1176
		}
1177
1178
		ko.utils.objectForEach(object, disposeOne);
1179
	}
1180
}
1181
1182
/**
1183
 * @param {Object|Array} objectOrObjects
1184
 * @returns {void}
1185
 */
1186
export function delegateRunOnDestroy(objectOrObjects)
1187
{
1188
	if (objectOrObjects)
1189
	{
1190
		if (isArray(objectOrObjects))
1191
		{
1192
			_.each(objectOrObjects, (item) => {
1193
				delegateRunOnDestroy(item);
1194
			});
1195
		}
1196
		else if (objectOrObjects && objectOrObjects.onDestroy)
1197
		{
1198
			objectOrObjects.onDestroy();
1199
		}
1200
	}
1201
}
1202
1203
/**
1204
 * @param {object} $styleTag
1205
 * @param {string} css
1206
 * @returns {boolean}
1207
 */
1208
export function appendStyles($styleTag, css)
1209
{
1210
	if ($styleTag && $styleTag[0])
1211
	{
1212
		if ($styleTag[0].styleSheet && !isUnd($styleTag[0].styleSheet.cssText))
1213
		{
1214
			$styleTag[0].styleSheet.cssText = css;
1215
		}
1216
		else
1217
		{
1218
			$styleTag.text(css);
1219
		}
1220
1221
		return true;
1222
	}
1223
1224
	return false;
1225
}
1226
1227
let
1228
	__themeTimer = 0,
1229
	__themeAjax = null;
1230
1231
/**
1232
 * @param {string} value
1233
 * @param {function=} themeTrigger = noop
1234
 * @returns {void}
1235
 */
1236
export function changeTheme(value, themeTrigger = noop)
1237
{
1238
	const
1239
		themeLink = $('#app-theme-link'),
1240
		clearTimer = () => {
1241
			__themeTimer = window.setTimeout(() => themeTrigger(SaveSettingsStep.Idle), 1000);
1242
			__themeAjax = null;
1243
		};
1244
1245
	let
1246
		themeStyle = $('#app-theme-style'),
1247
		url = themeLink.attr('href');
1248
1249
	if (!url)
1250
	{
1251
		url = themeStyle.attr('data-href');
1252
	}
1253
1254
	if (url)
1255
	{
1256
		url = url.toString().replace(/\/-\/[^\/]+\/\-\//, '/-/' + value + '/-/');
1257
		url = url.replace(/\/Css\/[^\/]+\/User\//, '/Css/0/User/');
1258
		url = url.replace(/\/Hash\/[^\/]+\//, '/Hash/-/');
1259
1260
		if ('Json/' !== url.substring(url.length - 5, url.length))
1261
		{
1262
			url += 'Json/';
1263
		}
1264
1265
		window.clearTimeout(__themeTimer);
1266
1267
		themeTrigger(SaveSettingsStep.Animate);
1268
1269
		if (__themeAjax && __themeAjax.abort)
1270
		{
1271
			__themeAjax.abort();
1272
		}
1273
1274
		__themeAjax = $.ajax({
1275
			url: url,
1276
			dataType: 'json'
1277
		}).then((data) => {
1278
1279
			if (data && isArray(data) && 2 === data.length)
1280
			{
1281
				if (themeLink && themeLink[0] && (!themeStyle || !themeStyle[0]))
1282
				{
1283
					themeStyle = $('<style id="app-theme-style"></style>');
1284
					themeLink.after(themeStyle);
1285
					themeLink.remove();
1286
				}
1287
1288
				if (themeStyle && themeStyle[0])
1289
				{
1290
					if (appendStyles(themeStyle, data[1]))
1291
					{
1292
						themeStyle.attr('data-href', url).attr('data-theme', data[0]);
1293
					}
1294
				}
1295
1296
				themeTrigger(SaveSettingsStep.TrueResult);
1297
			}
1298
1299
		}).then(clearTimer, clearTimer);
1300
	}
1301
}
1302
1303
/**
1304
 * @returns {function}
1305
 */
1306
export function computedPagenatorHelper(koCurrentPage, koPageCount)
1307
{
1308
	return () => {
1309
1310
		const
1311
			currentPage = koCurrentPage(),
1312
			pageCount = koPageCount(),
1313
			result = [],
1314
			fAdd = (index, push = true, customName = '') => {
1315
1316
				const data = {
1317
					current: index === currentPage,
1318
					name: '' === customName ? index.toString() : customName.toString(),
1319
					custom: '' !== customName,
1320
					title: '' === customName ? '' : index.toString(),
1321
					value: index.toString()
1322
				};
1323
1324
				if (push)
1325
				{
1326
					result.push(data);
1327
				}
1328
				else
1329
				{
1330
					result.unshift(data);
1331
				}
1332
			};
1333
1334
		let
1335
			prev = 0,
1336
			next = 0,
1337
			limit = 2;
1338
1339
		if (1 < pageCount || (0 < pageCount && pageCount < currentPage))
1340
		{
1341
			if (pageCount < currentPage)
1342
			{
1343
				fAdd(pageCount);
1344
				prev = pageCount;
1345
				next = pageCount;
1346
			}
1347
			else
1348
			{
1349
				if (3 >= currentPage || pageCount - 2 <= currentPage)
1350
				{
1351
					limit += 2;
1352
				}
1353
1354
				fAdd(currentPage);
1355
				prev = currentPage;
1356
				next = currentPage;
1357
			}
1358
1359
			while (0 < limit) {
1360
1361
				prev -= 1;
1362
				next += 1;
1363
1364
				if (0 < prev)
1365
				{
1366
					fAdd(prev, false);
1367
					limit -= 1;
1368
				}
1369
1370
				if (pageCount >= next)
1371
				{
1372
					fAdd(next, true);
1373
					limit -= 1;
1374
				}
1375
				else if (0 >= prev)
1376
				{
1377
					break;
1378
				}
1379
			}
1380
1381
			if (3 === prev)
1382
			{
1383
				fAdd(2, false);
1384
			}
1385
			else if (3 < prev)
1386
			{
1387
				fAdd(Math.round((prev - 1) / 2), false, '...');
1388
			}
1389
1390
			if (pageCount - 2 === next)
1391
			{
1392
				fAdd(pageCount - 1, true);
1393
			}
1394
			else if (pageCount - 2 > next)
1395
			{
1396
				fAdd(Math.round((pageCount + next) / 2), true, '...');
1397
			}
1398
1399
			// first and last
1400
			if (1 < prev)
1401
			{
1402
				fAdd(1, false);
1403
			}
1404
1405
			if (pageCount > next)
1406
			{
1407
				fAdd(pageCount, true);
1408
			}
1409
		}
1410
1411
		return result;
1412
	};
1413
}
1414
1415
/**
1416
 * @param {string} fileName
1417
 * @returns {string}
1418
 */
1419
export function getFileExtension(fileName)
1420
{
1421
	fileName = trim(fileName).toLowerCase();
1422
1423
	const result = fileName.split('.').pop();
1424
	return result === fileName ? '' : result;
1425
}
1426
1427
/**
1428
 * @param {string} fileName
1429
 * @returns {string}
1430
 */
1431
export function mimeContentType(fileName)
1432
{
1433
	let
1434
		ext = '',
1435
		result = 'application/octet-stream';
1436
1437
	fileName = trim(fileName).toLowerCase();
1438
1439
	if ('winmail.dat' === fileName)
1440
	{
1441
		return 'application/ms-tnef';
1442
	}
1443
1444
	ext = getFileExtension(fileName);
1445
	if (ext && 0 < ext.length && !isUnd(Mime[ext]))
1446
	{
1447
		result = Mime[ext];
1448
	}
1449
1450
	return result;
1451
}
1452
1453
/**
1454
 * @param {Object} $el
1455
 * @returns {number}
1456
 */
1457
export function getRealHeight($el)
1458
{
1459
	$el.clone().show().appendTo($hcont);
1460
	const result = $hcont.height();
1461
	$hcont.empty();
1462
	return result;
1463
}
1464
1465
/**
1466
 * @param {string} url
1467
 * @param {number} value
1468
 * @param {Function} fCallback
1469
 */
1470
export function resizeAndCrop(url, value, fCallback)
1471
{
1472
	const img = new window.Image();
1473
	img.onload = function() {
1474
1475
		let
1476
			diff = [0, 0];
0 ignored issues
show
Unused Code introduced by
The assignment to variable diff seems to be never used. Consider removing it.
Loading history...
1477
1478
		const
1479
			canvas = window.document.createElement('canvas'),
1480
			ctx = canvas.getContext('2d');
1481
1482
		canvas.width = value;
1483
		canvas.height = value;
1484
1485
		if (this.width > this.height)
1486
		{
1487
			diff = [this.width - this.height, 0];
1488
		}
1489
		else
1490
		{
1491
			diff = [0, this.height - this.width];
1492
		}
1493
1494
		ctx.fillStyle = '#fff';
1495
		ctx.fillRect(0, 0, value, value);
1496
		ctx.drawImage(this, diff[0] / 2, diff[1] / 2, this.width - diff[0], this.height - diff[1], 0, 0, value, value);
1497
1498
		fCallback(canvas.toDataURL('image/jpeg'));
1499
	};
1500
1501
	img.src = url;
1502
}
1503
1504
/**
1505
 * @param {string} mailToUrl
1506
 * @param {Function} PopupComposeVoreModel
1507
 * @returns {boolean}
1508
 */
1509
export function mailToHelper(mailToUrl, PopupComposeVoreModel)
1510
{
1511
	if (mailToUrl && 'mailto:' === mailToUrl.toString().substr(0, 7).toLowerCase())
1512
	{
1513
		if (!PopupComposeVoreModel)
1514
		{
1515
			return true;
1516
		}
1517
1518
		mailToUrl = mailToUrl.toString().substr(7);
1519
1520
		let
1521
			to = [],
0 ignored issues
show
Unused Code introduced by
The assignment to variable to seems to be never used. Consider removing it.
Loading history...
1522
			cc = null,
1523
			bcc = null,
1524
			params = {};
0 ignored issues
show
Unused Code introduced by
The assignment to variable params seems to be never used. Consider removing it.
Loading history...
1525
1526
		const
1527
			email = mailToUrl.replace(/\?.+$/, ''),
1528
			query = mailToUrl.replace(/^[^\?]*\?/, ''),
1529
			EmailModel = require('Model/Email').default,
1530
			emailObj = new EmailModel(),
1531
			fParseEmailLine = (line) => (line ? _.compact(_.map(decodeURIComponent(line).split(/[,]/), (item) => {
1532
				emailObj.clear();
1533
				emailObj.mailsoParse(item);
1534
				return '' !== emailObj.email ? emailObj : null;
1535
			})) : null);
1536
1537
		to = fParseEmailLine(email);
1538
		params = simpleQueryParser(query);
1539
1540
		if (!isUnd(params.cc))
1541
		{
1542
			cc = fParseEmailLine(decodeURIComponent(params.cc));
1543
		}
1544
1545
		if (!isUnd(params.bcc))
1546
		{
1547
			bcc = fParseEmailLine(decodeURIComponent(params.bcc));
1548
		}
1549
1550
		require('Knoin/Knoin').showScreenPopup(PopupComposeVoreModel, [
1551
			ComposeType.Empty, null, to, cc, bcc,
1552
			isUnd(params.subject) ? null : pString(decodeURIComponent(params.subject)),
1553
			isUnd(params.body) ? null : plainToHtml(pString(decodeURIComponent(params.body)))
1554
		]);
1555
1556
		return true;
1557
	}
1558
1559
	return false;
1560
}
1561
1562
/**
1563
 * @param {Function} fn
1564
 * @returns {void}
1565
 */
1566
export function domReady(fn)
1567
{
1568
	$(() => fn());
1569
//
1570
//	if ('loading' !== window.document.readyState)
1571
//	{
1572
//		fn();
1573
//	}
1574
//	else
1575
//	{
1576
//		window.document.addEventListener('DOMContentLoaded', fn);
1577
//	}
1578
}
1579
1580
export const windowResize = _.debounce((timeout) => {
1581
	if (isUnd(timeout) || isNull(timeout))
1582
	{
1583
		$win.resize();
1584
	}
1585
	else
1586
	{
1587
		window.setTimeout(() => {
1588
			$win.resize();
1589
		}, timeout);
1590
	}
1591
}, 50);
1592
1593
/**
1594
 * @returns {void}
1595
 */
1596
export function windowResizeCallback()
1597
{
1598
	windowResize();
1599
}
1600
1601
let substr = window.String.substr;
1602
if ('b' !== 'ab'.substr(-1))
1603
{
1604
	substr = (str, start, length) => {
1605
		start = 0 > start ? str.length + start : start;
1606
		return str.substr(start, length);
1607
	};
1608
1609
	window.String.substr = substr;
1610
}
1611