Completed
Push — master ( 5e340b...6daeb2 )
by Rain
08:27
created

Utils.js ➔ isTransparent   A

Complexity

Conditions 1
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 2
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
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, ccClass, ccCreds, ccLabel}, 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('{{ccClass}}', encodeHtml(ccClass))
640
		.replace('{{ccCreds}}', encodeHtml(ccCreds))
641
		.replace('{{ccLabel}}', encodeHtml(ccLabel))
642
		.replace('{{bodyClass}}', bodyClass)
643
		.replace('{{html}}', html)
644
	);
645
646
	doc.close();
647
648
	if (print)
649
	{
650
		window.setTimeout(() => win.print(), 100);
651
	}
652
}
653
654
/**
655
 * @param {Function} fCallback
656
 * @param {?} koTrigger
657
 * @param {?} context = null
658
 * @param {number=} timer = 1000
659
 * @returns {Function}
660
 */
661
export function settingsSaveHelperFunction(fCallback, koTrigger, context = null, timer = 1000)
662
{
663
	timer = pInt(timer);
664
	return (type, data, cached, requestAction, requestParameters) => {
665
		koTrigger.call(context, data && data.Result ? SaveSettingsStep.TrueResult : SaveSettingsStep.FalseResult);
666
		if (fCallback)
667
		{
668
			fCallback.call(context, type, data, cached, requestAction, requestParameters);
669
		}
670
		_.delay(() => {
671
			koTrigger.call(context, SaveSettingsStep.Idle);
672
		}, timer);
673
	};
674
}
675
676
/**
677
 * @param {object} koTrigger
678
 * @param {mixed} context
679
 * @returns {mixed}
680
 */
681
export function settingsSaveHelperSimpleFunction(koTrigger, context)
682
{
683
	return settingsSaveHelperFunction(null, koTrigger, context, 1000);
684
}
685
686
/**
687
 * @param {object} remote
688
 * @param {string} settingName
689
 * @param {string} type
690
 * @param {function} fTriggerFunction
691
 * @returns {function}
692
 */
693
export function settingsSaveHelperSubscribeFunction(remote, settingName, type, fTriggerFunction)
694
{
695
	return (value) => {
696
697
		if (remote)
698
		{
699
			switch (type)
700
			{
701
				case 'bool':
702
				case 'boolean':
703
					value = value ? '1' : '0';
704
					break;
705
				case 'int':
706
				case 'integer':
707
				case 'number':
708
					value = pInt(value);
709
					break;
710
				case 'trim':
711
					value = trim(value);
712
					break;
713
				default:
714
					value = pString(value);
715
					break;
716
			}
717
718
			const data = {};
719
			data[settingName] = value;
720
721
			if (remote.saveAdminConfig)
722
			{
723
				remote.saveAdminConfig(fTriggerFunction || null, data);
724
			}
725
			else if (remote.saveSettings)
726
			{
727
				remote.saveSettings(fTriggerFunction || null, data);
728
			}
729
		}
730
	};
731
}
732
733
/**
734
 * @param {string} html
735
 * @returns {string}
736
 */
737
export function findEmailAndLinks(html)
738
{
739
//	return html;
740
	return Autolinker ? Autolinker.link(html, {
741
		newWindow: true,
742
		stripPrefix: false,
743
		urls: true,
744
		email: true,
745
		mention: false,
746
		phone: false,
747
		hashtag: false,
748
		replaceFn: function(match) {
749
			return !(match && 'url' === match.getType() && match.matchedText && 0 !== match.matchedText.indexOf('http'));
750
		}
751
	}) : html;
752
}
753
754
/**
755
 * @param {string} html
756
 * @returns {string}
757
 */
758
export function htmlToPlain(html)
759
{
760
	let
761
		pos = 0,
762
		limit = 0,
763
		iP1 = 0,
764
		iP2 = 0,
765
		iP3 = 0,
766
767
		text = '';
768
769
	const convertBlockquote = (blockquoteText) => {
770
		blockquoteText = '> ' + trim(blockquoteText).replace(/\n/gm, '\n> ');
771
		return blockquoteText.replace(/(^|\n)([> ]+)/gm,
772
			(...args) => (args && 2 < args.length ? args[1] + trim(args[2].replace(/[\s]/g, '')) + ' ' : ''));
773
	};
774
775
	const convertDivs = (...args) => {
776
		if (args && 1 < args.length)
777
		{
778
			let divText = trim(args[1]);
779
			if (0 < divText.length)
780
			{
781
				divText = divText.replace(/<div[^>]*>([\s\S\r\n]*)<\/div>/gmi, convertDivs);
782
				divText = '\n' + trim(divText) + '\n';
783
			}
784
785
			return divText;
786
		}
787
788
		return '';
789
	};
790
791
	const
792
		convertPre = (...args) => (args && 1 < args.length ? args[1].toString().replace(/[\n]/gm, '<br />').replace(/[\r]/gm, '') : ''),
793
		fixAttibuteValue = (...args) => (args && 1 < args.length ? '' + args[1] + _.escape(args[2]) : ''),
794
		convertLinks = (...args) => (args && 1 < args.length ? trim(args[1]) : '');
795
796
	text = html
797
		.replace(/<p[^>]*><\/p>/gi, '')
798
		.replace(/<pre[^>]*>([\s\S\r\n\t]*)<\/pre>/gmi, convertPre)
799
		.replace(/[\s]+/gm, ' ')
800
		.replace(/((?:href|data)\s?=\s?)("[^"]+?"|'[^']+?')/gmi, fixAttibuteValue)
801
		.replace(/<br[^>]*>/gmi, '\n')
802
		.replace(/<\/h[\d]>/gi, '\n')
803
		.replace(/<\/p>/gi, '\n\n')
804
		.replace(/<ul[^>]*>/gmi, '\n')
805
		.replace(/<\/ul>/gi, '\n')
806
		.replace(/<li[^>]*>/gmi, ' * ')
807
		.replace(/<\/li>/gi, '\n')
808
		.replace(/<\/td>/gi, '\n')
809
		.replace(/<\/tr>/gi, '\n')
810
		.replace(/<hr[^>]*>/gmi, '\n_______________________________\n\n')
811
		.replace(/<div[^>]*>([\s\S\r\n]*)<\/div>/gmi, convertDivs)
812
		.replace(/<blockquote[^>]*>/gmi, '\n__bq__start__\n')
813
		.replace(/<\/blockquote>/gmi, '\n__bq__end__\n')
814
		.replace(/<a [^>]*>([\s\S\r\n]*?)<\/a>/gmi, convertLinks)
815
		.replace(/<\/div>/gi, '\n')
816
		.replace(/&nbsp;/gi, ' ')
817
		.replace(/&quot;/gi, '"')
818
		.replace(/<[^>]*>/gm, '');
819
820
	text = $div.html(text).text();
821
822
	text = text
823
		.replace(/\n[ \t]+/gm, '\n')
824
		.replace(/[\n]{3,}/gm, '\n\n')
825
		.replace(/&gt;/gi, '>')
826
		.replace(/&lt;/gi, '<')
827
		.replace(/&amp;/gi, '&');
828
829
	text = splitPlainText(text);
830
831
	pos = 0;
832
	limit = 800;
833
834
	while (0 < limit)
835
	{
836
		limit -= 1;
837
		iP1 = text.indexOf('__bq__start__', pos);
838
		if (-1 < iP1)
839
		{
840
			iP2 = text.indexOf('__bq__start__', iP1 + 5);
841
			iP3 = text.indexOf('__bq__end__', iP1 + 5);
842
843
			if ((-1 === iP2 || iP3 < iP2) && iP1 < iP3)
844
			{
845
				text = text.substring(0, iP1) +
846
					convertBlockquote(text.substring(iP1 + 13, iP3)) +
847
					text.substring(iP3 + 11);
848
849
				pos = 0;
850
			}
851
			else if (-1 < iP2 && iP2 < iP3)
852
			{
853
				pos = iP2 - 1;
854
			}
855
			else
856
			{
857
				pos = 0;
858
			}
859
		}
860
		else
861
		{
862
			break;
863
		}
864
	}
865
866
	text = text
867
		.replace(/__bq__start__/gm, '')
868
		.replace(/__bq__end__/gm, '');
869
870
	return text;
871
}
872
873
/**
874
 * @param {string} plain
875
 * @param {boolean} findEmailAndLinksInText = false
876
 * @returns {string}
877
 */
878
export function plainToHtml(plain, findEmailAndLinksInText = false)
879
{
880
	plain = plain.toString().replace(/\r/g, '');
881
	plain = plain.replace(/^>[> ]>+/gm, ([match]) => (match ? match.replace(/[ ]+/g, '') : match));
882
883
	let
884
		bIn = false,
885
		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...
886
		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...
887
		aNextText = [],
0 ignored issues
show
Unused Code introduced by
The assignment to variable aNextText seems to be never used. Consider removing it.
Loading history...
888
		sLine = '',
889
		iIndex = 0,
890
		aText = plain.split('\n');
891
892
	do
893
	{
894
		bDo = false;
895
		aNextText = [];
896
		for (iIndex = 0; iIndex < aText.length; iIndex++)
897
		{
898
			sLine = aText[iIndex];
899
			bStart = '>' === sLine.substr(0, 1);
900
			if (bStart && !bIn)
901
			{
902
				bDo = true;
903
				bIn = true;
904
				aNextText.push('~~~blockquote~~~');
905
				aNextText.push(sLine.substr(1));
906
			}
907
			else if (!bStart && bIn)
908
			{
909
				if ('' !== sLine)
910
				{
911
					bIn = false;
912
					aNextText.push('~~~/blockquote~~~');
913
					aNextText.push(sLine);
914
				}
915
				else
916
				{
917
					aNextText.push(sLine);
918
				}
919
			}
920
			else if (bStart && bIn)
921
			{
922
				aNextText.push(sLine.substr(1));
923
			}
924
			else
925
			{
926
				aNextText.push(sLine);
927
			}
928
		}
929
930
		if (bIn)
931
		{
932
			bIn = false;
933
			aNextText.push('~~~/blockquote~~~');
934
		}
935
936
		aText = aNextText;
937
	}
938
	while (bDo);
939
940
	plain = aText.join('\n');
941
942
	plain = plain
943
		// .replace(/~~~\/blockquote~~~\n~~~blockquote~~~/g, '\n')
944
		.replace(/&/g, '&amp;')
945
		.replace(/>/g, '&gt;').replace(/</g, '&lt;')
946
		.replace(/~~~blockquote~~~[\s]*/g, '<blockquote>')
947
		.replace(/[\s]*~~~\/blockquote~~~/g, '</blockquote>')
948
		.replace(/\n/g, '<br />');
949
950
	return findEmailAndLinksInText ? findEmailAndLinks(plain) : plain;
951
}
952
953
window['rainloop_Utils_htmlToPlain'] = htmlToPlain; // eslint-disable-line dot-notation
954
window['rainloop_Utils_plainToHtml'] = plainToHtml; // eslint-disable-line dot-notation
955
956
/**
957
 * @param {Array} aSystem
958
 * @param {Array} aList
959
 * @param {Array=} aDisabled
960
 * @param {Array=} aHeaderLines
961
 * @param {?number=} iUnDeep
962
 * @param {Function=} fDisableCallback
963
 * @param {Function=} fVisibleCallback
964
 * @param {Function=} fRenameCallback
965
 * @param {boolean=} bSystem
966
 * @param {boolean=} bBuildUnvisible
967
 * @returns {Array}
968
 */
969
export function folderListOptionsBuilder(aSystem, aList, aDisabled, aHeaderLines,
970
	iUnDeep, fDisableCallback, fVisibleCallback, fRenameCallback, bSystem, bBuildUnvisible)
971
{
972
	let
973
		/**
974
		 * @type {?FolderModel}
975
		 */
976
		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...
977
		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...
978
		iIndex = 0,
979
		iLen = 0,
980
		aResult = [];
981
982
	const sDeepPrefix = '\u00A0\u00A0\u00A0';
983
984
	bBuildUnvisible = isUnd(bBuildUnvisible) ? false : !!bBuildUnvisible;
985
	bSystem = !isNormal(bSystem) ? 0 < aSystem.length : bSystem;
986
	iUnDeep = !isNormal(iUnDeep) ? 0 : iUnDeep;
987
	fDisableCallback = isNormal(fDisableCallback) ? fDisableCallback : null;
988
	fVisibleCallback = isNormal(fVisibleCallback) ? fVisibleCallback : null;
989
	fRenameCallback = isNormal(fRenameCallback) ? fRenameCallback : null;
990
991
	if (!isArray(aDisabled))
992
	{
993
		aDisabled = [];
994
	}
995
996
	if (!isArray(aHeaderLines))
997
	{
998
		aHeaderLines = [];
999
	}
1000
1001
	for (iIndex = 0, iLen = aHeaderLines.length; iIndex < iLen; iIndex++)
1002
	{
1003
		aResult.push({
1004
			id: aHeaderLines[iIndex][0],
1005
			name: aHeaderLines[iIndex][1],
1006
			system: false,
1007
			seporator: false,
1008
			disabled: false
1009
		});
1010
	}
1011
1012
	bSep = true;
1013
	for (iIndex = 0, iLen = aSystem.length; iIndex < iLen; iIndex++)
1014
	{
1015
		oItem = aSystem[iIndex];
1016
		if (fVisibleCallback ? fVisibleCallback(oItem) : true)
1017
		{
1018
			if (bSep && 0 < aResult.length)
1019
			{
1020
				aResult.push({
1021
					id: '---',
1022
					name: '---',
1023
					system: false,
1024
					seporator: true,
1025
					disabled: true
1026
				});
1027
			}
1028
1029
			bSep = false;
1030
			aResult.push({
1031
				id: oItem.fullNameRaw,
1032
				name: fRenameCallback ? fRenameCallback(oItem) : oItem.name(),
1033
				system: true,
1034
				seporator: false,
1035
				disabled: !oItem.selectable || -1 < inArray(oItem.fullNameRaw, aDisabled) ||
1036
					(fDisableCallback ? fDisableCallback(oItem) : false)
1037
			});
1038
		}
1039
	}
1040
1041
	bSep = true;
1042
	for (iIndex = 0, iLen = aList.length; iIndex < iLen; iIndex++)
1043
	{
1044
		oItem = aList[iIndex];
1045
		// if (oItem.subScribed() || !oItem.existen || bBuildUnvisible)
1046
		if ((oItem.subScribed() || !oItem.existen || bBuildUnvisible) && (oItem.selectable || oItem.hasSubScribedSubfolders()))
1047
		{
1048
			if (fVisibleCallback ? fVisibleCallback(oItem) : true)
1049
			{
1050
				if (FolderType.User === oItem.type() || !bSystem || oItem.hasSubScribedSubfolders())
1051
				{
1052
					if (bSep && 0 < aResult.length)
1053
					{
1054
						aResult.push({
1055
							id: '---',
1056
							name: '---',
1057
							system: false,
1058
							seporator: true,
1059
							disabled: true
1060
						});
1061
					}
1062
1063
					bSep = false;
1064
					aResult.push({
1065
						id: oItem.fullNameRaw,
1066
						name: (new window.Array(oItem.deep + 1 - iUnDeep)).join(sDeepPrefix) +
1067
							(fRenameCallback ? fRenameCallback(oItem) : oItem.name()),
1068
						system: false,
1069
						seporator: false,
1070
						disabled: !oItem.selectable || -1 < inArray(oItem.fullNameRaw, aDisabled) ||
1071
							(fDisableCallback ? fDisableCallback(oItem) : false)
1072
					});
1073
				}
1074
			}
1075
		}
1076
1077
		if (oItem.subScribed() && 0 < oItem.subFolders().length)
1078
		{
1079
			aResult = aResult.concat(folderListOptionsBuilder([], oItem.subFolders(), aDisabled, [],
1080
				iUnDeep, fDisableCallback, fVisibleCallback, fRenameCallback, bSystem, bBuildUnvisible));
1081
		}
1082
	}
1083
1084
	return aResult;
1085
}
1086
1087
/**
1088
 * @param {object} element
1089
 * @returns {void}
1090
 */
1091
export function selectElement(element)
1092
{
1093
	let
1094
		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...
1095
		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...
1096
1097
	if (window.getSelection)
1098
	{
1099
		sel = window.getSelection();
1100
		sel.removeAllRanges();
1101
		range = window.document.createRange();
1102
		range.selectNodeContents(element);
1103
		sel.addRange(range);
1104
	}
1105
	else if (window.document.selection)
1106
	{
1107
		range = window.document.body.createTextRange();
1108
		range.moveToElementText(element);
1109
		range.select();
1110
	}
1111
}
1112
1113
export const detectDropdownVisibility = _.debounce(() => {
1114
	dropdownVisibility(!!_.find(GlobalsData.aBootstrapDropdowns, (item) => item.hasClass('open')));
1115
}, 50);
1116
1117
/**
1118
 * @param {boolean=} delay = false
1119
 */
1120
export function triggerAutocompleteInputChange(delay = false) {
1121
1122
	const fFunc = () => {
1123
		$('.checkAutocomplete').trigger('change');
1124
	};
1125
1126
	if (delay)
1127
	{
1128
		_.delay(fFunc, 100);
1129
	}
1130
	else
1131
	{
1132
		fFunc();
1133
	}
1134
}
1135
1136
const configurationScriptTagCache = {};
1137
1138
/**
1139
 * @param {string} configuration
1140
 * @returns {object}
1141
 */
1142
export function getConfigurationFromScriptTag(configuration)
1143
{
1144
	if (!configurationScriptTagCache[configuration])
1145
	{
1146
		configurationScriptTagCache[configuration] = $('script[type="application/json"][data-configuration="' + configuration + '"]');
1147
	}
1148
1149
	try
1150
	{
1151
		return JSON.parse(configurationScriptTagCache[configuration].text());
1152
	}
1153
	catch (e) {} // eslint-disable-line no-empty
1154
1155
	return {};
1156
}
1157
1158
/**
1159
 * @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...
1160
 * @param {mixed} value
1161
 */
1162
export function disposeOne(propOrValue, value)
1163
{
1164
	const disposable = value || propOrValue;
1165
	if (disposable && 'function' === typeof disposable.dispose)
1166
	{
1167
		disposable.dispose();
1168
	}
1169
}
1170
1171
/**
1172
 * @param {Object} object
1173
 */
1174
export function disposeObject(object)
1175
{
1176
	if (object)
1177
	{
1178
		if (isArray(object.disposables))
1179
		{
1180
			_.each(object.disposables, disposeOne);
1181
		}
1182
1183
		ko.utils.objectForEach(object, disposeOne);
1184
	}
1185
}
1186
1187
/**
1188
 * @param {Object|Array} objectOrObjects
1189
 * @returns {void}
1190
 */
1191
export function delegateRunOnDestroy(objectOrObjects)
1192
{
1193
	if (objectOrObjects)
1194
	{
1195
		if (isArray(objectOrObjects))
1196
		{
1197
			_.each(objectOrObjects, (item) => {
1198
				delegateRunOnDestroy(item);
1199
			});
1200
		}
1201
		else if (objectOrObjects && objectOrObjects.onDestroy)
1202
		{
1203
			objectOrObjects.onDestroy();
1204
		}
1205
	}
1206
}
1207
1208
/**
1209
 * @param {object} $styleTag
1210
 * @param {string} css
1211
 * @returns {boolean}
1212
 */
1213
export function appendStyles($styleTag, css)
1214
{
1215
	if ($styleTag && $styleTag[0])
1216
	{
1217
		if ($styleTag[0].styleSheet && !isUnd($styleTag[0].styleSheet.cssText))
1218
		{
1219
			$styleTag[0].styleSheet.cssText = css;
1220
		}
1221
		else
1222
		{
1223
			$styleTag.text(css);
1224
		}
1225
1226
		return true;
1227
	}
1228
1229
	return false;
1230
}
1231
1232
let
1233
	__themeTimer = 0,
1234
	__themeAjax = null;
1235
1236
/**
1237
 * @param {string} value
1238
 * @param {function=} themeTrigger = noop
1239
 * @returns {void}
1240
 */
1241
export function changeTheme(value, themeTrigger = noop)
1242
{
1243
	const
1244
		themeLink = $('#app-theme-link'),
1245
		clearTimer = () => {
1246
			__themeTimer = window.setTimeout(() => themeTrigger(SaveSettingsStep.Idle), 1000);
1247
			__themeAjax = null;
1248
		};
1249
1250
	let
1251
		themeStyle = $('#app-theme-style'),
1252
		url = themeLink.attr('href');
1253
1254
	if (!url)
1255
	{
1256
		url = themeStyle.attr('data-href');
1257
	}
1258
1259
	if (url)
1260
	{
1261
		url = url.toString().replace(/\/-\/[^\/]+\/\-\//, '/-/' + value + '/-/');
1262
		url = url.replace(/\/Css\/[^\/]+\/User\//, '/Css/0/User/');
1263
		url = url.replace(/\/Hash\/[^\/]+\//, '/Hash/-/');
1264
1265
		if ('Json/' !== url.substring(url.length - 5, url.length))
1266
		{
1267
			url += 'Json/';
1268
		}
1269
1270
		window.clearTimeout(__themeTimer);
1271
1272
		themeTrigger(SaveSettingsStep.Animate);
1273
1274
		if (__themeAjax && __themeAjax.abort)
1275
		{
1276
			__themeAjax.abort();
1277
		}
1278
1279
		__themeAjax = $.ajax({
1280
			url: url,
1281
			dataType: 'json'
1282
		}).then((data) => {
1283
1284
			if (data && isArray(data) && 2 === data.length)
1285
			{
1286
				if (themeLink && themeLink[0] && (!themeStyle || !themeStyle[0]))
1287
				{
1288
					themeStyle = $('<style id="app-theme-style"></style>');
1289
					themeLink.after(themeStyle);
1290
					themeLink.remove();
1291
				}
1292
1293
				if (themeStyle && themeStyle[0])
1294
				{
1295
					if (appendStyles(themeStyle, data[1]))
1296
					{
1297
						themeStyle.attr('data-href', url).attr('data-theme', data[0]);
1298
					}
1299
				}
1300
1301
				themeTrigger(SaveSettingsStep.TrueResult);
1302
			}
1303
1304
		}).then(clearTimer, clearTimer);
1305
	}
1306
}
1307
1308
/**
1309
 * @returns {function}
1310
 */
1311
export function computedPagenatorHelper(koCurrentPage, koPageCount)
1312
{
1313
	return () => {
1314
1315
		const
1316
			currentPage = koCurrentPage(),
1317
			pageCount = koPageCount(),
1318
			result = [],
1319
			fAdd = (index, push = true, customName = '') => {
1320
1321
				const data = {
1322
					current: index === currentPage,
1323
					name: '' === customName ? index.toString() : customName.toString(),
1324
					custom: '' !== customName,
1325
					title: '' === customName ? '' : index.toString(),
1326
					value: index.toString()
1327
				};
1328
1329
				if (push)
1330
				{
1331
					result.push(data);
1332
				}
1333
				else
1334
				{
1335
					result.unshift(data);
1336
				}
1337
			};
1338
1339
		let
1340
			prev = 0,
1341
			next = 0,
1342
			limit = 2;
1343
1344
		if (1 < pageCount || (0 < pageCount && pageCount < currentPage))
1345
		{
1346
			if (pageCount < currentPage)
1347
			{
1348
				fAdd(pageCount);
1349
				prev = pageCount;
1350
				next = pageCount;
1351
			}
1352
			else
1353
			{
1354
				if (3 >= currentPage || pageCount - 2 <= currentPage)
1355
				{
1356
					limit += 2;
1357
				}
1358
1359
				fAdd(currentPage);
1360
				prev = currentPage;
1361
				next = currentPage;
1362
			}
1363
1364
			while (0 < limit) {
1365
1366
				prev -= 1;
1367
				next += 1;
1368
1369
				if (0 < prev)
1370
				{
1371
					fAdd(prev, false);
1372
					limit -= 1;
1373
				}
1374
1375
				if (pageCount >= next)
1376
				{
1377
					fAdd(next, true);
1378
					limit -= 1;
1379
				}
1380
				else if (0 >= prev)
1381
				{
1382
					break;
1383
				}
1384
			}
1385
1386
			if (3 === prev)
1387
			{
1388
				fAdd(2, false);
1389
			}
1390
			else if (3 < prev)
1391
			{
1392
				fAdd(Math.round((prev - 1) / 2), false, '...');
1393
			}
1394
1395
			if (pageCount - 2 === next)
1396
			{
1397
				fAdd(pageCount - 1, true);
1398
			}
1399
			else if (pageCount - 2 > next)
1400
			{
1401
				fAdd(Math.round((pageCount + next) / 2), true, '...');
1402
			}
1403
1404
			// first and last
1405
			if (1 < prev)
1406
			{
1407
				fAdd(1, false);
1408
			}
1409
1410
			if (pageCount > next)
1411
			{
1412
				fAdd(pageCount, true);
1413
			}
1414
		}
1415
1416
		return result;
1417
	};
1418
}
1419
1420
/**
1421
 * @param {string} fileName
1422
 * @returns {string}
1423
 */
1424
export function getFileExtension(fileName)
1425
{
1426
	fileName = trim(fileName).toLowerCase();
1427
1428
	const result = fileName.split('.').pop();
1429
	return result === fileName ? '' : result;
1430
}
1431
1432
/**
1433
 * @param {string} fileName
1434
 * @returns {string}
1435
 */
1436
export function mimeContentType(fileName)
1437
{
1438
	let
1439
		ext = '',
1440
		result = 'application/octet-stream';
1441
1442
	fileName = trim(fileName).toLowerCase();
1443
1444
	if ('winmail.dat' === fileName)
1445
	{
1446
		return 'application/ms-tnef';
1447
	}
1448
1449
	ext = getFileExtension(fileName);
1450
	if (ext && 0 < ext.length && !isUnd(Mime[ext]))
1451
	{
1452
		result = Mime[ext];
1453
	}
1454
1455
	return result;
1456
}
1457
1458
/**
1459
 * @param {string} color
1460
 * @returns {boolean}
1461
 */
1462
export function isTransparent(color)
1463
{
1464
	return 'rgba(0, 0, 0, 0)' === color || 'transparent' === color;
1465
}
1466
1467
/**
1468
 * @param {Object} $el
1469
 * @returns {number}
1470
 */
1471
export function getRealHeight($el)
1472
{
1473
	$el.clone().show().appendTo($hcont);
1474
	const result = $hcont.height();
1475
	$hcont.empty();
1476
	return result;
1477
}
1478
1479
/**
1480
 * @param {string} url
1481
 * @param {number} value
1482
 * @param {Function} fCallback
1483
 */
1484
export function resizeAndCrop(url, value, fCallback)
1485
{
1486
	const img = new window.Image();
1487
	img.onload = function() {
1488
1489
		let
1490
			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...
1491
1492
		const
1493
			canvas = window.document.createElement('canvas'),
1494
			ctx = canvas.getContext('2d');
1495
1496
		canvas.width = value;
1497
		canvas.height = value;
1498
1499
		if (this.width > this.height)
1500
		{
1501
			diff = [this.width - this.height, 0];
1502
		}
1503
		else
1504
		{
1505
			diff = [0, this.height - this.width];
1506
		}
1507
1508
		ctx.fillStyle = '#fff';
1509
		ctx.fillRect(0, 0, value, value);
1510
		ctx.drawImage(this, diff[0] / 2, diff[1] / 2, this.width - diff[0], this.height - diff[1], 0, 0, value, value);
1511
1512
		fCallback(canvas.toDataURL('image/jpeg'));
1513
	};
1514
1515
	img.src = url;
1516
}
1517
1518
/**
1519
 * @param {string} mailToUrl
1520
 * @param {Function} PopupComposeVoreModel
1521
 * @returns {boolean}
1522
 */
1523
export function mailToHelper(mailToUrl, PopupComposeVoreModel)
1524
{
1525
	if (mailToUrl && 'mailto:' === mailToUrl.toString().substr(0, 7).toLowerCase())
1526
	{
1527
		if (!PopupComposeVoreModel)
1528
		{
1529
			return true;
1530
		}
1531
1532
		mailToUrl = mailToUrl.toString().substr(7);
1533
1534
		let
1535
			to = [],
0 ignored issues
show
Unused Code introduced by
The assignment to variable to seems to be never used. Consider removing it.
Loading history...
1536
			cc = null,
1537
			bcc = null,
1538
			params = {};
0 ignored issues
show
Unused Code introduced by
The assignment to variable params seems to be never used. Consider removing it.
Loading history...
1539
1540
		const
1541
			email = mailToUrl.replace(/\?.+$/, ''),
1542
			query = mailToUrl.replace(/^[^\?]*\?/, ''),
1543
			EmailModel = require('Model/Email').default,
1544
			emailObj = new EmailModel(),
1545
			fParseEmailLine = (line) => (line ? _.compact(_.map(decodeURIComponent(line).split(/[,]/), (item) => {
1546
				emailObj.clear();
1547
				emailObj.mailsoParse(item);
1548
				return '' !== emailObj.email ? emailObj : null;
1549
			})) : null);
1550
1551
		to = fParseEmailLine(email);
1552
		params = simpleQueryParser(query);
1553
1554
		if (!isUnd(params.cc))
1555
		{
1556
			cc = fParseEmailLine(decodeURIComponent(params.cc));
1557
		}
1558
1559
		if (!isUnd(params.bcc))
1560
		{
1561
			bcc = fParseEmailLine(decodeURIComponent(params.bcc));
1562
		}
1563
1564
		require('Knoin/Knoin').showScreenPopup(PopupComposeVoreModel, [
1565
			ComposeType.Empty, null, to, cc, bcc,
1566
			isUnd(params.subject) ? null : pString(decodeURIComponent(params.subject)),
1567
			isUnd(params.body) ? null : plainToHtml(pString(decodeURIComponent(params.body)))
1568
		]);
1569
1570
		return true;
1571
	}
1572
1573
	return false;
1574
}
1575
1576
/**
1577
 * @param {Function} fn
1578
 * @returns {void}
1579
 */
1580
export function domReady(fn)
1581
{
1582
	$(() => fn());
1583
//
1584
//	if ('loading' !== window.document.readyState)
1585
//	{
1586
//		fn();
1587
//	}
1588
//	else
1589
//	{
1590
//		window.document.addEventListener('DOMContentLoaded', fn);
1591
//	}
1592
}
1593
1594
export const windowResize = _.debounce((timeout) => {
1595
	if (isUnd(timeout) || isNull(timeout))
1596
	{
1597
		$win.resize();
1598
	}
1599
	else
1600
	{
1601
		window.setTimeout(() => {
1602
			$win.resize();
1603
		}, timeout);
1604
	}
1605
}, 50);
1606
1607
/**
1608
 * @returns {void}
1609
 */
1610
export function windowResizeCallback()
1611
{
1612
	windowResize();
1613
}
1614
1615
let substr = window.String.substr;
1616
if ('b' !== 'ab'.substr(-1))
1617
{
1618
	substr = (str, start, length) => {
1619
		start = 0 > start ? str.length + start : start;
1620
		return str.substr(start, length);
1621
	};
1622
1623
	window.String.substr = substr;
1624
}
1625