PageSourceSearch

https://ioctopus.online/wp-content/plugins/wordpressmvc/js/core.all.js?ver=1716768620

js ioctopus.online collected 2026-10-04 22:21:55 UTC 90,393 bytes, 3,373 lines download raw bytes

1var wdproData = typeof wdproData !== 'undefined' ? wdproData : null;
2
3// Основной обеъект плагина, в который добавляются дргуие штуки
4var wdpro = {
5	templates: {},
6	data: wdproData,
7	speed: 200, // Скорость эффектов (там, там, где используется)
8
9	// Константы
10	WDPRO_TEMPLATE_URL: '',
11	WDPRO_UPLOAD_IMAGES_URL: '',
12	WDPRO_HOME_URL: '',
13	WDPRO_UPLOAD_URL: '',
14
15	// Номера клавиш
16	KEY_ESCAPE: 27,
17	KEY_ENTER: 13,
18	KEY_LEFT: 37,
19	KEY_UP: 38,
20	KEY_RIGHT: 39,
21	KEY_DOWN: 40,
22
23	// base64_decode
24	base64_decode: function (str) {
25		return decodeURIComponent(escape(window.atob( str )));
26	}
27};
28
29
30// Метод forEach для массивов
31if (!Array.prototype.forEach)
32{
33	Array.prototype.forEach = function(fun /*, thisp */)
34	{
35		"use strict";
36
37		if (this === void 0 || this === null)
38			throw new TypeError();
39
40
41		var t = Object(this);
42		var len = t.length >>> 0;
43		if (typeof fun !== "function")
44			throw new TypeError();
45
46		var thisp = arguments[1];
47		for (var i = 0; i < len; i++)
48		{
49			if (i in t)
50				fun.call(thisp, t[i], i, t);
51		}
52	};
53}
54
55
56
57// Проверка типа на массив
58if (typeof Array.isArray === 'undefined') {
59	Array.isArray = function(obj) {
60		return Object.toString.call(obj) === '[object Array]';
61	}
62};
63
64(function ($) {
65
66	var readyFns = [];
67	var readyInited = false;
68
69
70
71	/**
72	 * Запускает каллбэк при готовности страницы к выколнению скриптов
73	 *
74	 * Аналог $(document).ready()
75	 *
76	 * @param [n] {number} Порядковый номер выполнения. Актуален до первого срабатывания каллбков.
77	 * Дальше будет срабатывать сразу
78	 * @param callback {function} Каллбэк, которые запускается
79	 */
80	wdpro.ready = function (n, callback) {
81
82		var args = wdpro.argumentsSortByTypes(arguments);
83
84		if (readyInited) {
85			args['function']($);
86		}
87
88		else {
89			// Номер
90			if (args['number'] === undefined) {
91				args['number'] = 10;
92			}
93
94			readyFns.push(args);
95		}
96
97	};
98
99	$(document).ready(function () {
100		readyInited = true;
101
102		readyFns.sort(function (a, b) {
103			if (a['number'] > b['number']) {
104				return 1;
105			}
106			if (a['number'] < b['number']) {
107				return -1;
108			}
109			return 0;
110		});
111
112		$.each(readyFns, function (i, object) {
113			object['function']($);
114		});
115
116		wdpro.body = $('body');
117	});
118
119
120
121	/**
122	 * Как mktime в php
123	 *
124	 * http://javascript.ru/php/mktime
125	 */
126	wdpro.mktime = function mktime() {
127
128		var i = 0, d = new Date(), argv = arguments, argc = argv.length;
129
130		var dateManip = {
131			0: function(tt){ return d.setHours(tt); },
132			1: function(tt){ return d.setMinutes(tt); },
133			2: function(tt){ return d.setSeconds(tt); },
134			3: function(tt){ return d.setMonth((tt)-1); },
135			4: function(tt){ return d.setDate(tt); },
136			5: function(tt){ return d.setYear(tt); }
137		};
138
139		for( i = 0; i < argc; i++ ){
140			if(argv[i] && isNaN(argv[i])){
141				return false;
142			} else if(argv[i]){
143				// arg is number, let's manipulate date object
144				if(!dateManip[i](argv[i])){
145					// failed
146					return false;
147				}
148			}
149		}
150
151		return Math.floor(d.getTime()/1000);
152	};
153
154
155	wdpro.localStorage = {
156
157
158		/**
159		 * Сохраняет данные в localStorage
160		 * @param name {string} Имя данных
161		 * @param data {*} Данные
162		 */
163		set: function (name, data) {
164
165			if (data === null) {
166				localStorage.removeItem(name);
167			}
168
169			else {
170				localStorage.setItem(name, JSON.stringify(data));
171			}
172		},
173
174
175		/**
176		 * Загружает данные по имени
177		 *
178		 * @param name {string} Имя данных
179		 * @return {*}
180		 */
181		get: function (name) {
182			var json = localStorage.getItem(name);
183
184			if (json) {
185				var data = wdpro.parseJSON(json);
186				return data;
187			}
188		}
189	};
190
191
192	// Yandex Metrika
193	var yandexMetrikaIds = [];
194
195	/**
196	 * Добавляет счетчик Метрики
197	 */
198	wdpro.yandexMetrikaAddId = function (id) {
199		yandexMetrikaIds.push(id);
200	};
201
202
203	/**
204	 * Запускает функцию ym для каждого счетчика метрики
205	 *
206	 * @example
207	 * ym(XXXXXX, 'reachGoal', target[, params[, callback[, ctx]]]);
208	 *
209	 * https://yandex.ru/support/metrica/objects/reachgoal.html
210	 * wdpro.yandexMetrikaAddId(XXXXXX);
211	 * wdpro.yandexMetrika('reachGoal', target[, params[, callback[, ctx]]]);
212	 */
213	wdpro.yandexMetrika = function (...args) {
214		wdpro.each(yandexMetrikaIds, function (id) {
215			let argsForNewMethod = wdpro.clone(args);
216			let argsForOldMethod = wdpro.clone(args);
217			argsForOldMethod.unshift(id);
218
219			console.log('metrika', argsForOldMethod);
220
221			let counterFnKey = 'yaCounter'+id;
222
223			let counter = window[counterFnKey];
224			let method = argsForNewMethod.shift();
225
226
227			//console.log('counter', counter);
228			//console.log('method', method);
229			//console.log('args', argsForNewMethod);
230
231			if (counter && counter[argsForOldMethod[1]]) {
232				console.log('counter', method, argsForNewMethod);
233				counter[method](...argsForNewMethod);
234			}
235			else if (window.ym) {
236				ym(...argsForOldMethod);
237			}
238		});
239	};
240
241
242	/**
243	 * Достижение цели
244	 *
245	 * https://yandex.ru/support/metrica/objects/reachgoal.html
246	 *
247	 * target[, params[, callback[, ctx]]]);
248	 */
249	wdpro.yandexMetrikaGoal = function (...args) {
250		var args2 = wdpro.clone(args);
251		args2.unshift('reachGoal');
252
253		wdpro.yandexMetrika(...args2);
254	};
255
256
257	(function(){
258		var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
259
260		/**
261		 * The base Class implementation (does nothing)
262		 *
263		 * @this {Class}
264		 * @constructor
265		 */
266		this.Class = function(){};
267
268
269		Class.extendMulty = function (prop1, prop2, propn)
270		{
271			var newClass = Class;
272
273			wdpro.each(wdpro.args(arguments), function (prop)
274			{
275				newClass = newClass.extend(prop);
276			});
277
278			return newClass;
279		};
280
281		// Create a new Class that inherits from this class
282		Class.extend = function(prop) {
283
284			var _super = this.prototype;
285
286			// Instantiate a base class (but only create the instance,
287			// don't run the init constructor)
288			initializing = true;
289			var prototype = new this();
290			initializing = false;
291
292			// Copy the properties over onto the new prototype
293			for (var name in prop) {
294				// Check if we're overwriting an existing function
295				prototype[name] = typeof prop[name] == "function" &&
296				                  typeof _super[name] == "function" && fnTest.test(prop[name]) ?
297				                  (function(name, fn){
298					                  return function() {
299						                  var tmp = this._super;
300
301						                  // Add a new ._super() method that is the same method
302						                  // but on the super-class
303						                  this._super = _super[name];
304
305						                  // The method only need to be bound temporarily, so we
306						                  // remove it when we're done executing
307						                  var ret = fn.apply(this, arguments);
308						                  this._super = tmp;
309
310						                  return ret;
311					                  };
312				                  })(name, prop[name]) :
313				                  prop[name];
314			}
315
316			// The dummy class constructor
317			function Class() {
318				// All construction is actually done in the init method
319				if ( !initializing && this.init )
320					this.init.apply(this, arguments);
321			}
322
323			// Populate our constructed prototype object
324			Class.prototype = prototype;
325
326			// Enforce the constructor to be what we expect
327			Class.prototype.constructor = Class;
328
329			// And make this class extendable
330			Class.extend = arguments.callee;
331
332			return Class;
333		};
334	})();
335
336	/**
337	 * Создание класса
338	 *
339	 * @param prop
340	 * @returns {Class}
341	 */
342	wdpro.Class = function (prop)
343	{
344		return Class.extendMulty.apply(Class, arguments);
345	};
346
347
348	/**
349	 * Удаление значений из массива
350	 *
351	 * @param arr {[]} Массив
352	 * @param value {*} Значение
353	 */
354	wdpro.arrayRemoveValue = function (arr, value)
355	{
356		for (var i=0; i < arr.length; i++){
357			if (value == arr[i]){
358				arr.splice(i, 1);
359				--i;
360			}
361		}
362	};
363
364
365	/**
366	 * Удаление из массива по ключу
367	 *
368	 * @param arr {Array} Массив
369	 * @param i {number} Ключ
370	 */
371	wdpro.arrayRemoveByI = function (arr, i) {
372		arr.splice(i, 1);
373	};
374
375
376	/**
377	 * Удаление повторяющихся значений из массива
378	 *
379	 * @param array {Array} Массив с повторяющимися значениями
380	 * @returns {Array}
381	 */
382	wdpro.arrayRemoveRepeatsValues = function (array) {
383
384		var i = array.length, result = [];
385
386		array.sort(function(a,b) {
387			return b-a;
388		});
389
390		while(i--){
391			if(result.join().search(array[i]+'\\b') == '-1') {
392				result.push(array[i]);
393			}
394		}
395
396		return result;
397	};
398
399
400	/**
401	 * Возвращает количество элементов в объекте
402	 *
403	 * @param obj {{}} Объект
404	 * @returns {number}
405	 */
406	wdpro.objectLength = function (obj) {
407
408		var n = 0;
409
410		wdpro.each(obj, function () {
411			n ++;
412		});
413
414		return n;
415	};
416
417
418	/**
419	 * Обработка контента
420	 *
421	 * @param content {jQuery}
422	 */
423	wdpro.contentProcess = function (content) {
424		wdpro.trigger('content', content);
425	};
426
427
428	/**
429	 * Установка обработчиков контента
430	 *
431	 * @param callback {function} Каллбэк, в который отправляется контент для обработки
432	 */
433	wdpro.onContent = function (callback) {
434		wdpro.on('content', callback);
435	};
436
437
438	/**
439	 * Run filter
440	 *
441	 * @param  {string}   filterName
442	 * @param  {*}   data
443	 * @param  {Function} callback
444	 */
445	wdpro.doFilter = (filterName, data, callback) => {
446
447		if (!this._filters || !this._filters[filterName]) {
448			callback(data);
449			return;
450		}
451
452		wdpro.orderList(
453			this._filters[filterName],
454
455			(filter, next) => {
456				filter(data, newData => {
457					data = newData;
458					next();
459				});
460			},
461
462			() => callback(data)
463		);
464	};
465
466
467	/**
468	 * Add filter
469	 *
470	 * @param {string}   filterName
471	 * @param {Function} callback
472	 */
473	wdpro.addFilter = (filterName, callback) => {
474		this._filters = this._filters || {};
475		this._filters[filterName] = this._filters[filterName] || [];
476
477		this._filters[filterName].push(callback);
478	};
479
480
481	/**
482	 * Запускает каллбэк для каждого элемента поочереди, после чего запускает завершающий каллбэк
483	 *
484	 * @param list {{}|[]|Set} Список элементов
485	 * @param callbackEachElement {function} Каллбэк, принимающия элементы
486	 * @param [callbackFinish] {function} Каллбэк, запускающийся в конце
487	 */
488	wdpro.orderList = (list, callbackEachElement, callbackFinish) => {
489		wdpro.waitList (list, callbackEachElement, callbackFinish, true);
490	};
491
492
493	/**
494	 * Запускает каллбэк для каждого элемента поочереди, после чего запускает завершающий каллбэк
495	 *
496	 * @param list {{}|[]|Set} Список элементов
497	 * @param callbackEachElement {function} Каллбэк, принимающия элементы
498	 * @param callbackFinish {function} Каллбэк, запускающийся в конце
499	 * @param [oneByOne] {boolean} Поочереди
500	 */
501	wdpro.waitList = (list, callbackEachElement, callbackFinish, oneByOne) => {
502		let waiter = new Waiter();
503
504		if (list instanceof Set) {
505			list.forEach(element => {
506				waiter.add(next => callbackEachElement(element, next), oneByOne);
507			});
508		}
509
510		else {
511			for (let key in list) {
512				let element = list[key];
513				waiter.add(next => callbackEachElement(element, next), oneByOne);
514			}
515		}
516
517		waiter.run(callbackFinish);
518	};
519
520
521	wdpro.setCookie = (cname, cvalue, { exdays = 365, path=null, domain=null } = {}) => {
522		
523		path = path || '/';
524
525		wdpro.deleteCookie(cname);
526		// path = path || wdpro.homeUrl();
527
528		domain = domain || location.hostname;
529
530		exdays = exdays || 365;
531		let d = new Date();
532		d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
533		let expires = "expires=" + d.toUTCString();
534
535		let pathString = path ? ";path=" + path : '';
536		let cookieString = cname + "=" + cvalue + ";" + expires + pathString;
537		cookieString += '; domain=' + domain;
538		document.cookie = cookieString;
539
540		console.log('setCookie', cookieString)
541
542	};
543
544
545  wdpro.getCookie = (cname) => {
546    const name = cname + "=";
547    const decodedCookie = decodeURIComponent(document.cookie);
548    const cookieArray = decodedCookie.split(';');
549
550    for (let i = 0; i < cookieArray.length; i++) {
551      let cookie = cookieArray[i].trim();
552      if (cookie.indexOf(name) === 0) {
553        return cookie.substring(name.length, cookie.length);
554      }
555    }
556    return null; // Вернуть null, если куки с таким именем не найдено
557  };
558
559
560	wdpro.deleteCookie = name => {
561		let domain = location.hostname,
562			path = '/'; // root path
563	
564		const deleteFromDomain = domain => {
565			// let cookieString = [
566			// 	name, '=',
567			// 	'; expires=' + new Date(0).toUTCString(),
568			// 	'; path=' + path,
569			// 	'; domain=' + domain
570			// ].join('');
571
572      let cookie = [
573        name+'=',
574        'expires=' + new Date(0).toUTCString(),
575        'path=' + path,
576      ]
577
578      if (domain) {
579        cookie.push('domain='+domain)
580      }
581
582      let cookieString = cookie.join('; ')
583      console.log('cookieString', cookieString)
584
585			// "auth_id=;expires=Mon, 11 Oct 2021 18:09:18 GMT;path=/" OK
586			//console.log('delete cookieString', cookieString)
587			document.cookie = cookieString;
588		};
589
590    deleteFromDomain();
591    deleteFromDomain(domain);
592		deleteFromDomain('.'+domain);
593
594	};
595
596
597	/**
598	 * Возвращает простой объект без всяких строгих типов
599	 *
600	 * @param object
601	 * @returns {any}
602	 */
603	var obj = wdpro.obj = function (object)
604	{
605		return object;
606	};
607
608
609	/**
610	 * Преобразует arguments в массив
611	 *
612	 * @param args {arguments} Аргументы функции
613	 * @param [start] {number} Индекс первого добавляемого в массив элемента
614	 * @returns {Array}
615	 */
616	var args = wdpro.args = function (args, start)
617	{
618		if (start === undefined)
619		{
620			start = 0;
621		}
622
623		var arr = [];
624
625		for(var i = start; i < args.length; i++)
626		{
627			arr.push(args[i]);
628		}
629
630		return arr;
631	};
632
633
634	/**
635	 * Объединение объектов
636	 *
637	 * @param obj {{}} Объект в который копируются свойства
638	 * @param objects {{}} Объекты из которых копируются свойства
639	 * @returns {{}}
640	 */
641	var extend = wdpro.extend = function (obj, objects)
642	{
643		obj = obj || {};
644
645		args(arguments).forEach(function(source) {
646			if (source) {
647				for (var prop in source) {
648					if (source[prop])
649					{
650						if (source[prop].constructor === Object) {
651							if (!obj[prop] || obj[prop].constructor === Object) {
652								obj[prop] = obj[prop] || {};
653								extend(obj[prop], source[prop]);
654							} else {
655								obj[prop] = source[prop];
656							}
657						} else {
658							obj[prop] = source[prop];
659						}
660					}else {
661						obj[prop] = source[prop];
662					}
663				}
664			}
665		});
666		return obj;
667	};
668
669
670	/**
671	 * Перебор элементов
672	 *
673	 * @param list {*} Список элементов
674	 * @param callback {function} Каллбэк, принимающий элементы
675	 */
676	var each = wdpro.each = function (list, callback)
677	{
678		if (typeof list == 'string')
679		{
680			list = [list];
681		}
682
683		if (typeof list == 'object')
684		{
685			for(var n in list)
686			{
687				callback(list[n], n);
688			}
689		}
690	};
691
692
693	/**
694	 * Берет из аргументов все объекты {} и объеиняет их
695	 * @param arguments {*} Аргументы
696	 * @param firstKey {number} Первый ключ аргументов, с которого объединять объекты аргументов
697	 * @returns {{}} Объединенный объект
698	 */
699	var argumentsObjectsUnion = function (arguments, firstKey)
700	{
701		var summ = {};
702
703		args(arguments, firstKey).forEach(function (element)
704		{
705			if (typeof element == 'object')
706			{
707				summ = extend(element, summ);
708			}
709		});
710
711		return summ;
712	};
713	wdpro.argumentsObjectsUnion = argumentsObjectsUnion;
714
715
716	/**
717	 * Оаспределяет аргументы по типам
718	 *
719	 * @param arguments
720	 * @returns {{}}
721	 */
722	var argumentsSortByTypes = function (arguments)
723	{
724		var argsByTypes = {};
725
726		each(args(arguments), function (argument)
727		{
728			argsByTypes[typeof argument] = argument;
729		});
730
731		return argsByTypes;
732	};
733	wdpro.argumentsSortByTypes = argumentsSortByTypes;
734
735
736	/**
737	 * Объединяет все объекты в массиве в один
738	 *
739	 * @param arr {array} Массив объектов
740	 * @returns {{}}
741	 */
742	var extendObjectsFromArray = function(arr)
743	{
744		var summ = {};
745
746		each(arr, function (element)
747		{
748			if (typeof element == 'object')
749			{
750				summ = extend(element, summ);
751			}
752		});
753
754		return summ;
755	};
756	wdpro.extendObjectsFromArray = extendObjectsFromArray;
757
758
759	/**
760	 * Преобразует объект в queryString
761	 *
762	 * @param obj {{}} Объект
763	 * @param [prefix] Префикс
764	 * @returns {string}
765	 */
766	var param = wdpro.param = function (obj, prefix)
767	{
768		var str = [];
769		for(var p in obj) {
770			var k = prefix ? prefix + "[" + p + "]" : p, v = obj[p];
771			str.push(typeof v == "object" ?
772			         param(v, k) :
773			         encodeURIComponent(k) + "=" + encodeURIComponent(v));
774		}
775		return str.join("&");
776	};
777
778
779	/**
780	 * Возвращает значение объекта по строке типа "key1.key2.key3"
781	 *
782	 * @param keys {string} Строка, обозначающая объект типа "key1.key2.key3"
783	 * @returns {*}
784	 */
785	var getObjectFromStringKeys = wdpro.getObjectFromStringKeys = function (keys)
786	{
787		var arr = keys.split('.');
788
789		var IF = '';
790		var lastPiece = '';
791
792		each(arr, function (key)
793		{
794			if (IF != '')
795			{
796				IF += ' && ';
797			}
798
799			var currentKey = key;
800			if (lastPiece)
801			{
802				currentKey = lastPiece+'.'+currentKey;
803			}
804
805			IF += currentKey;
806
807			lastPiece = currentKey;
808		});
809
810		if (eval(IF))
811		{
812			return eval(lastPiece);
813		}
814	};
815
816
817	/**
818	 * Вызывает функцию из объекта, находя ее по ключу типа "key1.key2.key3"
819	 *
820	 * @param keys {string} Ключ
821	 * @param attrs {*} Атрибуты для функции
822	 * @returns {*}
823	 */
824	var evalFnFromStringKeysObject = wdpro.evalFnFromStringKeysObject = function (keys, attrs)
825	{
826		var fn = getObjectFromStringKeys(keys);
827
828		if (typeof fn == 'function')
829		{
830			return fn(attrs);
831		}
832	};
833
834
835
836	/**
837	 * Класс, который запускает каллбэк по завершению всех добавленных функций
838	 */
839	var Waiter = wdpro.Waiter = Class.extend({
840
841		/**
842		 * Конструктор
843		 */
844		init: function ()
845		      {
846			      // Создаем список функций
847			      this.functions = new List();
848
849			      // Данные, собранные из ожидаемых функций
850			      this.data = {};
851		      },
852
853
854		/**
855		 * Добавить функцию, выполнение которой надо дождаться
856		 *
857		 * @param fn {function} Функция
858		 * @param [params] Параметры
859		 */
860		wait: function(fn, params)
861		      {
862			      var ob = extend({
863				      fn: fn
864			      }, params);
865
866			      this.functions.push(ob);
867		      },
868
869
870		/**
871		 * Добавить функцию, выполнение которой надо дождаться
872		 *
873		 * @param fn {function} Функция
874		 * @param [params] Параметры
875		 */
876		add: function (fn, params) {
877			this.wait(fn, params);
878		},
879
880
881		/**
882		 * Запуск всех функций, завершение которых надо дождаться
883		 *
884		 * @param completeCallback {function}
884 Каллбэк, срабатывающий при завершении всех функций
885		 */
886		onCompletion: function(completeCallback)
887    {
888      var self = this;
889
890      if (completeCallback)
891      {
892        this.completeCallback = completeCallback;
893      }
894
895      // Если есть функции, которые надо выполнить
896      if (this.functions.count() > 0)
897      {
898        // Остановить процесс
899        var stop = false;
900
901        /**
902         * Список ожидающих функций
903         */
904        var runArr = new List();
905
906        // Перечисляем функции
907        this.functions.each(function (fnOb, n)
908        {
909          if (!stop)
910          {
911            // Надо ли ожидать выполнение предыдущей функции
912            var wait = fnOb.wait;
913
914            // Надо ждать и это не первая функция
915            if (wait && n != 0)
916            {
917              stop = true;
918
919              // Убираем из функции метку о том, что надо ждать выполнение предыдущей функции
920              fnOb.wait = false;
921            }
922
923            // Не надо ждать выполнение предыдущей функции, запускаем эту
924            else if (!fnOb.runned)
925            {
926              (function ()
927              {
928	              // Получаем функцию
929	              var fn = fnOb.fn;
930
931	              // Запоминаем, что функция запущена
932	              fnOb.runned = true;
933
934	              // Каллбэк, который вызывается по завершении работы функции
935	              var complete = (function (data)
936	              {
937		              if (data && (
938				              data.className
939				              || data.prototype && data.prototype.className
940				              || data['__proto__'] && ['__proto__'].className
941			              ))
942		              {
943			              // Ошибка, когда в результат отправляется объект класса
944			              // Так нельзя делать, потому что этот результат сливается с другими результатами
945			              // И у объектов ломаются свойства
946			              throw new Error('Waiter.wait() получил результат в виде объекта на основе класса ');
947		              }
948
949		              if (typeof data == 'object')
950		              {
951			              self.data = extend(data, self.data);
952		              }
953
954		              self.functionComplete(n);
955	              });
956
957	              // Запоминаем функцию на запуск, который произойдет после перебора всех функций
958	              runArr.push({
959		              fn: fn,
960		              complete: complete
961	              });
962              })();
963            }
964          }
965        });
966
967        // Запуск функций после перебора
968        runArr.each(function(element)
969        {
970          element.fn(element.complete, self.data);
971        });
972      }
973
974
975      // Функций больше нет
976      else
977      {
978        // Запускаем каллбэк, который ожидал их выполнение
979        this.completeCallback && this.completeCallback(this.data);
980      }
981    },
982
983
984		/**
985		 * Аналог waiter.onCompletion
986		 *
987		 * @param callback {function}
988		 */
989		onFinish: function (callback) {
990			this.onCompletion(callback);
991		},
992
993
994		/**
995		 * Аналог waiter.onCompletion
996		 *
997		 * @param callback {function}
998		 */
999		run: function (callback) {
1000			this.onCompletion(callback);
1001		},
1002
1003
1004		/**
1005		 * Обработка завершенной функции
1006		 *
1007		 * @param n {number} Номер функции
1008		 */
1009		functionComplete: function(n) {
1010			                  this.functions.unset(n);
1011
1012			                  this.onCompletion();
1013		                  }
1014	});
1015
1016
1017	/**
1018	 * Выполняет функции по порядку
1019	 *
1020	 * @param fn1 {function} Функция 1
1021	 * @param fn2 {function} Функция 2
1022	 * @param fnN {function} Функция N
1023	 */
1024	wdpro.order = function (fn1, fn2, fnN) {
1025
1026		var args = wdpro.args(arguments);
1027
1028		var waiter = new wdpro.Waiter();
1029
1030		// Добавляем функции кроме последней
1031		for (var i=0; i < args.length - 1; i ++) {
1032			waiter.wait(args[i], true);
1033		}
1034
1035		// Добавляем последнюю функцию
1036		waiter.run(args[args.length-1]);
1037	};
1038
1039
1040	/**
1041	 * Дожидается выполнения всех функций и после этого запускает последнюю
1042	 *
1043	 * @param fn1 {function} Функция 1
1044	 * @param fn2 {function} Функция 2
1045	 * @param fnN {function} Функция N
1046	 */
1047	wdpro.wait = function (fn1, fn2, fnN) {
1048
1049		var args = wdpro.args(arguments);
1050
1051		var waiter = new wdpro.Waiter();
1052
1053		// Добавляем функции кроме последней
1054		for (var i=0; i < args.length - 1; i ++) {
1055			waiter.wait(args[i]);
1056		}
1057
1058		// Добавляем последнюю функцию
1059		waiter.run(args[args.length-1]);
1060	};
1061
1062
1063	/**
1064	 *
1065	 */
1066	// export class WaiterGet
1067	var WaiterGet = wdpro.WaiterGet = Class.extend({
1068
1069
1070		/**
1071		 * Запоминает объект
1072		 *
1073		 * @param name {string} Имя, под которым объект запоминается
1074		 * @param [value] {*} Сам объект
1075		 */
1076		waitSet: function(name, value)
1077		         {
1078			         // Сохраняем объект
1079			         this.waiterGetObjects = this.waiterGetObjects || {};
1080			         this.waiterGetObjects[name] = value;
1081
1082			         // Если есть ожидающие каллбэÐ
1082ºÐ¸
1083			         if (this.waiterGetCallbacks && this.waiterGetCallbacks[name])
1084			         {
1085				         // Перебираем их
1086				         this.waiterGetCallbacks[name].forEach(function(callback)
1087				         {
1088					         if (typeof callback == 'function')
1089					         {
1090						         callback(value);
1091					         }
1092				         });
1093
1094				         // Удаляем их
1095				         //delete this.waiterGetCallbacks[name];
1096			         }
1097		         },
1098
1099
1100		/**
1101		 * Возвращает объект при наличии или появлении объекта
1102		 *
1103		 * @param name {string} Имя, под которым обхект был сохранен
1104		 * @param callback {function} Каллбэк, получающий обхект
1105		 */
1106		waitGet: function(name, callback)
1107		         {
1108			         // Если есть объект
1109			         if (this.waiterGetObjects && this.waiterGetObjects[name])
1110			         {
1111				         // Возвращаем его
1112				         callback(this.waiterGetObjects[name]);
1113			         }
1114
1115			         // Иначе запоминаем каллбэк, чтобы возвратить объект в этот каллбэк при появлении обхекта
1116			         else
1117			         {
1118				         this.waiterGetCallbacks = this.waiterGetCallbacks || {};
1119				         this.waiterGetCallbacks[name] = this.waiterGetCallbacks[name] || [];
1120				         this.waiterGetCallbacks[name].push(callback);
1121			         }
1122		         }
1123	});
1124
1125
1126	/**
1127	 * Список элементов
1128	 *
1129	 * @this {List}
1130	 */
1131	var List = wdpro.List = Class.extend({
1132
1133
1134		init: function ()
1135		      {
1136			      // Последний немерной ключ, для добавления данных без ключа
1137			      this.ob = {};
1138
1139			      // Текущее количество элементов в списке
1140			      this.lastKey = 1;
1141
1142			      // Текущее количество элементов в списке
1143			      this._count = 0;
1144		      },
1145
1146
1147		/**
1148		 * Установка значения
1149		 *
1150		 * @param key {*} Ключ
1151		 * @param value {*} Данные
1152		 * @returns {boolean}
1153		 */
1154		set:function(key, value)
1155		    {
1156			    /*if (value === undefined)
1157			     {
1158			     value = true;
1159			     }*/
1160
1161			    // Значение уже есть
1162			    if (this.isset(key))
1163			    {
1164				    var ret = false;
1165			    }
1166
1167			    // Значения еще нет
1168			    else
1169			    {
1170				    var ret = true;
1171
1172				    // Увеличиваем счетчик элементов
1173				    this._count ++;
1174			    }
1175
1176			    this.ob[key] = {
1177				    value: value
1178			    };
1179
1180			    return ret;
1181		    },
1182
1183
1184		/**
1185		 * Добавление значения в конец списка
1186		 *
1187		 * @param value {*} Значение
1188		 * @returns {number} Ключ значения в списке
1189		 */
1190		push: function(value)
1191		      {
1192			      // Пока есть данные по такому автоматическому ключу
1193			      while(this.isset(this.lastKey))
1194			      {
1195				      // Увеличиваем счетчик автоматических ключей
1196				      this.lastKey ++;
1197			      }
1198
1199			      // Получаем текущий ключ, чтобы его вернуть в конце метода
1200			      var ret = this.lastKey;
1201
1202			      // Устанавливаем значение
1203			      this.set(this.lastKey, value);
1204
1205			      return ret;
1206		      },
1207
1208
1209		/**
1210		 * Возвращает значение, если оно существует
1211		 *
1212		 * @param key {*} Ключ значения
1213		 * @returns {*}
1214		 */
1215		get: function(key)
1216		     {
1217			     if (this.isset(key))
1218			     {
1219				     return this.ob[key].value;
1220			     }
1221		     },
1222
1223
1224		/**
1225		 * Проверяет наличие данных
1226		 *
1227		 * @param key {*} Ключ данных
1228		 * @returns {boolean}
1229		 */
1230		isset: function(key)
1231		       {
1232			       return this.ob[key] ? true : false;
1233			       //return key in this.ob ? true : false;
1234		       },
1235
1236
1237		/**
1238		 * Удаление значения по ключу
1239		 *
1240		 * @param key {*} Ключ
1241		 * @returns {boolean}
1242		 */
1243		unset: function(key)
1244		       {
1245			       // Если такой элемент существует
1246			       if (this.isset(key))
1247			       {
1248				       // Запоминаем чтобы возвратить
1249				       var ret = this.ob[key];
1250
1251				       // Удаляем его
1252				       delete this.ob[key];
1253
1254				       // Уменьшаем количество элементов
1255				       this._count --;
1256
1257				       return ret.value;
1258			       }
1259
1260			       return false;
1261		       },
1262
1263
1264		/**
1265		 * Перебор всех элементов
1266		 *
1267		 * @param callback {function} Каллбэк, получающий элементы
1268		 */
1269		each: function(callback)
1270		      {
1271			      for (var key in this.ob)
1272			      {
1273				      callback(this.ob[key].value, key);
1274			      }
1275		      },
1276
1277
1278		/**
1279		 * Возвращает количество элементов
1280		 *
1281		 * @returns {number}
1282		 */
1283		count: function ()
1284		       {
1285			       return this._count;
1286		       },
1287
1288
1289		/**
1290		 * Возвращает первый элемент списка
1291		 *
1292		 * @returns {*}
1293		 */
1294		getFirst: function ()
1295		          {
1296			          for (var n in this.ob)
1297			          {
1298				          return this.ob[n];
1299				          break;
1300			          }
1301		          },
1302
1303
1304		/**
1305		 * Возвращает массив всех ключей
1306		 *
1307		 * @returns {Array}
1308		 */
1309		keys: function () {
1310			var keys = [];
1311
1312			for(var key in this.ob)
1313			{
1314				keys.push(key);
1315			}
1316
1317			return keys;
1318		}
1319	});
1320
1321
1322	/**
1323	 * Класс событий для наследования другими классами
1324	 *
1325	 * @type {{}|*}
1326	 */
1327	var Event = wdpro.Event = Class.extend({
1328
1329
1330		/**
1331		 * Установка прослушки на событие
1332		 *
1333		 * @param actionNames {string} Имя (имена) события с пространством имен в формате name.namespace
1334		 * @param callback {function} Каллбэк, принимающий событие
1335		 * @param [params] {{ listenPrev: boolean }} Параметры
1336		 */
1337		on: function (actionNames, callback, params) {
1338			var self = this;
1339
1340			// Загружать предыдущий, когда просто true
1341			if (params === true || params === 1)
1342			{
1343				params = {last: true};
1344			}
1345
1346			// По-умолчанию
1347			params = extend({}, params);
1348
1349			// Разбиваем строку событий на имена
1350			var actionNamesArr = actionNames.split(' ');
1351
1352			// Перебираем имена событий
1353			each(actionNamesArr, function (actionName) {
1354				// Парсим имя
1355				var action = self.eventParseName(actionName);
1356
1357				// Если нету массива каллбэков
1358				if (!self.callbacks)
1359				{
1360					// Создаем этот список
1361					self.callbacks = new List();
1362				}
1363
1364				// Если нету списка каллбэков для данного действия
1365				if (!self.callbacks.isset(action.name))
1366				{
1367					// Создаем этот список
1368					self.callbacks.set(action.name, new List());
1369				}
1370
1371				// Добавляем каллбэк в массив
1372				var callbackN = self.callbacks.get(action.name).push({
1373					fn:     callback,
1374					action: actionName
1375				});
1376
1377				// Для удаления по пространству имен
1378				if (action.namespace)
1379				{
1380					// Если не существует списка номеров для удаления
1381					if (!self.removeByNamespace)
1382					{
1383						// Создаем этот список
1384						self.removeByNamespace = new List();
1385					}
1386
1387					// Если не существует списка для данного действия
1388					if (!self.removeByNamespace.isset(actionName))
1389					{
1390						// Создаем этот список
1391						self.removeByNamespace.set(actionName, new List());
1392					}
1393
1394					// Добавляем номер в список
1395					self.removeByNamespace.get(actionName).push(callbackN);
1396				}
1397
1398
1399				// Учет предыдущего события
1400				// Чтобы можно было добавить прослушку на событие, которое уже произошло
1401				if (params.listenPrev || params.prev || params.last)
1402				{
1403					// Если есть предыдущее событие
1404					if (self.prevEvents && self.prevEvents.isset(action.name))
1405					{
1406						// Отправляем его в каллбэк
1407						callback(self.prevEvents.get(action.name));
1408					}
1409				}
1410			});
1411
1412
1413			return this;
1414		},
1415
1416
1417		/**
1418		 * Снятие прослушки события
1419		 *
1420		 * @param actionName {string} Имя события с пространством имен в формате name.namespace
1421		 * @returns {Event}
1422		 */
1423		off: function (actionName) {
1424			var self = this;
1425
1426			// Парсим имя события
1427			var action = this.eventParseName(actionName);
1428
1429			// Удаление по пространству имен
1430			if (action.namespace) {
1431				// Если есть каллбэки для данного пространства имен
1432				if (this.removeByNamespace && this.removeByNamespace.isset(actionName)) {
1433					// Получаем список номеров каллбэков пространства имен
1434					var ns = this.removeByNamespace.unset(actionName);
1435
1436					// Получаем список вызываемых каллбэков
1437					var callbacks = self.callbacks.get(action.name);
1438
1439					// Если есть список вызываемых каллбэков
1440					if (callbacks) {
1441						// Перебираем номера каллбэков
1442						ns.each(function (n) {
1443							// Удаляем этот каллбэк по номеру
1444							callbacks.unset(n);
1445						});
1446					}
1447				}
1448			}
1449
1450
1451			// Удаление без пространства имен
1452			else {
1453				// Удаляем все каллбэки из списка каллбэков по имени события
1454				if (this.callbacks) {
1455					this.callbacks.unset(action.name);
1456				}
1457
1458				// Удаляем инфу о каллбэках по пространствам имен
1459				this.removeByNamespace && this.removeByNamespace.each(function (ns, actionName) {
1460					// Парсим имя события
1461					var action = self.eventParseName(actionName);
1462
1463					// Если имя совпадает с удаляемым
1464					if (action.name === actionName) {
1465						// Удаляем номера этого каллбэка из списка номеров для удаления по пространству имен
1466						self.removeByNamespace.unset(actionName);
1467					}
1468				});
1469			}
1470
1471
1472			return this;
1473		},
1474
1475
1476		/**
1477		 * Отправка событие во все прослушки этого события
1478		 *
1479		 * @param actionName {string} Имя события без пространства имен
1480		 * @param [data] {{}} Данные, отправляемые в прослушки события
1481		 * @returns {number} Количество запущенных прослушек в данный момент
1482		 */
1483		trigger: function (actionName, data) {
1484			// Количество запущенных каллбэков
1485			var ret = 0;
1486
1487			// Если есть список прослушек
1488			if (this.callbacks) {
1489				// Получаем список прослушек для этого события
1490				var callbacks = this.callbacks.get(actionName);
1491
1492				// Если есть прослушки на данное событие
1493				if (callbacks) {
1494					// Перебираем прослушки
1495					callbacks.each(function (callback) {
1496						if (typeof callback.fn === 'function') {
1497							// Запускаем прослушку
1498							callback.fn(data);
1499
1500							// Увеличиваем счетчик запущенных каллбэков
1501							ret++;
1502						}
1503					});
1504				}
1505			}
1506
1507			// Запоминаем данное событие для прослушек, которые будут установлены после,
1508			// но которые по параметрам будут требовать данное событие
1509			if (!this.prevEvents) {
1510				this.prevEvents = new List();
1511			}
1512			this.prevEvents.set(actionName, data);
1513
1514			return ret;
1515		},
1516
1517
1518		/**
1519		 * Уствновка списка прослушек одним разом
1520		 *
1521		 * @param [list] {{}} Объект, содержащий прослушки
1522		 * @param [keyInList] {*} Ключ списка прослушек в этом объекте (когда объект не список прослушек, а список всяких штук, одной из штук которого является список прослушек)
1523		 */
1524		onList: function (list, keyInList) {
1525			var self = this;
1526
1527			if (typeof list === 'object') {
1528				// Список каллбэков в ключе
1529				if (keyInList) {
1530					list = list[keyInList];
1531				}
1532
1533				// Если есть такой список калббэков
1534				if (list && typeof list === 'object') {
1535					// Перебираем прослушки
1536					each(list, function (eventCallback, eventName) {
1537						// Добавляем прослушку события
1538						self.on(eventName, eventCallback);
1539					});
1540				}
1541			}
1542		},
1543
1544
1545		/**
1546		 * Разбивает имя события на имя и пространство имен
1547		 *
1548		 * @param name {string}
1549		 * @returns {{name: string, namespace: string}}
1550		 */
1551		eventParseName: function (name) {
1552			var pieces = name.split('.');
1553
1554			return {
1555				name: pieces[0],
1556				namespace: pieces[1]
1557			};
1558		}
1559	});
1560
1561
1562
1563	var jQueryToHtmlObjects, jQueryToHtmlObjectsId;
1564
1565	/**
1566	 * Возвращает <span>, который потом заменится на jQuery блок
1567	 *
1568	 * Следует использовать тогда, когда мы имеем html код в виде строки
1569	 * и в него надо вставить jQuery элемент
1570	 *
1571	 * После того, как html строка превратится в jQuery элемент, следует запустить Seobit.jQueryToHtmlRun
1572	 *
1573	 * Алиас служит для того, чтобы после Seobit.jQueryToHtmlRun удалились все объекты, которые были добавлены
1574	 * по этому алиасу
1575	 *
1576	 * @param jQueryObject jQuery объект, вставляемый в html строку
1577	 * @param alias Алиас
1578	 */
1579	wdpro.jQueryToHtml = function (jQueryObject, alias)
1580	{
1581		// Создаем при необходимости список меток данного алиаса
1582		jQueryToHtmlObjects = jQueryToHtmlObjects || {};
1583		jQueryToHtmlObjects[alias] = jQueryToHtmlObjects[alias] || {};
1584
1585		// Получаем уникальный номер метки
1586		jQueryToHtmlObjectsId = jQueryToHtmlObjectsId || 0;
1587		jQueryToHtmlObjectsId ++;
1588
1589		// Запоминаем объект jQuery
1590		jQueryToHtmlObjects[alias][jQueryToHtmlObjectsId] = jQueryObject;
1591
1592		// Возвращаем код метки
1593		return '<span class="JS_seobit_jquery_to_html" JS_id="'+jQueryToHtmlObjectsId+'"></span>';
1594	};
1595
1596
1597	/**
1598	 * Заменяет в блоке jQuery метки <span> на jQuery элементы, которые были добавлены в jQuery блок,
1599	 * когда он был html строкой
1600	 *
1601	 * @param jQueryBlock jQuery блок с метками, который ранее был html строкой
1602	 * @param alias {String} Алиас,
1603	 * метки jQuery элементы которого удаляются после расстановки jQuery элементов
1604	 */
1605	wdpro.jQueryToHtmlRun = function (jQueryBlock, alias)
1606	{
1607		if (jQueryBlock instanceof $)
1608		{
1609			/**
1610			 * Функция, которая находит в jQuery блоке метки и заменяет их на jQuery элементы
1611			 *
1612			 * @returns {boolean}
1613			 */
1614			var replace = function ()
1615			{
1616				// Были найдены <span> метки
1617				var spanExists = false;
1618
1619				// Перебираем span метки
1620				$(jQueryBlock).find('.JS_seobit_jquery_to_html').each(function ()
1621				{
1622					// Запоминаем что были найдены <span> метки
1623					spanExists = true;
1624
1625					// Получаем id jQuery элемента
1626					var id = $(this).attr('JS_id');
1627
1628					// Заменяем метку на jQuery элемент
1629					$(this).after(jQueryToHtmlObjects[alias][id]).remove();
1630				});
1631
1632				return spanExists;
1633			};
1634
1635			// Заменяем метки в jQuery блоке, пока они там есть и появляются
1636			while(replace()) {};
1637
1638			// Удаляем объекты по данному алиасу
1639			if (jQueryToHtmlObjects && jQueryToHtmlObjects[alias])
1640			{
1641				delete jQueryToHtmlObjects[alias];
1642			}
1643		}
1644
1645		else
1646		{
1647			console.log('Не верный формат jQuery блока в jQueryToHtmlRun');
1648		}
1649	};
1650
1651
1652	// Список всех таймеров
1653	var timers = {};
1654
1655	// Список таймеров по именам
1656	var timersByNames = {};
1657
1658	// Последний индекс для таймера
1659	var lastTimersIndex = 0;
1660
1661	// Таймеры еще не инициированы
1662	var timersInited = false;
1663
1664	/**
1665	 * Инициализация основного таймера
1666	 */
1667	wdpro.timeInit = function ()
1668	{
1669		// Завершаем работу, если таймеры были инициированы
1670		if (timersInited) { return; }
1671
1672		// Запускаем общий таймер
1673		setInterval(function()
1674		{
1675			// Перебираем все таймеры
1676			each(timers, function(timer, i)
1677			{
1678				// Текущее время
1679				var now = new Date().getTime();
1680
1681				// Если время таймера подошло к запуску
1682				if (timer.time <= now)
1683				{
1684					// Запускаем каллбэк
1685					timer.callback();
1686
1687					// Если каллбэк необходимо было запустить 1 раз
1688					if (timer.single)
1689					{
1690						// Удаляем этот таймер
1691						timeStop(timer.id);
1692					}
1693
1694					// Если это повторяющийся таймер
1695					else
1696					{
1697						// Меняем время его следующего запуска
1698						timer.time += timer.interval;
1699					}
1700				}
1701			});
1702
1703		}, 12);
1704
1705		// Таймеры инициированы
1706		timersInited = true;
1707	};
1708
1709
1710
1711
1712	/**
1713	 * Запускать каллбэк через каждый промежуток времени
1714	 *
1715	 * @param [name] {string} Имя задачи
1716	 * @param time {number} Время
1717	 * @param callback {function} Каллбэк, постоянно запускающийся через заданное время
1718	 * @param [single] {boolean} Запустить только 1 раз
1719	 */
1720	var timeEach = function (name, time, callback, single)
1721	{
1722		var args = argumentsSortByTypes(arguments);
1723
1724		// Текущее время
1725		var now = new Date().getTime();
1726
1727		// ID таймера
1728		lastTimersIndex ++;
1729
1730		// Имя таймера
1731		var name = args['string'];
1732
1733		// Создаем данные таймера
1734		var newTimer = {
1735			time: now + args['number'],
1736			interval: args['number'],
1737			name: name,
1738			callback: args['function'],
1739			id: lastTimersIndex,
1740			single: args['boolean']
1741		};
1742
1743		// Если есть имя таймера, запоминаем его по в этом имене
1744		if (name)
1745		{
1746			if (!timersByNames[name]) { timersByNames[name] = []; }
1747			timersByNames[name].push(lastTimersIndex);
1748		}
1749
1750		// Добавляем таймер в общий список
1751		timers[lastTimersIndex] = newTimer;
1752	};
1753	wdpro.timeEach = timeEach;
1754
1755
1756	/**
1757	 * Запустить каллбэк 1 раз через заданный промежуток времени
1758	 *
1759	 * @param name {string} Имя задачи
1760	 * @param time {number} Время
1761	 * @param callback {function}
1761 Каллбэк, запускающийся через заданное время
1762	 */
1763	wdpro.timeOne = function (name, time, callback)
1764	{
1765		var args = argumentsSortByTypes(arguments);
1766		timeEach(args['string'], args['number'], args['function'], true);
1767	};
1768
1769
1770	/**
1771	 * Остановка временной задачи
1772	 *
1773	 * @param name {string|number} Имя или ID задачи
1774	 */
1775	var timeStop = wdpro.timeStop = function (id)
1776	{
1777		var ids = [];
1778
1779		// По имени
1780		if (typeof id == 'string')
1781		{
1782			var name = id;
1783
1784			if (timersByNames[name])
1785			{
1786				each(timersByNames[name], function (id)
1787				{
1788					ids.push(id);
1789				});
1790
1791				delete timersByNames[name];
1792			}
1793		}
1794
1795		// По ID
1796		else
1797		{
1798			ids = [id];
1799		}
1800
1801
1802		// Удаление
1803		each(ids, function (id)
1804		{
1805			delete timers[id];
1806		});
1807	};
1808
1809
1810
1811	/**
1812	 * Возвращает копию объекта
1813	 *
1814	 * @param object {{}} Оригинальный объект
1815	 * @param [recursive] {boolean} Копировать рекурсивно
1816	 * @returns {{}|Array}
1817	 */
1818	var clone = wdpro.clone = function (object, recursive)
1819	{
1820		var ret;
1821
1822		if (Array.isArray(object))
1823		{
1824			ret = [];
1825		}
1826		else
1827		{
1828			ret = {};
1829		}
1830
1831		each(object, function (value, i)
1832		{
1833			if (recursive)
1834			{
1835				if (typeof value == 'object')
1836				{
1837					value = clone(value);
1838				}
1839			}
1840			ret[i] = value;
1841		});
1842
1843		return ret;
1844	};
1845
1846
1847
1848	/**
1849	 * Округление числа с заданной точностью
1850	 *
1851	 * @param number {number} Число
1852	 * @param accuracy {number} Точность (по-умолчанию 100)
1853	 * @returns {number}
1854	 */
1855	var round = wdpro.round = function (number, accuracy)
1856	{
1857		if (!accuracy)
1858		{
1859			accuracy = 100;
1860		}
1861
1862		return Math.round(number * accuracy) / accuracy;
1863	};
1864
1865
1866	/**
1867	 * Парсинг JSON
1868	 *
1869	 * @param json {string} JSON строка
1870	 * @returns {*}
1871	 */
1872	var parseJSON = function (json)
1873	{
1874		try
1875		{
1876			if (typeof json === 'string')
1877			{
1878				var ob = JSON.parse(json);
1879				if ((ob != null) && (ob.log != null))
1880				{
1881					console.log('Ob Log: ');
1882					console.log(ob.log);
1883				}
1884			}
1885
1886			else if (typeof json === 'object')
1887			{
1888				ob = json;
1889			}
1890		}
1891		catch (err)
1892		{
1893			console.log('JSON ошибка: ', json);
1894			throw err;
1895		}
1896
1897		return ob;
1898	};
1899
1900
1901	/**
1902	 *
1903	 * @type {parseJSON}
1904	 */
1905	wdpro.parseJSON = parseJSON;
1906
1907
1908	/**
1909	 * Удаляет лишние пробелы из конца и начала строки
1910	 *
1911	 * @returns {string}
1912	 */
1913	String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g, '');};
1914
1915
1916	/**
1917	 * Удаляет лишние пробелы из конца и начала строки
1918	 *
1919	 * @param string
1920	 * @return {string}
1921	 */
1922	wdpro.trim = string => string.replace(/^\s+|\s+$/g, '');
1923
1924
1925	/**
1926	 * Удаляет лишние пробелы из всех мест строки
1927	 *
1928	 * @returns {string}
1929	 */
1930	String.prototype.fulltrim=function(){return this.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g,'').replace(/\s+/g,' ');};
1931
1932
1933	/**
1934	 * Удаляет лишние пробелы из всех мест строки
1935	 *
1936	 * @param string
1937	 * @return {string}
1938	 */
1939	wdpro.fulltrim = string => string.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g,'').replace(/\s+/g,' ');
1940
1941
1942	/**
1943	 * Делает первую букву заглавной, остальные прописные
1944	 *
1945	 * @param string
1946	 * @return {string}
1947	 */
1948	wdpro.upercaseFirstLetterSmallOthers = string => string[0].toUpperCase() + string.slice(1).toLowerCase();
1949
1950
1951	/**
1952	 * Заменяет текстовые ссылки на гипперссылки
1953	 *
1954	 * @param inputText {string} Текст с текстовыми ссылками
1955	 * @returns {string} Текст с гиперссылками
1956	 */
1957	wdpro.findAndReplaceLink = function (inputText) {
1958		function indexOf(arr, value, from) {
1959			for (var i = from || 0, l = (arr || []).length; i < l; i++) {
1960				if (arr[i] == value) return i;
1961			}
1962			return -1;
1963		}
1964
1965		function clean(str) {
1966			return str ? str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;') : '';
1967		}
1968
1969		function replaceEntities(str) {
1970			return se('<textarea>' + ((str || '').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;')) + '</textarea>').value;
1971		}
1972		function se(html) {return ce('div', {innerHTML: html}).firstChild;}
1973		function ce(tagName, attr, style) {
1974			var el = document.createElement(tagName);
1975			if (attr) extend(el, attr);
1976			if (style) setStyle(el, style);
1977			return el;
1978		}
1979		function setStyle(elem, name, value){
1980			elem = ge(elem);
1981			if (!elem) return;
1982			if (typeof name == 'object') return each(name, function(k, v) { setStyle(elem,k,v); });
1983			if (name == 'opacity') {
1984				if (browser.msie) {
1985					if ((value + '').length) {
1986						if (value !== 1) {
1987							elem.style.filter = 'alpha(opacity=' + value * 100 + ')';
1988						} else {
1989							elem.style.filter = '';
1990						}
1991					} else {
1992						elem.style.cssText = elem.style.cssText.replace(/filter\s*:[^;]*/gi, '');
1993					}
1994					elem.style.zoom = 1;
1995				};
1996				elem.style.opacity = value;
1997			} else {
1998				try{
1999					var isN = typeof(value) == 'number';
2000					if (isN && (/height|width/i).test(name)) value = Math.abs(value);
2001					elem.style[name] = isN && !(/z-?index|font-?weight|opacity|zoom|line-?height/i).test(name) ? value + 'px' : value;
2002				} catch(e){debugLog('setStyle error: ', [name, value], e);}
2003			}
2004		}
2005		function extend() {
2006			var a = arguments, target = a[0] || {}, i = 1, l = a.length, deep = false, options;
2007
2008			if (typeof target === 'boolean') {
2009				deep = target;
2010				target = a[1] || {};
2011				i = 2;
2012			}
2013
2014			if (typeof target !== 'object' && !isFunction(target)) target = {};
2015
2016			for (; i < l; ++i) {
2017				if ((options = a[i]) != null) {
2018					for (var name in options) {
2019						var src = target[name], copy = options[name];
2020
2021						if (target === copy) continue;
2022
2023						if (deep && copy && typeof copy === 'object' && !copy.nodeType) {
2024							target[name] = extend(deep, src || (copy.length != null ? [] : {}), copy);
2025						} else if (copy !== undefined) {
2026							target[name] = copy;
2027						}
2028					}
2029				}
2030			}
2031
2032			return target;
2033		}
2034
2035		var replacedText = (inputText || '').replace(/(^|[^A-Za-z0-9А-Яа-яёЁ\-\_])(https?:\/\/)?((?:[A-Za-z\$0-9А-Яа-яёЁ](?:[A-Za-z\$0-9\-\_А-Яа-яёЁ]*[A-Za-z\$0-9А-Яа-яёЁ])?\.?){1,5}[A-Za-z\$рфуконлайнстРФУКОНЛАЙНСТ\-\d]{2,22}(?::\d{2,5})?)((?:\/(?:(?:\&amp;|\&#33;|,[_%]|[A-Za-z0-9А-Яа-яёЁ\-\_#%?+\/\$.~=;:]+|\[[A-Za-z0-9А-Яа-яёЁ\-\_#%?+\/\$.,~=;:]*\]|\([A-Za-z0-9А-Яа-яёЁ\-\_#%?+\/\$.,~=;:]*\))*(?:,[_%]|[A-Za-z0-9А-Яа-яёЁ\-\_#%?+\/\$.~=;:]*[A-Za-z0-9А-Яа-яёЁ\_#%?+\/\$~=]|\[[A-Za-z0-9А-Яа-яёЁ\-\_#%?+\/\$.,~=;:]*\]|\([A-Za-z0-9А-Яа-яёЁ\-\_#%?+\/\$.,~=;:]*\)))?)?)/ig,
2036			function () { // copied to notifier.js:3401
2037				var matches = Array.prototype.slice.apply(arguments),
2038					prefix = matches[1] || '',
2039					protocol = matches[2] || 'http://',
2040					domain = matches[3] || '',
2041					url = domain + (matches[4] || ''),
2042					full = (matches[2] || '') + matches[3] + matches[4];
2043
2044				domain = domain.replace(/(:\d+)/, '');
2045
2046				if ((domain.indexOf('.') == -1 && domain != 'localhost') || domain.indexOf('..') != -1) return matches[0];
2047				var topDomain = domain.split('.').pop();
2048				if (topDomain.length > 6 || indexOf('info,name,aero,arpa,coop,museum,mobi,travel,xxx,asia,biz,com,net,org,gov,mil,edu,int,tel,ac,ad,ae,af,ag,ai,al,am,an,ao,aq,ar,as,at,au,aw,ax,az,ba,bb,bd,be,bf,bg,bh,bi,bj,bm,bn,bo,br,bs,bt,bv,bw,by,bz,ca,cc,cd,cf,cg,ch,ci,ck,cl,cm,cn,co,cr,cu,cv,cx,cy,cz,de,dj,dk,dm,do,dz,ec,ee,eg,eh,er,es,et,eu,fi,fj,fk,fm,fo,fr,ga,gd,ge,gf,gg,gh,gi,gl,gm,gn,gp,gq,gr,gs,gt,gu,gw,gy,hk,hm,hn,hr,ht,hu,id,ie,il,im,in,io,iq,ir,is,it,je,jm,jo,jp,ke,kg,kh,ki,km,kn,kp,kr,kw,ky,kz,la,lb,lc,li,lk,lr,ls,lt,lu,lv,ly,ma,mc,md,me,mg,mh,mk,ml,mm,mn,mo,mp,mq,mr,ms,mt,mu,mv,mw,mx,my,mz,na,nc,ne,nf,ng,ni,nl,no,np,nr,nu,nz,om,pa,pe,pf,pg,ph,pk,pl,pm,pn,pr,ps,pt,pw,py,qa,re,ro,ru,rs,rw,sa,sb,sc,sd,se,sg,sh,si,sj,sk,sl,sm,sn,so,sr,ss,st,su,sv,sx,sy,sz,tc,td,tf,tg,th,tj,tk,tl,tm,tn,to,tp,tr,tt,tv,tw,tz,ua,ug,uk,um,us,uy,uz,va,vc,ve,vg,vi,vn,vu,wf,ws,ye,yt,yu,za,zm,zw,рф,укр,сайт,онлайн,срб,cat,pro,local,localhost'.split(','), topDomain) == -1) {
2049					if (!/^[a-zA-Z]+$/.test(topDomain) || !matches[2]) {
2050						return matches[0];
2051					}
2052				}
2053
2054
2055				if (matches[0].indexOf('@') != -1) {
2056					return matches[0];
2057				}
2058				try {
2059					full = decodeURIComponent(full);
2060				} catch (e){}
2061
2062				if (full.length > 55) {
2063					full = full.substr(0, 53) + '..';
2064				}
2065				full = clean(full).replace(/&amp;/g, '&');
2066
2067				url = replaceEntities(url).replace(/([^a-zA-Z0-9#%;_\-.\/?&=\[\]:])/g, encodeURIComponent);
2068				var tryUrl = url, hashPos = url.indexOf('#/');
2069				if (hashPos >= 0) {
2070					tryUrl = url.substr(hashPos + 1);
2071				} else {
2072					hashPos = url.indexOf('#!');
2073					if (hashPos >= 0) {
2074						tryUrl = '/' + url.substr(hashPos + 2).replace(/^\//, '');
2075					}
2076				}
2077				return prefix + '<a href="'+ (protocol + url).replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;') + '" target="_blank">' + full + '</a>';
2078			});
2079
2080		return replacedText;
2081	};
2082
2083
2084	/**
2085	 * Возвращает размеры квадрата, пропорционально уменьшив их до заданной ширины
2086	 *
2087	 * @param size {{ width: number, height: number }} Размеры
2088	 * @param newWidth {number} Заданная ширина
2089	 * @returns {{ width: number, height: number }}
2090	 */
2091	wdpro.sizeToWidth = function (size, newWidth) {
2092
2093		var w = size.width;
2094		var h = size.height;
2095
2096		var n = newWidth / w;
2097
2098		if (n < 1)
2099		{
2100			var W = Math.round(w * n);
2101			var H = Math.round(h * n);
2102
2103			size.width = W;
2104			size.height = H;
2105		}
2106
2107		return size;
2108	}
2109
2110
2111	/**
2112	 * Перебор элементов
2113	 *
2114	 * Отличается от $.each тем, что в первом аргументе каллбэк получает jQuery
2115	 * объект, а торым индекс i
2116	 *
2117	 * При этом jQuery объект готов к использованию не только по конструкции
2118	 * $(object).doSomething(),
2119	 * но и напрямую object.doSomething()
2120	 *
2121	 * @param callback {function}
2122	 */
2123	jQuery.fn.wdpro_each = function (callback)
2124	{
2125		$(this).each(function (i)
2126		{
2127			callback($(this), i);
2128		});
2129
2130		return this;
2131	};
2132
2133
2134	/**
2135	 * Преобразует строку запроса вида "?a=A&b=B" в объект { a: "A", b: "B" }
2136	 *
2137	 * @param [queryString] {string} Строка запроса (по-умолчанию querystring в адресе
2138	 * браузера)
2139	 * @returns {{}}
2140	 */
2141	wdpro.parseQueryString = function (queryString)
2142	{
2143		queryString = queryString || window.location.search;
2144
2145		var result = {}, queryString = queryString.slice(1),
2146			re = /([^&=]+)=([^&]*)/g, m;
2147
2148		while (m = re.exec(queryString)) {
2149			result[decodeURIComponent(m[1])] = decodeURIComponent(m[2]);
2150		}
2151
2152		return result;
2153	};
2154
2155
2156	/**
2157	 * Преобразует строку для вставки в QUERY_STRING
2158	 *
2159	 * @param string {string} Строка, которую надо преобразовать
2160	 * @return {string}
2161	 */
2162	wdpro.urlencode = function (string) {
2163
2164		return encodeURI(string);
2165
2166		return encodeURIComponent(string).
2167		// Note that although RFC3986 reserves "!", RFC5987 does not,
2168		// so we do not need to escape it
2169		replace(/['()]/g, escape). // i.e., %27 %28 %29
2170		replace(/\*/g, '%2A').
2171		// The following are not required for percent-encoding per RFC5987,
2172		// so we can allow for a little better readability over the wire: |`^
2173		replace(/%(?:7C|60|5E)/g, unescape);
2174	};
2175
2176
2177	/**
2178	 * Возвращает данные формы в виде объекта
2179	 *
2180	 * @returns {{}}
2181	 */
2182	$.fn.serializeObject = function(){
2183
2184		var self = this,
2185			json = {},
2186			push_counters = {},
2187			patterns = {
2188				"validate": /^[a-zA-Z][a-zA-Z0-9_]*(?:\[(?:\d*|[a-zA-Z0-9_]+)\])*$/,
2189				"key":      /[a-zA-Z0-9_]+|(?=\[\])/g,
2190				"push":     /^$/,
2191				"fixed":    /^\d+$/,
2192				"named":    /^[a-zA-Z0-9_]+$/
2193			};
2194
2195
2196		this.build = function(base, key, value){
2197			base[key] = value;
2198			return base;
2199		};
2200
2201		this.push_counter = function(key){
2202			if(push_counters[key] === undefined){
2203				push_counters[key] = 0;
2204			}
2205			return push_counters[key]++;
2206		};
2207
2208		$.each($(this).serializeArray(), function(){
2209
2210			// skip invalid keys
2211			if(!patterns.validate.test(this.name)){
2212				return;
2213			}
2214
2215			var k,
2216				keys = this.name.match(patterns.key),
2217				merge = this.value,
2218				reverse_key = this.name;
2219
2220			while((k = keys.pop()) !== undefined){
2221
2222				// adjust reverse_key
2223				reverse_key = reverse_key.replace(new RegExp("\\[" + k + "\\]$"), '');
2224
2225				// push
2226				if(k.match(patterns.push)){
2227					merge = self.build([], self.push_counter(reverse_key), merge);
2228				}
2229
2230				// fixed
2231				else if(false && k.match(patterns.fixed)){
2232					merge = self.build([], k, merge);
2233				}
2234
2235				// named
2236				else if(k.match(patterns.named) || k.match(patterns.fixed)){
2237					//console.log(merge);
2238					merge = self.build({}, k, merge);
2239				}
2240			}
2241
2242			json = $.extend(true, json, merge);
2243		});
2244
2245		return json;
2246	};
2247
2248
2249	/**
2250	* Обновляет страницу
2251	*/
2252	wdpro.reloadPage = function () {
2253		window.location = window.location;
2254		console.log('window.location', window.location);
2255	};
2256
2257
2258	/**
2259	 * Возвращает адрес для ajax запроса
2260	 *
2261	 * @param  {{}|string} params Данные запроса или имя запроса
2262	 *
2263	 * @return {string}
2264	 */
2265	wdpro.ajaxUrl = function (params) {
2266
2267		if (typeof params === 'string') params = { 'action': params };
2268
2269		var query = wdpro.extend(params, {
2270				'action': 'wdpro',
2271				'wdproAction': params['action'],
2272				'lang': wdpro.data.lang,
2273				'get': wdpro.data.get
2274			});
2275
2276			return this.data['ajaxUrl']+'?'+ $.param(query);
2277	};
2278
2279
2280
2281	/**
2282	 * Выполняет ajax запрос
2283	 *
2284	 * @param $_GET_OR_ACTION {{}|string} Имя события, по которой модуль поймает этот запрос
2285	 * или QUERY_STRING - то, что в php будет доступно в массиве $_GET
2286	 * @param [$_POST] {{}} Данные - то, что в php будет доступно в массиве $_POST
2287	 * @param [callback] {function} каллбэк, принимающий результат запроса
2288	 */
2289	wdpro.ajax = function ($_GET_OR_ACTION, $_POST, callback) {
2290
2291		var self = this;
2292		var url;
2293
2294		// Когда указан конкретный адрес
2295		if (typeof $_GET_OR_ACTION === 'string'
2296			&& ($_GET_OR_ACTION.match(/^\/\//)
2297				|| $_GET_OR_ACTION.match(/^http:\/\//)
2298				|| $_GET_OR_ACTION.match(/^https:\/\//))
2299			) {
2300			url = $_GET_OR_ACTION;
2301		}
2302
2303		// Когда указаны действия или еще чего
2304		else {
2305			if (typeof $_GET_OR_ACTION === 'string')
2306			{
2307				$_GET_OR_ACTION = {
2308					'action': $_GET_OR_ACTION
2309				};
2310			}
2311
2312			var $_GET2 = wdpro.extend($_GET_OR_ACTION, {
2313				'action': 'wdpro',
2314				'wdproAction': $_GET_OR_ACTION['action'],
2315				'lang': wdpro.data['lang'],
2316				'currentPostId': wdpro.data['currentPostId'],
2317				'currentPostName': wdpro.data['currentPostName'],
2318			});
2319
2320			url = this.data['ajaxUrl']+'?'+ $.param($_GET2);
2321		}
2322
2323		if (typeof $_POST === 'function')
2324		{
2325			callback = $_POST;
2326			$_POST = null;
2327		}
2328
2329
2330    $_POST = $_POST || {}
2331    $_POST['person_post_token'] = wdpro.person_post_token
2332
2333		var params = {
2334			'url': url,
2335			'type': 'POST',
2336			'data': $_POST,
2337			'success': function (json, textStatus, request) {
2338
2339				// console.log('json', json)
2340				// console.log('textStatus', textStatus)
2341
2342				// console.log('headers', request.getAllResponseHeaders())
2343
2344				var data = self.parseJSON(json);
2345
2346				if (typeof data !== 'string') {
2347					data = $.extend({}, data);
2348				}
2349
2350
2351				wdpro.trigger('ajaxData', data);
2352
2353				if (data['reloadPage']) {
2354					wdpro.reloadPage();
2355				}
2356
2357				if (callback)
2358				{
2359					callback(data);
2360				}
2361			},
2362			'error': function (error) {
2363				console.log(error['responseText'])
2364        callback({error})
2365			}
2366		};
2367
2368		$.ajax(params);
2369	};
2370
2371
2372	wdpro.alertAjaxError = res => {
2373		if (res.error) {
2374			alert(res.error);
2375			return true;
2376		}
2377	};
2378
2379
2380	/**
2381	 * Prepare string for use in regular expression
2382	 * 
2383	 * https://javascript.ru/php/preg_quote
2384	 * 
2385	 * @param {string} str 
2386	 */
2387	wdpro.preg_quote = str => {	// Quote regular expression characters
2388		// 
2389		// +   original by: booeyOH
2390		// +   improved by: Ates Goral (http://magnetiq.com)
2391
2392		return str.replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1");
2393	}
2394
2395
2396
2397	// Глобальные события
2398	var wdproGlobalEvents = new wdpro.Event();
2399
2400	/**
2401	 * Установка прослушки на событие
2402	 *
2403	 * @param actionNames {string} Имя (имена) события с пространством имен в формате name.namespace
2404	 * @param callback {function} Каллбэк, принимающий событие
2405	 * @param [params] {{ listenPrev: boolean }} Параметры
2406	 */
2407	wdpro.on = function (eventName, callback, params) {
2408		wdproGlobalEvents.on(eventName, callback, params);
2409	};
2410
2411
2412	/**
2413	 * Return current lang code or empty string when it's core lang
2414	 *
2415	 * @return {string}
2416	 */
2417	wdpro.lang = () => wdpro.data['lang'];
2418
2419
2420	/**
2421	 * Return current lang code
2422	 *
2423	 * @return {[type]} [description]
2424	 */
2425	wdpro.langNotEmpty = () => wdpro.data['langNotEmpty'];
2426
2427
2428	/**
2429	 * Return value of current lang from object
2430	 *
2431	 * @param  {{}} texts {{ en: '...', ru: '...' }}
2432	 * @return {string|*}
2433	 */
2434	wdpro.langTranslate = wdpro.translate = texts => {
2435		if (texts[wdpro.langNotEmpty()]) {
2436			return texts[wdpro.langNotEmpty()];
2437		}
2438
2439		if (texts[wdpro.lang()]) {
2440			return texts[wdpro.lang()];
2441		}
2442
2443		if (wdpro.langsDefaults) {
2444			for (let lang of wdpro.langsDefaults) {
2445				if (texts[lang]) {
2446					return texts[lang];
2447				}
2448			}
2449		}
2450
2451		return texts[''];
2452	};
2453	wdpro.langText = wdpro.langTranslate;
2454
2455	/**
2456	 * Отправка событие во все прослушки этого события
2457	 *
2458	 * @param actionName {string} Имя события без пространства имен
2459	 * @param [data] {{}} Данные, отправляемые в прослушки события
2460	 * @returns {number} Количество запущенных прослушек в данный момент
2461	 */
2462	wdpro.trigger = function (eventname, data) {
2463		wdproGlobalEvents.trigger(eventname, data);
2464	};
2465
2466
2467	/**
2468	 * Возвращает корневой url сайта (главная страница)
2469	 *
2470	 * @returns {string}
2471	 */
2472	wdpro.homeUrl = function () {
2473		return wdpro.data['homeUrl'];
2474	};
2475
2476
2477	/**
2478	 * Возвращает адрес папки, в которую загружаются рисунки
2479	 *
2480	 * @returns {string}
2481	 */
2482	wdpro.imagesUrl = function () {
2483		return wdpro.data['imagesUrl'];
2484	};
2485
2486
2487	/**
2488	 * Возвращает данные из слоя, в котором содержиться json строка
2489	 *
2490	 * @returns {{}|[]|null}
2491	 */
2492	$.fn.wdproGetJsonData = function() {
2493
2494		var json = $(this).text();
2495		return wdpro.parseJSON(json);
2496	};
2497
2498
2499	/**
2500	 * Проверяет, телефон ли это устройство
2501	 *
2502	 * @returns {boolean}
2503	 */
2504	wdpro.isMobile = function () {
2505		var check = false;
2506		(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
2507		return check;
2508	};
2509
2510
2511	/**
2512	 * Проверяет, что это либо телефон, либо планшет
2513	 *
2514	 * @returns {boolean}
2515	 */
2516	wdpro.isMobileOrTablet = function () {
2517		var check = false;
2518		(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4)))check = true})(navigator.userAgent||navigator.vendor||window.opera);
2519		return check;
2520	};
2521
2522
2523	wdpro.isMacOs = () => window.navigator.platform.toUpperCase().indexOf('MAC') >= 0;
2524
2525
2526	/**
2527	 * Отключение нажатия на что-либо
2528	 *
2529	 * @returns {jQuery}
2530	 */
2531	jQuery.fn.wdpro_del_down = function () {
2532		$(this).attr("onmousedown", "return false;
2532").mousedown(function () {
2533			return false;
2534		});
2535
2536		return this;
2537	};
2538
2539
2540	/**
2541	 * Удаляет теги
2542	 *
2543	 * @param html
2544	 */
2545	wdpro.stripTags = function (html) {
2546		return html.replace(/<\/?[^>]+>/gi, '');
2547	};
2548
2549	/**
2550	 * Декодирует html символы обратно в html код
2551	 * @param string
2552	 * @param quoteStyle
2553	 * @return {*}
2554	 */
2555	wdpro.htmlspecialcharsDecode = function (string, quoteStyle)
2556	{
2557		// eslint-disable-line camelcase
2558		//       discuss at: http://locutus.io/php/htmlspecialchars_decode/
2559		//      original by: Mirek Slugen
2560		//      improved by: Kevin van Zonneveld (http://kvz.io)
2561		//      bugfixed by: Mateusz "loonquawl" Zalega
2562		//      bugfixed by: Onno Marsman (https://twitter.com/onnomarsman)
2563		//      bugfixed by: Brett Zamir (http://brett-zamir.me)
2564		//      bugfixed by: Brett Zamir (http://brett-zamir.me)
2565		//         input by: ReverseSyntax
2566		//         input by: Slawomir Kaniecki
2567		//         input by: Scott Cariss
2568		//         input by: Francois
2569		//         input by: Ratheous
2570		//         input by: Mailfaker (http://www.weedem.fr/)
2571		//       revised by: Kevin van Zonneveld (http://kvz.io)
2572		// reimplemented by: Brett Zamir (http://brett-zamir.me)
2573		//        example 1: htmlspecialchars_decode("<p>this -&gt; &quot;</p>", 'ENT_NOQUOTES')
2574		//        returns 1: '<p>this -> &quot;</p>'
2575		//        example 2: htmlspecialchars_decode("&amp;quot;")
2576		//        returns 2: '&quot;'
2577		var optTemp = 0
2578		var i = 0
2579		var noquotes = false
2580		if (typeof quoteStyle === 'undefined') {
2581			quoteStyle = 2
2582		}
2583		string = string.toString()
2584			.replace(/&lt;/g, '<')
2585			.replace(/&gt;/g, '>')
2586		var OPTS = {
2587			'ENT_NOQUOTES': 0,
2588			'ENT_HTML_QUOTE_SINGLE': 1,
2589			'ENT_HTML_QUOTE_DOUBLE': 2,
2590			'ENT_COMPAT': 2,
2591			'ENT_QUOTES': 3,
2592			'ENT_IGNORE': 4
2593		}
2594		if (quoteStyle === 0) {
2595			noquotes = true
2596		}
2597		if (typeof quoteStyle !== 'number') {
2598			// Allow for a single string or an array of string flags
2599			quoteStyle = [].concat(quoteStyle)
2600			for (i = 0; i < quoteStyle.length; i++) {
2601				// Resolve string input to bitwise e.g. 'PATHINFO_EXTENSION' becomes 4
2602				if (OPTS[quoteStyle[i]] === 0) {
2603					noquotes = true
2604				} else if (OPTS[quoteStyle[i]]) {
2605					optTemp = optTemp | OPTS[quoteStyle[i]]
2606				}
2607			}
2608			quoteStyle = optTemp
2609		}
2610		if (quoteStyle & OPTS.ENT_HTML_QUOTE_SINGLE) {
2611			// PHP doesn't currently escape if more than one 0, but it should:
2612			string = string.replace(/&#0*39;/g, "'")
2613			// This would also be useful here, but not a part of PHP:
2614			// string = string.replace(/&apos;|&#x0*27;/g, "'");
2615		}
2616		if (!noquotes) {
2617			string = string.replace(/&quot;/g, '"')
2618		}
2619		// Put this in last place to avoid escape being double-decoded
2620		string = string.replace(/&amp;/g, '&')
2621		return string
2622	};
2623
2624
2625	/**
2626	 * Преобразует текстовый ключ в объект, чтобы его было проще дальше использовать
2627	 *
2628	 * @param key {string|{}} Ключ
2629	 * @returns {{key: string, object: {}}}
2630	 */
2631	wdpro.keyParse = function (key) {
2632
2633		// Когда ключ массив
2634		if (typeof key === 'object') {
2635			if (key['allReady']) {
2636				return key;
2637			}
2638
2639			var infoArr = key;
2640			var infoString = '';
2641
2642			$.each(key, function (i, value) {
2643				if (infoString != '')
2644				{
2645					infoString += ',';
2646				}
2647				infoString += i + ':' + value;
2648			});
2649
2650
2651		}
2652
2653
2654		// Ключь в виде строки
2655		else if (typeof key === 'string') {
2656			// Создаем строку ключа
2657			var infoString = key;
2658
2659			var infoArr = {};
2660
2661			var elements = infoString.split(',');
2662
2663			wdpro.each(elements, function (element) {
2664				var elementParts = element.split(':');
2665				infoArr[elementParts[0]] = elementParts[1];
2666			});
2667		}
2668
2669		if (infoString) {
2670			return {
2671				'key': infoString,
2672				'object': infoArr,
2673				'allReady': true
2674			};
2675		}
2676	};
2677
2678
2679	/**
2680	 * Добавляет в ключ объекта данные
2681	 *
2682	 * @param originalKey {string|{}}Оригинальный ключ
2683	 * @param newData {{}} Новые данные
2684	 * @returns {{key: string, object: {}}}
2685	 */
2686	wdpro.keyUpdate = function (originalKey, newData) {
2687		var key = wdpro.keyParse(originalKey);
2688
2689		$.each(newData, function (newKey, newValue) {
2690			key['object'][newKey] = newValue;
2691		});
2692
2693		return wdpro.keyParse(key['object']);
2694	};
2695
2696
2697	/**
2698	 * Обновляет search (query string) троку
2699	 *
2700	 * @param search {string} Строка
2701	 * @param values {{}} Новые данные query string
2702	 */
2703	wdpro.queryStringUpdate = function (search, values) {
2704
2705		var search = $.parseParams(search);
2706
2707		$.each(values, function (key, value) {
2708			console.log('search', key, value);
2709
2710			search[key] = value;
2711		});
2712
2713		return $.param(search);
2714	};
2715
2716
2717	/**
2718	 * Возвращает данные query string в виде объекта
2719	 *
2720	 * @returns {{}}
2721	 */
2722	wdpro.getQueryStringObject = function (queryString) {
2723		if (!queryString) {
2724			queryString = window.location.search;
2725		}
2726		return wdpro.parseParams(queryString);
2727	};
2728
2729
2730	/**
2731	 * Заменяет в адресе search строку на новую
2732	 *
2733	 * @param url {string} Url
2734	 * @param search {string|{}} Query String
2735	 */
2736	wdpro.replaceQueryStringInUrl = function (url, search) {
2737		var urlData = wdpro.parseUrl(url);
2738
2739		if (typeof search === 'object') {
2740			search = $.param(search);
2741		}
2742
2743		var url = urlData.protocol + '//' +
2744			urlData.hostname +
2745			urlData.pathname +
2746			'?' + search;
2747
2748		return url;
2749	};
2750
2751
2752	/**
2753	 * Обновляет Search строку в адресе
2754	 *
2755	 * @param url {string} Адрес
2756	 * @param newSearchData {{}} Новые дынне в search строке
2757	 * @returns {string}
2758	 */
2759	wdpro.updateQueryStringInUrl = function (url, newSearchData) {
2760		var urlData = wdpro.parseUrl(url);
2761		var search = wdpro.queryStringUpdate(urlData.search, newSearchData);
2762
2763		var returnUrl = urlData.protocol + '//' +
2764			urlData.hostname
2765
2766    if (urlData.port) {
2767      returnUrl += ':'+urlData.port
2768    }
2769
2770    returnUrl +=
2771      urlData.pathname +
2772      '?' + search
2773
2774		return returnUrl;
2775	};
2776
2777
2778	/**
2779	 * Возвращает данные QueryString из адреса в виде объекта
2780	 *
2781	 * @param url {string} Url адрес
2782	 * @returns {{}}
2783	 */
2784	wdpro.getQueryStringDataFromUrl = function (url) {
2785		var urlData = wdpro.parseUrl(url);
2786
2787		return $.parseParams(urlData.search);
2788	};
2789
2790
2791	/**
2792	 * Меняет адрес в браузере без перезагрузки страницы
2793	 *
2794	 * @param url {string} Url
2795	 */
2796	wdpro.changeUrlWithoutReloadPage = function (url) {
2797		if (window.history.replaceState) {
2798			window.history.replaceState(null, null, url);
2799		}
2800	};
2801
2802
2803	/**
2804	 *
2805	 * @param newQueryData
2806	 */
2807	wdpro.updateQueryString = function (newQueryData) {
2808		var queryStringData = wdpro.getQueryStringObject();
2809
2810		wdpro.each(newQueryData, function (value, key) {
2811			if (value === null) {
2812				delete queryStringData[key];
2813			}
2814			else {
2815				queryStringData[key] = value;
2816			}
2817		});
2818
2819		console.log('window.location.pathname', window.location.pathname);
2820
2821		var queryString = $.param(queryStringData);
2822		var url = window.location.protocol
2823			+ '//' + window.location.host
2824			+ window.location.pathname;
2825
2826		if (queryString) {
2827			url += '?'+queryString;
2828		}
2829
2830		wdpro.changeUrlWithoutReloadPage(url);
2831	};
2832
2833
2834	/**
2835	 * Преобразует url в массив данных
2836	 *
2837	 * @param url {string} Url
2838	 * @returns {HTMLAnchorElement}
2839	 */
2840	wdpro.parseUrl = function (url) {
2841		if (!url) url = window.location;
2842
2843		var l = document.createElement("a");
2844		l.href = url;
2845		return l;
2846	};
2847
2848
2849	/**
2850	 * Преобразует url из массива в строку
2851	 *
2852	 * @param parsedUrl {HTMLAnchorElement|{}}
2853	 * @return string
2854	 */
2855	wdpro.unparseUrl = function(parsedUrl) {
2856		return parsedUrl.protocol+"//"
2857		+parsedUrl.host
2858		+parsedUrl.pathname
2859		+parsedUrl.search
2860		+parsedUrl.hash;
2861	};
2862
2863
2864	/**
2865	 * Преобразует относительный адрес в абсолютный
2866	 *
2867	 * @param relativeUrl {string} Относительный адрес
2868	 * @return {string}
2869	 */
2870	wdpro.relativeUrlToAbsolute = relativeUrl =>
2871		wdpro.unparseUrl( wdpro.parseUrl(relativeUrl) );
2872
2873	
2874	wdpro.debounce = (func, wait) => {
2875		let timeout;
2876
2877		return function executedFunction(...args) {
2878			const later = () => {
2879				clearTimeout(timeout);
2880				func(...args);
2881			};
2882
2883			clearTimeout(timeout);
2884			timeout = setTimeout(later, wait);
2885		};
2886	};
2887
2888
2889	wdpro.copyTextFromElement = $element => {
2890		const element = $element.get ? $element.get(0) : $element;
2891
2892		str = element.innerHTML;
2893		const el = document.createElement('textarea');
2894		el.value = str;
2895		document.body.appendChild(el);
2896		el.select();
2897		document.execCommand('copy');
2898		document.body.removeChild(el);
2899	};
2900
2901
2902	// https://stackoverflow.com/a/45184412/6694099
2903	wdpro.toNonExponential = value => {
2904		// if value is not a number try to convert it to number
2905		if (typeof value !== "number") {
2906			value = parseFloat(value);
2907
2908			// after convert, if value is not a number return empty string
2909			if (isNaN(value)) {
2910				return "";
2911			}
2912		}
2913
2914		var sign;
2915		var e;
2916
2917		// if value is negative, save "-" in sign variable and calculate the absolute value
2918		if (value < 0) {
2919			sign = "-";
2920			value = Math.abs(value);
2921		}
2922		else {
2923			sign = "";
2924		}
2925
2926		// if value is between 0 and 1
2927		if (value < 1.0) {
2928			// get e value
2929			e = parseInt(value.toString().split('e-')[1]);
2930
2931			// if value is exponential convert it to non exponential
2932			if (e) {
2933				value *= Math.pow(10, e - 1);
2934				value = '0.' + (new Array(e)).join('0') + value.toString().substring(2);
2935			}
2936		}
2937		else {
2938			// get e value
2939			e = parseInt(value.toString().split('e+')[1]);
2940
2941			// if value is exponential convert it to non exponential
2942			if (e) {
2943				value /= Math.pow(10, e);
2944				value += (new Array(e + 1)).join('0');
2945			}
2946		}
2947
2948		// if value has negative sign, add to it
2949		return sign + value;
2950	}
2951
2952
2953	let originalPlaceId = 0;
2954
2955	/**
2956	 * Добавляет элемент в конец целевого контейнера, сохраняя возможность вернуть элемент обратно на свое место
2957	 *
2958	 * Чтобы вернуть элемент обратно, используйте $(element).toOriginalPlace();
2959	 * @param $to {jQuery} Целевой контейнер
2960	 * @return {jQuery}
2961	 */
2962	$.fn.appendFromOriginalPlaceTo = function ($to) {
2963		$(this).each(function () {
2964
2965			const element = $(this);
2966			saveOriginalPlace(element);
2967
2968
2969			element.appendTo($to);
2970		});
2971
2972		return this;
2973	};
2974
2975	/**
2976	 * Добавляет элемент в начало целевого контейнера, сохраняя возможность вернуть элемент обратно на свое место
2977	 *
2978	 * Чтобы вернуть элемент обратно, используйте $(element).toOriginalPlace();
2979	 * @param $to {jQuery} Целевой контейнер
2980	 * @return {jQuery}
2981	 */
2982	$.fn.prependFromOriginalPlaceTo = function ($to) {
2983		$(this).each(function () {
2984
2985			const element = $(this);
2986			saveOriginalPlace(element);
2987
2988
2989			element.appendTo($to);
2990		});
2991
2992		return this;
2993	};
2994
2995
2996	/**
2997	 * Возвращает элемент на место после таких перемещения типа $(element).appendFromOriginalPlaceTo
2998	 *
2999	 * @return {jQuery}
3000	 */
3001	$.fn.toOriginalPlace = function () {
3002		$(this).each(function () {
3003
3004			const element = $(this);
3005			const id = element.data('original-place-id');
3006			const originalPlace = $('#js-original-place-'+id);
3007			originalPlace.after(element);
3008		});
3009
3010		return this;
3011	};
3012
3013
3014	/**
3015	 * Сохраняет позицию элемента, чтобы его можно было вернуть потом сюда же
3016	 *
3017	 * @param element {jQuery}
3018	 */
3019	const saveOriginalPlace = (element) => {
3020
3021		if (!element.data('original-place-id')) {
3022			originalPlaceId ++;
3023
3024			const placeHolder = $('<div id="js-original-place-'+originalPlaceId+'"></div>').hide();
3025			element.data('original-place-id', originalPlaceId);
3026
3027			element.after(placeHolder);
3028		}
3029	};
3030
3031
3032	/**
3033	 * Перераспределяет элементы по контейнерам в зависимости от ширины страницы
3034	 *
3035	 * @example
3036	 * <code>
3037	 * // Адаптивная перестановка элементов
3038	 * wdpro.adaptiveStructure({
3039	 *
3040	 * 	996: {
3041	 *
3042	 * 		// Шапка
3043	 * 		'#js-mobile-header': [
3044	 * 			'*', // Любой элемент, который уже есть в контейнере
3045	 * 			'#js-header-cart',
3046	 * 			'#js-callback-button'
3047	 * 		],
3048	 *
3049	 * 		// Меню
3050	 * 		'#js-mobile-menu': [
3051	 * 			'#js-search-form',
3052	 * 			'#js-header-phones',
3053	 * 			'#js-header-mail',
3054	 * 			'#js-header-schedule',
3055	 * 			'#js-header-address',
3056	 * 			'#js-aside'
3057	 * 		]
3058	 * 	},
3059	 *
3060	 *
3061	 * 	480: {
3062	 *
3063	 * 		// Шапка
3064	 * 		'#js-mobile-header': [
3065	 * 			'*',
3066	 * 			'#js-header-cart'
3067	 * 		],
3068	 *
3069	 * 		// Меню
3070	 * 		'#js-mobile-menu': [
3071	 * 			'#js-search-form',
3072	 * 			'#js-callback-button',
3073	 * 			'#js-header-phones',
3074	 * 			'#js-header-mail',
3075	 * 			'#js-header-schedule',
3076	 * 			'#js-header-address',
3077	 * 			'#js-aside'
3078	 * 		]
3079	 * 	}
3080	 *
3081	 * });
3082	 * </code>
3083	 *
3084	 * @param params
3085	 */
3086	wdpro.adaptiveStructure = (params) => {
3087
3088		let elements = {};
3089		let lastWidth;
3090
3091
3092		const getElement = (selector) => {
3093			if (!elements[selector]) {
3094				elements[selector] = $(selector);
3095			}
3096			return elements[selector];
3097		};
3098
3099
3100		const rePos = () => {
3101
3102			let fixI = 0;
3103
3104			// Ширина окна
3105			let windowWidth = window.innerWidth;
3106
3107			// Находим текущую ширину параметров
3108			let paramsWidth;
3109			for(let w in params) {
3110
3111				// Ширина окна меньше ширины параметров
3112				if (windowWidth <= w) {
3113					if (!paramsWidth) {
3114						paramsWidth = w;
3115					}
3116
3117					else {
3118						paramsWidth = Math.min(w, paramsWidth);
3119					}
3120				}
3121			}
3122
3123
3124			if (lastWidth === paramsWidth) return false;
3125
3126
3127			// Позиция из параметров
3128			if (paramsWidth) {
3129
3130				// Получаем текущую структуру
3131				let structure = params[paramsWidth];
3132
3133
3134				// Перебираем контейнеры
3135				$.each(structure, (containerSelector, childIds) => {
3136
3137					// Получаем контейнер
3138					let $container = getElement(containerSelector);
3139
3140					// Перебираем элементы
3141					$.each(childIds, function (i, childSelector) {
3142
3143						i -= fixI;
3144
3145						// Если по этому номеру находится другой элемент
3146						let $currentElement = $container.children(':eq('+i+')');
3147
3148						if (!$currentElement.is(childSelector)) {
3149
3150							// Перед этим элементом ставим тот, который указан в параметрах
3151							let $targetElement = getElement(childSelector);
3152
3153							if ($targetElement.length) {
3154
3155								saveOriginalPlace($targetElement);
3156
3157								if (i) {
3158									$container.children(':eq('+(i - 1)+')').after($targetElement);
3159								}
3160
3161								else {
3162									$container.prepend($targetElement);
3163								}
3164							}
3165
3166							else {
3167								fixI ++;
3168							}
3169						}
3170					});
3171				});
3172			}
3173
3174
3175			// Изначальная позиция
3176			else {
3177				$.each(elements, (i, $element) => {
3178					$element.toOriginalPlace();
3179				});
3180			}
3181
3182
3183			lastWidth = paramsWidth;
3184		};
3185
3186
3187		rePos();
3188		$(window).on('resize', rePos);
3189	};
3190
3191
3192	// Каллбэки на определенные размеры экрана
3193	{
3194		const list = [];
3195
3196		const update = () => {
3197			const windowWidth = window.innerWidth;
3198
3199			const inside = (params) => {
3200
3201				if (typeof params.min === 'number' && params.min >= windowWidth)
3202					return false;
3203
3204				if (typeof params.max === 'number' && params.max <= windowWidth)
3205					return false;
3206
3207				return true;
3208			};
3209
3210			for (let item of list) {
3211
3212				// Когда каллбэк уже внутри заданного размера
3213				if (item.inside === null || item.inside) {
3214
3215					// Если каллбэк вышел из размера
3216					if (!inside(item.params)) {
3217						item.off && item.off();
3218						item.inside = false;
3219						break;
3220					}
3221				}
3222
3223				// Когда каллбэк снаружи заданного размера
3224				if (item.inside === null || !item.inside) {
3225
3226					// Если каллбэк вошел в размер
3227					if (inside(item.params)) {
3228						item.on && item.on();
3229						item.inside = true;
3230						break;
3231					}
3232				}
3233			}
3234		};
3235
3236		/**
3237		 * Запускает каллбэки при определенном размере окна
3238		 *
3239		 * @param params {{ [min]:number, [max]:number }}
3240		 * @param callbackOn {function} Запускается, когда размер окна подходит под заданный размер
3241		 * @param callbackOff {function} Запускается, когда размер окна не подходит под заданный размер
3242		 */
3243		wdpro.onWindowSize = (params, callbackOn, callbackOff) => {
3244
3245			list.push({
3246				params: params,
3247				on: callbackOn,
3248				off: callbackOff,
3249				inside: null
3250			});
3251
3252			update();
3253		};
3254
3255
3256		update();
3257		$(window).on('resize', update);
3258	}
3259
3260
3261	/**
3262	 * Добавляет в цену пробелы
3263	 *
3264	 * @return {jQuery} Объект, в котором находится цена
3265	 */
3266	$.fn.wdproCostSpaces = function (space) {
3267
3268		if (typeof space === "undefined")
3269			space = ' ';
3270
3271		$(this).each(function () {
3272
3273			const $container = $(this);
3274			if ($container.is('.js-cost-inited')) return true;
3275			$container.addClass('js-cost-inited');
3276
3277			let cost = $container.text();
3278
3279			if (cost) {
3280				cost = cost.replace(/(\d)(?=(\d\d\d)+([^\d]|$))/g, '$1'+space);
3281				$container.text(cost);
3282			}
3283
3284		});
3285
3286		return this;
3287	};
3288
3289
3290
3291
3292	// Add an URL parser to JQuery that returns an object
3293	// This function is meant to be used with an URL like the window.location
3294	// Use: $.parseParams('http://mysite.com/?var=string') or $.parseParams() to parse the window.location
3295	// Simple variable:  ?var=abc                        returns {var: "abc"}
3296	// Simple object:    ?var.length=2&var.scope=123     returns {var: {length: "2", scope: "123"}}
3297	// Simple array:     ?var[]=0&var[]=9                returns {var: ["0", "9"]}
3298	// Array with index: ?var[0]=0&var[1]=9              returns {var: ["0", "9"]}
3299	// Nested objects:   ?my.var.is.here=5               returns {my: {var: {is: {here: "5"}}}}
3300	// All together:     ?var=a&my.var[]=b&my.cookie=no  returns {var: "a", my: {var: ["b"], cookie: "no"}}
3301	// You just cant have an object in an array, ?var[1].test=abc DOES NOT WORK
3302	(function ($) {
3303		var re = /([^&=]+)=?([^&]*)/g;
3304		var decode = function (str) {
3305			return decodeURIComponent(str.replace(/\+/g, ' '));
3306		};
3307		$.parseParams = wdpro.parseParams = function (query) {
3308			// recursive function to construct the result object
3309			function createElement(params, key, value) {
3310				key = key + '';
3311				// if the key is a property
3312				if (key.indexOf('.') !== -1) {
3313					// extract the first part with the name of the object
3314					var list = key.split('.');
3315					// the rest of the key
3316					var new_key = key.split(/\.(.+)?/)[1];
3317					// create the object if it doesnt exist
3318					if (!params[list[0]]) params[list[0]] = {};
3319					// if the key is not empty, create it in the object
3320					if (new_key !== '') {
3321						createElement(params[list[0]], new_key, value);
3322					} else console.warn('parseParams :: empty property in key "' + key + '"');
3323				} else
3324				// if the key is an array
3325				if (key.indexOf('[') !== -1) {
3326					// extract the array name
3327					var list = key.split('[');
3328					key = list[0];
3329					// extract the index of the array
3330					var list = list[1].split(']');
3331					var index = list[0]
3332
3333
3334					if (!params) params = {};
3335					if (!params[key]) params[key] = {};
3336					params[key][index] = value;
3337				} else
3338				// just normal key
3339				{
3340					if (!params) params = {};
3341					params[key] = value;
3342				}
3343			}
3344			// be sure the query is a string
3345			query = query + '';
3346			if (query === '') query = window.location + '';
3347			var params = {}, e;
3348			if (query) {
3349				// remove # from end of query
3350				if (query.indexOf('#') !== -1) {
3351					query = query.substr(0, query.indexOf('#'));
3352				}
3353
3354				// remove ? at the begining of the query
3355				if (query.indexOf('?') !== -1) {
3356					query = query.substr(query.indexOf('?') + 1, query.length);
3357				} else return {};
3358				// empty parameters
3359				if (query == '') return {};
3360				// execute a createElement on every key and value
3361				while (e = re.exec(query)) {
3362					var key = decode(e[1]);
3363					var value = decode(e[2]);
3364					createElement(params, key, value);
3365				}
3366			}
3367			return params;
3368		};
3369	})(jQuery);
3370
3371}
3371)(jQuery);
3372
3373window.wdpro = wdpro;

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.