PageSourceSearch

https://backpacks.ru/includes/javascript/optimized-shops-map.js?1.0.16

js backpacks.ru collected 2026-10-03 19:22:19 UTC 37,527 bytes, 1,227 lines download raw bytes

1(function($) {
2	'use strict';
3
4	var configuredShopId = window.PROSKTR && window.PROSKTR.shop
5		? window.PROSKTR.shop.id
6		: '';
7	var adapterConstructor = configuredShopId === 'PRO'
8		? window.ProShopsMapAdapter
9		: window.ymapsShopsMapAdatderNew;
10	if ( configuredShopId === 'BBNG' ) {
11		adapterConstructor = window.ProShopsMapAdapter;
12	}
13
14	if (
15		!$
16		|| !$.map
17		|| !$.map.shopsMap
18		|| typeof adapterConstructor !== 'function'
19	) {
20		return;
21	}
22
23	var adapterPrototype = adapterConstructor.prototype;
24
25	function getRequestSignature(values) {
26		values = values || {};
27		var keys = [
28			'city',
29			'zone_id',
30			'country',
31			'shop_id',
32			'pvzResourcesFlag',
33			'price',
34			'weight',
35			'product_group',
36			'noPostamat',
37			'free_shipping',
38			'full_prepay_discount_delivery',
39			'fitting',
40			'typesToFitting',
41			'hideButtons',
42			'dateSendActual'
43		];
44		var signature = {};
45
46		for ( var i = 0; i < keys.length; i++ ) {
47			signature[keys[i]] = typeof values[keys[i]] === 'undefined' ? null : values[keys[i]];
48		}
49		if ( typeof signature.city === 'string' ) {
50			signature.city = signature.city.toLowerCase();
51		}
52		return JSON.stringify(signature);
53	}
54
55	function isCoffeeShop() {
56		return !!(
57			window.PROSKTR
58			&& window.PROSKTR.shop
59			&& ['COFFEE', 'SOVA', 'COAL'].indexOf(window.PROSKTR.shop.id) !== -1
60		);
61	}
62
63	function isIridatoShop() {
64		return !!(
65			window.PROSKTR
66			&& window.PROSKTR.shop
67			&& window.PROSKTR.shop.id === 'IRIDATO'
68		);
69	}
70
71	function isTimeShop() {
72		return !!(
73			window.PROSKTR
74			&& window.PROSKTR.shop
75			&& window.PROSKTR.shop.id === 'TIME'
76		);
77	}
78
79	function isTwoXuShop() {
80		return !!(
81			window.PROSKTR
82			&& window.PROSKTR.shop
83			&& window.PROSKTR.shop.id === '2XU'
84		);
85	}
86
87	function isBackShop() {
88		return !!(
89			window.PROSKTR
90			&& window.PROSKTR.shop
91			&& window.PROSKTR.shop.id === 'BACK'
92		);
93	}
94
95	function isProShop() {
96		return !!(
97			window.PROSKTR
98			&& window.PROSKTR.shop
99			&& window.PROSKTR.shop.id === 'PRO'
100		);
101	}
102
103	function isBillabongShop() {
104		return !!(
105			window.PROSKTR
106			&& window.PROSKTR.shop
107			&& window.PROSKTR.shop.id === 'BBNG'
108		);
109	}
110
111	function usesResilientInitialMapLoading() {
112		if ( isBillabongShop() ) {
113			return true;
114		}
115		return isCoffeeShop()
116			|| isIridatoShop()
117			|| isTimeShop()
118			|| isLoveShop()
119			|| isTwoXuShop()
120			|| isBackShop()
121			|| isProShop();
122	}
123
124	function usesLiveMapOpenerContext() {
125		return usesResilientInitialMapLoading();
126	}
127
128	function ensureBackMapCityOption(widget, city) {
129		if ( !widget || !widget.citySelect || !widget.citySelect.is('select') || typeof city !== 'string' ) {
130			return;
131		}
132		var cityText = $.trim(city);
133		var cityValue = cityText.toLowerCase();
134		if ( !cityValue ) {
135			return;
136		}
137		var option = widget.citySelect.find('option').filter(function() {
138			return String($(this).val() || '').toLowerCase() === cityValue;
139		}).first();
140		if ( !option.length ) {
141			option = $('<option>').val(cityValue).text(cityText).appendTo(widget.citySelect);
142		}
143		widget.citySelect.val(option.val());
144	}
145
146	function isLoveShop() {
147		return !!(
148			window.PROSKTR
149			&& window.PROSKTR.shop
150			&& window.PROSKTR.shop.id === 'LOVE'
151		);
152	}
153
154	function enableLovePointPrefetch(adapter) {
155		var events = adapter
156			&& adapter.objectManager
157			&& adapter.objectManager.objects
158			&& adapter.objectManager.objects.events;
159		if (
160			!isLoveShop()
161			|| !adapter
162			|| !adapter.shopsMap
163			|| typeof adapter.shopsMap.prefetchPointDetails !== 'function'
164			|| !events
165			|| adapter._lovePointPrefetchEvents === events
166		) {
167			return;
168		}
169
170		adapter._lovePointPrefetchEvents = events;
171		var prefetchTimer = null;
172		var prefetchPointId = null;
173		var prefetch = function(pointId) {
174			adapter.shopsMap.prefetchPointDetails(pointId);
175		};
176		var schedulePrefetch = function(event) {
177			var pointId = event.get('objectId');
178			window.clearTimeout(prefetchTimer);
179			prefetchPointId = pointId;
180			prefetchTimer = window.setTimeout(function() {
181				prefetch(pointId);
182			}, 60);
183		};
184		var startPrefetch = function(event) {
185			window.clearTimeout(prefetchTimer);
186			prefetch(event.get('objectId'));
187		};
188
189		events.add('mouseenter', schedulePrefetch);
190		events.add(['mousedown', 'touchstart'], startPrefetch);
191		events.add('mouseleave', function(event) {
192			if ( prefetchPointId === event.get('objectId') ) {
193				window.clearTimeout(prefetchTimer);
194				prefetchPointId = null;
195			}
196		});
197	}
198
199	function usesDefaultPointDetailsErrorUi(widget) {
200		return !widget.options || typeof widget.options.pointLoadingEndEvent !== 'function';
201	}
202
203	function ensureCoffeeMapFilter(adapter) {
204		if (
205			!isCoffeeShop()
206			|| !adapter
207			|| !adapter.map
208			|| !adapter.objectManager
209			|| !window.ymaps
210		) {
211			return;
212		}
213
214		var points = adapter.objectManager.objects.getAll();
215		var available = {
216			cod: false,
217			round_the_clock: false,
218			cdek: false,
219			yandexgo: false,
220			fivepost: false,
221			boxberry: false
222		};
223		var labels = {
224			cod: 'Оплата при получении',
225			round_the_clock: '24 часа',
226			cdek: 'СДЭК',
227			yandexgo: 'Яндекс',
228			fivepost: 'Пятёрочка (5Post)',
229			boxberry: 'BoxBerry'
230		};
231
232		for ( var i = 0; i < points.length; i++ ) {
233			var properties = points[i].properties || {};
234			available.cod = available.cod || !!properties.cod || !!properties.pay_card || !!properties.point_cash;
235			available.round_the_clock = available.round_the_clock || !!properties.round_the_clock;
236			if ( typeof available[properties.delivery] !== 'undefined' ) {
237				available[properties.delivery] = true;
238			}
239		}
240
241		var filterOrder = ['cod', 'round_the_clock', 'cdek', 'yandexgo', 'fivepost', 'boxberry'];
242		var listBoxItems = [];
243		for ( var j = 0; j < filterOrder.length; j++ ) {
244			var filterValue = filterOrder[j];
245			if ( !available[filterValue] ) {
246				continue;
247			}
248			listBoxItems.push(new ymaps.control.ListBoxItem({
249				data: {
250					content: labels[filterValue],
251					filterValue: filterValue
252				},
253				state: {
254					selected: false
255				}
256			}));
257		}
258
259		if ( adapter.listBoxControl ) {
260			adapter.map.controls.remove(adapter.listBoxControl);
261		}
262		if ( !listBoxItems.length ) {
263			adapter.listBoxControl = null;
264			return;
265		}
266
267		var reduceFilters = function(filters, filter) {
268			filters[filter.data.get('filterValue')] = filter.isSelected();
269			return filters;
270		};
271		adapter.listBoxControl = new ymaps.control.ListBox({
272			data: {
273				content: 'Фильтр',
274				title: 'Фильтр'
275			},
276			items: listBoxItems,
277			state: {
278				expanded: false,
279				filters: listBoxItems.reduce(reduceFilters, {})
280			}
281		});
282		adapter.map.controls.add(adapter.listBoxControl);
283
284		adapter.listBoxControl.events.add(['select', 'deselect'], function(event) {
285			var listBoxItem = event.get('target');
286			var filters = ymaps.util.extend({}, adapter.listBoxControl.state.get('filters'));
287			filters[listBoxItem.data.get('filterValue')] = listBoxItem.isSelected();
288			adapter.listBoxControl.state.set('filters', filters);
289		});
290
291		var filterMonitor = new ymaps.Monitor(adapter.listBoxControl.state);
292		filterMonitor.add('filters', function(filters) {
293			adapter.objectManager.setFilter(function(object) {
294				var properties = object.properties || {};
295				if ( filters.cod && !(properties.cod || properties.pay_card || properties.point_cash) ) {
296					return false;
297				}
298				if ( filters.round_the_clock && !properties.round_the_clock ) {
299					return false;
300				}
301
302				var selectedDeliveries = [];
303				var deliveryFilters = ['cdek', 'yandexgo', 'fivepost', 'boxberry'];
304				for ( var k = 0; k < deliveryFilters.length; k++ ) {
305					if ( filters[deliveryFilters[k]] ) {
306						selectedDeliveries.push(deliveryFilters[k]);
307					}
308				}
309				return !selectedDeliveries.length || selectedDeliveries.indexOf(properties.delivery) !== -1;
310			});
311		});
312	}
313
314	function showPointLoading(widget, pointId) {
315		var checkedWrapper = $('.store-item-checked', widget.element);
316		$('.store-page__list', widget.element).addClass('d-none');
317		checkedWrapper
318			.data('loading-point-id', String(pointId))
319			.addClass('is-loading')
320			.removeAttr('hidden')
321			.removeClass('d-none hidden')
322			.show();
323	}
324
325	function hidePointLoading(widget, pointId, hideWrapper) {
326		var checkedWrapper = $('.store-item-checked', widget.element);
327		if (
328			typeof pointId !== 'undefined'
329			&& checkedWrapper.data('loading-point-id') !== String(pointId)
330		) {
331			return;
332		}
333		checkedWrapper.removeClass('is-loading').removeData('loading-point-id');
334		if ( hideWrapper ) {
335			checkedWrapper.addClass('d-none');
336		}
337	}
338
339	function clearPointDetailsError(widget) {
340		$('.store-item-checked', widget.element)
341			.removeClass('has-load-error')
342			.find('.store-load-error')
343			.remove();
344	}
345
346	function showPointDetailsError(widget, pointId, loadError) {
347		var checkedWrapper = $('.store-item-checked', widget.element);
348		var message = loadError === 'timeout'
349			? '\u0421\u0435\u0440\u0432\u0435\u0440 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 \u0434\u043e\u043b\u044c\u0448\u0435 \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437.'
350			: '\u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437.';
351		var error = $('<div/>', {
352			'class': 'store-load-error',
353			'role': 'alert'
354		});
355
356		hidePointLoading(widget, pointId, false);
357		clearPointDetailsError(widget);
358		$('<strong/>').text('\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u043f\u0443\u043d\u043a\u0442 \u0432\u044b\u0434\u0430\u0447\u0438').appendTo(error);
359		$('<span/>').text(message).appendTo(error);
360		$('<button/>', {
361			'type': 'button',
362			'class': 'btn js-default-pvz-details-retry',
363			'data-point-id': pointId,
364			'text': '\u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u044c'
365		}).appendTo(error);
366		checkedWrapper
367			.addClass('has-load-error')
368			.removeAttr('hidden')
369			.removeClass('d-none hidden')
370			.show()
371			.append(error);
372	}
373
374	function resetPointDetailsRetry(widget, pointId) {
375		var key = String(pointId);
376		widget._backPointRetryAttempts = widget._backPointRetryAttempts || {};
377		widget._backPointRetryTimers = widget._backPointRetryTimers || {};
378		if ( widget._backPointRetryTimers[key] ) {
379			window.clearTimeout(widget._backPointRetryTimers[key]);
380			delete widget._backPointRetryTimers[key];
381		}
382		delete widget._backPointRetryAttempts[key];
383	}
384
385	function retryPointDetails(widget, pointId, feature, loadError) {
386		if ( !usesDefaultPointDetailsErrorUi(widget) || loadError === 'abort' ) {
387			return false;
388		}
389
390		var key = String(pointId);
391		widget._backPointRetryAttempts = widget._backPointRetryAttempts || {};
392		widget._backPointRetryTimers = widget._backPointRetryTimers || {};
393		if ( (widget._backPointRetryAttempts[key] || 0) >= 1 ) {
394			return false;
395		}
396
397		widget._backPointRetryAttempts[key] = (widget._backPointRetryAttempts[key] || 0) + 1;
398		widget._backPointRetryTimers[key] = window.setTimeout(function() {
399			delete widget._backPointRetryTimers[key];
400			if (
401				String(widget._backActivePointId) === key
402				&& !widget._backPointDetails[pointId]
403				&& !widget._backPointRequests[pointId]
404			) {
405				widget._loadBackPointDetails(pointId, widget.map.getPointById(pointId) || feature);
406				return;
407			}
408			resetPointDetailsRetry(widget, pointId);
409		}, 300);
410		return true;
411	}
412
413	function resetBackPointSelection(widget) {
414		widget._backActivePointId = null;
415		widget._backSelectedPointId = null;
416		var pointRequests = widget._backPointRequests || {};
417		for ( var pointId in pointRequests ) {
418			if ( Object.prototype.hasOwnProperty.call(pointRequests, pointId)
419				&& pointRequests[pointId]
420				&& typeof pointRequests[pointId].abort === 'function' ) {
421				pointRequests[pointId].abort();
422			}
423		}
424		var retryTimers = widget._backPointRetryTimers || {};
425		for ( var retryPointId in retryTimers ) {
426			if ( Object.prototype.hasOwnProperty.call(retryTimers, retryPointId) ) {
427				window.clearTimeout(retryTimers[retryPointId]);
428			}
429		}
430		widget._backPointRequests = {};
431		widget._backPointRetryAttempts = {};
432		widget._backPointRetryTimers = {};
433
434		var objectBalloon = widget.map
435			&& widget.map.objectManager
436			&& widget.map.objectManager.objects
437			&& widget.map.objectManager.objects.balloon;
438		if ( objectBalloon && typeof objectBalloon.close === 'function' ) {
439			objectBalloon.close();
440		}
441		hidePointLoading(widget, undefined, true);
442		clearPointDetailsError(widget);
443		$('.store-item-checked', widget.element)
444			.stop(true, true)
445			.removeClass('is-loading has-load-error')
446			.removeData('loading-point-id')
447			.addClass('d-none')
448			.hide();
449		if ( widget.element && typeof widget.element.trigger === 'function' ) {
450			widget.element.trigger('backShopsMapSelectionReset');
451		}
452	}
453
454	$.widget('map.shopsMap', $.map.shopsMap, {
455		options: {
456			pointDetailsTimeout: 15000,
457			initialRequestTimeout: 20000
458		},
459		_getCitiesList: function() {
460			ensureBackMapCityOption(this, this.options.city);
461			return $.Deferred().resolve().promise();
462		},
463		_getMetroByCity: function() {
464			return false;
465		},
466		_getBackRequestSignature: function(values) {
467			return getRequestSignature($.extend({}, this.getPointsValues || {}, values || {}));
468		},
469		canReuseBackMap: function(options) {
470			return !!this._backInitialLoaded
471				&& this._backRequestSignature === this._getBackRequestSignature(options);
472		},
473		refreshBackViewport: function() {
474			if ( this.map && typeof this.map.fitToViewport === 'function' ) {
475				this.map.fitToViewport();
476			}
477		},
478		reloadBackMap: function(options) {
479			var previousSignature = this._getBackRequestSignature();
480			var keys = [
481				'city',
482				'zone_id',
483				'country',
484				'shop_id',
485				'pvzResourcesFlag',
486				'price',
487				'weight',
488				'product_group',
489				'noPostamat',
490				'free_shipping',
491				'full_prepay_discount_delivery',
492				'fitting',
493				'typesToFitting',
494				'dateSendActual'
495			];
496			options = options || {};
497			ensureBackMapCityOption(this, options.city);
498			for ( var i = 0; i < keys.length; i++ ) {
499				if ( typeof options[keys[i]] !== 'undefined' ) {
500					this.options[keys[i]] = options[keys[i]];
501					this.getPointsValues[keys[i]] = options[keys[i]];
502				}
503			}
504			if ( previousSignature !== this._getBackRequestSignature() ) {
505				this._backInitialRetryCount = 0;
506			}
507			this.getPoints();
508		},
509		_setOptions: function(options) {
510			var refreshProMap = isProShop() && !!this._backRequestSignature;
511			if ( isBillabongShop() && this._backRequestSignature ) {
512				refreshProMap = true;
513			}
514			var sameProMapRequest = refreshProMap
515				&& this._backRequestSignature === this._getBackRequestSignature(options);
516			if ( refreshProMap ) {
517				resetBackPointSelection(this);
518			}
519			if ( options ) {
520				ensureBackMapCityOption(this, options.city);
521			}
522			var result = this._super(options);
523			if ( refreshProMap ) {
524				if ( sameProMapRequest && this._backInitialLoaded ) {
525					var widget = this;
526					setTimeout(function() {
527						widget.refreshBackViewport();
528						hideCoffeeMapLoading();
529					}, 0);
530				} else if ( !this._backInitialRequest ) {
531					this.reloadBackMap(options);
532				}
533			}
534			return result;
535		},
536		getPoints: function() {
537			resetBackPointSelection(this);
538			if ( this.element && this.element[0] ) {
539				this.element[0].backShopsMapWidget = this;
540			}
541			if ( usesResilientInitialMapLoading() ) {
542				prepareCoffeeMapLoading();
543			}
544			this._backInitialRequestGeneration = (this._backInitialRequestGeneration || 0) + 1;
545			var requestGeneration = this._backInitialRequestGeneration;
546			clearTimeout(this._backBoundsTimer);
547			clearTimeout(this._backInitialRetryTimer);
548			if ( this._backInitialRequest ) {
549				this._backInitialRequest.abort();
550			}
551			if ( this._backBoundsRequest ) {
552				this._backBoundsRequest.abort();
553			}
554
555			this._clear();
556			this.pointListDiv.addClass('ajax-loading');
557			this._backLoadedPointIds = {};
558			this._backPointDetails = {};
559			this._backPointRequests = {};
560			this._backInitialLoaded = false;
561			this._backLastBoundsKey = null;
562			this._backBoundsLoadQueued = false;
563			this._backLoadedBoundsKeys = {};
564			this._backRequestSignature = this._getBackRequestSignature();
565			this.element.removeData('back-map-loaded');
566			if ( this.mapLoading ) {
567				this.mapLoading.addClass('loading');
568			}
569
570			var data = $.extend({}, this.getPointsValues, {backGeoJson: 1});
571			var initialRequest = $.ajax({
572				url: this.options.pointsSourceUrl,
573				method: 'POST',
574				dataType: this.options.dataType,
575				context: this,
576				timeout: this.options.initialRequestTimeout,
577				data: data
578			}).done(function(msg) {
579				if ( this._backInitialRequestGeneration !== requestGeneration ) {
580					return;
581				}
582				if ( !msg || !msg.geoJson || !msg.mapCenter ) {
583					this._backInitialRequestFailed = true;
584					return;
585				}
586				this.pointListDiv.removeClass('ajax-loading');
587				this._registerBackGeoJson(msg.geoJson);
588				this.map.addGeoJson(msg.geoJson, false);
589				var initialBoundsKey = this._getBackBoundsKey(msg.bounds);
590				if ( initialBoundsKey ) {
591					this._backLoadedBoundsKeys[initialBoundsKey] = true;
592				}
593				this.distanceFrom = msg.mapCenter;
594				this.map.setMapCenter(msg.mapCenter, this.options.startMapZoom || 10);
595				this._backInitialLoaded = true;
596				this.element.data('back-map-loaded', true);
597				this._toggleFiltrs();
598				this.putPoints();
599				if ( (isProShop() || isBillabongShop()) && msg.selectedPoint ) {
600					this._restoreProSelectedPoint(msg.selectedPoint);
601				}
602				this.options.pointsDrawed(this.element);
603				this._scheduleVisibleBackPointsLoad();
604				if ( usesResilientInitialMapLoading() ) {
605					this._backInitialRetryCount = 0;
606					waitForCoffeeMapRender(this, requestGeneration);
607				}
608			}).fail(function(xhr, status) {
609				if (
610					this._backInitialRequestGeneration !== requestGeneration
611					|| status === 'abort'
612				) {
613					return;
614				}
615				this._backInitialRequestFailed = true;
616			}).always(function() {
617				if ( this._backInitialRequestGeneration !== requestGeneration ) {
618					return;
619				}
620				this.pointListDiv.removeClass('ajax-loading');
621				if ( this._backInitialRequest === initialRequest ) {
622					this._backInitialRequest = null;
623				}
624				if ( !this._backInitialRequestFailed ) {
625					return;
626				}
627
628				this._backInitialRequestFailed = false;
629				if ( !usesResilientInitialMapLoading() ) {
630					if ( this.mapLoading ) {
631						this.mapLoading.removeClass('loading');
632					}
633					return;
634				}
635
636				this._backInitialRetryCount = (this._backInitialRetryCount || 0) + 1;
637				if ( this._backInitialRetryCount > 3 ) {
638					showCoffeeMapRetry(this, 'Не удалось загрузить карту.<br>Нажмите, чтобы повторить');
639					return;
640				}
641				setCoffeeMapLoadingText('Загрузка карты заняла больше времени.<br>Повторяем запрос...');
642
643				var widget = this;
644				var delay = Math.min(400 * this._backInitialRetryCount, 1600);
645				this._backInitialRetryTimer = setTimeout(function() {
646					if ( widget._backInitialRequestGeneration === requestGeneration ) {
647						widget.getPoints();
648					}
649				}, delay);
650			});
651			this._backInitialRequest = initialRequest;
652		},
653		_registerBackGeoJson: function(geoJson) {
654			if ( !geoJson || !geoJson.features ) {
655				return;
656			}
657			this.availableFiltrs = this.availableFiltrs || {};
658			this.availableFiltrs.filtr = this.availableFiltrs.filtr || {};
659			this.availableFiltrs.filtr_delivery = this.availableFiltrs.filtr_delivery || {};
660			var filterNames = ['cod', 'pay_card', 'round_the_clock', 'primerka', 'free_cost'];
661			for ( var filterIndex = 0; filterIndex < filterNames.length; filterIndex++ ) {
662				this.availableFiltrs.filtr[filterNames[filterIndex]] = this.availableFiltrs.filtr[filterNames[filterIndex]] || {};
663			}
664			for ( var i = 0; i < geoJson.features.length; i++ ) {
665				var feature = geoJson.features[i];
666				var point = feature.properties;
667				this._backLoadedPointIds[feature.id] = true;
668				this.availableFiltrs.filtr.cod[point.cod ? 1 : 0] = true;
669				this.availableFiltrs.filtr.pay_card[point.pay_card ? 1 : 0] = true;
670				this.availableFiltrs.filtr.round_the_clock[point.round_the_clock ? 1 : 0] = true;
671				this.availableFiltrs.filtr.primerka[point.primerka ? 1 : 0] = true;
672				this.availableFiltrs.filtr.free_cost[point.free_cost ? 1 : 0] = true;
673				this.availableFiltrs.filtr_delivery[point.delivery] = true;
674			}
675		},
676		_scheduleVisibleBackPointsLoad: function() {
677			var widget = this;
678			clearTimeout(this._backBoundsTimer);
679			this._backBoundsTimer = setTimeout(function() {
680				widget._loadVisibleBackPoints();
681			}, 250);
682		},
683		_getBackBoundsKey: function(bounds) {
684			if ( !bounds || bounds.length !== 2 ) {
685				return '';
686			}
687			var step = 0.02;
688			return JSON.stringify([
689				[
690					Math.floor((bounds[0][0] + 1e-9) / step) * step,
691					Math.floor((bounds[0][1] + 1e-9) / step) * step
692				],
693				[
694					Math.ceil((bounds[1][0] - 1e-9) / step) * step,
695					Math.ceil((bounds[1][1] - 1e-9) / step) * step
696				]
697			]);
698		},
699		_loadVisibleBackPoints: function() {
700			if ( !this._backInitialLoaded ) {
701				return;
702			}
703			var bounds = this.map.getBounds();
704			if ( !bounds || bounds.length !== 2 ) {
705				return;
706			}
707			var boundsKey = this._getBackBoundsKey(bounds);
708			if ( boundsKey === this._backLastBoundsKey || this._backLoadedBoundsKeys[boundsKey] ) {
709				return;
710			}
711			if ( this._backBoundsRequest ) {
712				this._backBoundsLoadQueued = true;
713				return;
714			}
715
716			this._backLastBoundsKey = boundsKey;
717			this._backLoadedBoundsKeys[boundsKey] = true;
718			this._backBoundsLoadQueued = false;
719			var data = $.extend({}, this.getPointsValues, {
720				backGeoJson: 1,
721				bounds: JSON.stringify(bounds)
722			});
723			this._backBoundsRequest = $.ajax({
724				url: this.options.pointsSourceUrl,
725				method: 'POST',
726				dataType: this.options.dataType,
727				context: this,
728				global: false,
729				data: data
730			}).done(function(msg) {
731				if ( !msg.geoJson || !msg.geoJson.features ) {
732					return;
733				}
734				msg.geoJson.features = msg.geoJson.features.filter(function(feature) {
735					return !this._backLoadedPointIds[feature.id];
736				}, this);
737				if ( msg.geoJson.features.length ) {
738					this._registerBackGeoJson(msg.geoJson);
739					this.map.addGeoJson(msg.geoJson, true);
740					this._toggleItemsByFiltrs();
741				}
742			}).fail(function() {
743				this._backLastBoundsKey = null;
744				delete this._backLoadedBoundsKeys[boundsKey];
745			}).always(function() {
746				this._backBoundsRequest = null;
747				if ( this._backBoundsLoadQueued ) {
748					this._backBoundsLoadQueued = false;
749					this._scheduleVisibleBackPointsLoad();
750				}
751			});
752		},
753		pointClickEvent: function(id) {
754			var feature = this.map.getPointById(id);
755			if ( !feature ) {
756				return;
757			}
758			if ( usesDefaultPointDetailsErrorUi(this) ) {
759				var checkedWrapper = $('.store-item-checked', this.element);
760				if ( checkedWrapper.hasClass('has-load-error') ) {
761					resetPointDetailsRetry(this, id);
762				}
763				clearPointDetailsError(this);
764			}
765			this._backActivePointId = id;
766			if ( this._backPointDetails[id] ) {
767				if ( usesDefaultPointDetailsErrorUi(this) ) {
768					resetPointDetailsRetry(this, id);
769				}
770				this._emitBackPointClick(this._backPointDetails[id], feature.geometry.coordinates);
771				return;
772			}
773
774			if ( typeof this.options.pointLoadingEvent === 'function' ) {
775				this.options.pointLoadingEvent.apply(this.element, [id, feature.geometry.coordinates]);
776			} else {
777				showPointLoading(this, id);
778			}
779			if ( this._backPointRequests[id] ) {
780				return;
781			}
782			this._loadBackPointDetails(id, feature);
783		},
784		prefetchPointDetails: function(id) {
785			if ( this._backPointDetails[id] || this._backPointRequests[id] ) {
786				return;
787			}
788			var feature = this.map.getPointById(id);
789			if ( !feature ) {
790				return;
791			}
792			this._loadBackPointDetails(id, feature);
793		},
794		_loadBackPointDetails: function(id, feature) {
795
796			var pointLoaded = false;
797			var loadError = 'error';
798			var data = $.extend({}, this.getPointsValues, {
799				pointId: id,
800				backGeoJson: 1
801			});
802			this._backPointRequests[id] = $.ajax({
803				url: '/ajax_shopsMap.php?action=getPointDetails',
804				method: 'POST',
805				dataType: this.options.dataType,
806				context: this,
807				global: false,
808				timeout: this.options.pointDetailsTimeout,
809				data: data
810			}).done(function(msg) {
811				if ( !msg.point ) {
812					loadError = 'empty';
813					return;
814				}
815				pointLoaded = true;
816				if ( usesDefaultPointDetailsErrorUi(this) ) {
817					resetPointDetailsRetry(this, id);
818					clearPointDetailsError(this);
819				}
820				var properties = this._buildBackPointProperties(msg.point, feature.properties);
821				this._backPointDetails[id] = properties;
822				if ( this._backActivePointId === id ) {
823					this._emitBackPointClick(properties, feature.geometry.coordinates);
824				}
825			}).fail(function(jqXHR, textStatus) {
826				loadError = textStatus || 'error';
827			}).always(function() {
828				delete this._backPointRequests[id];
829				if ( !pointLoaded && this._backActivePointId === id ) {
830					if ( retryPointDetails(this, id, feature, loadError) ) {
831						return;
832					}
833					if ( typeof this.options.pointLoadingEndEvent === 'function' ) {
834						this.options.pointLoadingEndEvent.apply(this.element, [id, loadError]);
835					} else if ( usesDefaultPointDetailsErrorUi(this) ) {
836						showPointDetailsError(this, id, loadError);
837					} else {
838						hidePointLoading(this, id, true);
839					}
840				}
841			});
842		},
843		_buildBackPointProperties: function(point, baseProperties) {
844			var pointCost = parseFloat(point.cost) || 0;
845			var startPrice = point.price_info && point.price_info.start_price
846				? parseFloat(point.price_info.start_price) || 0
847				: 0;
848			var displayCost = pointCost;
849			if (
850				window.PROSKTR
851				&& PROSKTR.shop
852				&& PROSKTR.shop.id === 'LOVE'
853				&& displayCost === 0
854				&& startPrice > 0
855			) {
856				displayCost = startPrice;
857			}
858			return $.extend({}, baseProperties, {
859				title: point.title,
860				delivery: point.point_resourse,
861				round_the_clock: point.round_the_clock,
862				point_cash: point.point_cash,
863				pay_card: point.pay_card,
864				primerka: point.primerka > 0,
865				free_cost: point.cost == 0 || point.fullPrepayFreeDelivery ? 1 : 0,
866				cod: point.pay_card > 0 || point.point_cash > 0,
867				point_metro: point.point_metro,
868				pointId: point.point_id,
869				point_ident: point.point_ident,
870				point_city: point.point_city,
871				dates: point.dates,
872				delivery_name: point.delivery_name,
873				schedule: point.schedule,
874				address: point.point_address,
875				cost: point.cost,
876				display_cost: displayCost,
877				pvz_iml_date: point.select && point.select.length ? point.select[0].id : '',
878				storage_days: point.storage_days,
879				fitting: point.showFitting,
880				details_loaded: true
881			});
882		},
883		_restoreProSelectedPoint: function(point) {
884			var pointId = point && parseInt(point.point_id, 10);
885			var feature = pointId ? this.map.getPointById(pointId) : null;
886			if ( !feature ) {
887				return;
888			}
889			var properties = this._buildBackPointProperties(point, feature.properties);
890			this._backPointDetails[pointId] = properties;
891			this._backSelectedPointId = pointId;
892			this._backActivePointId = pointId;
893			if ( typeof this.map.openPoint === 'function' ) {
894				this.map.openPoint(pointId);
895			}
896			this._emitBackPointClick(properties, feature.geometry.coordinates);
897		},
898		_emitBackPointClick: function(properties, coordinates) {
899			hidePointLoading(this);
900			if ( usesLiveMapOpenerContext() ) {
901				$('.store-item-checked', this.element)
902					.stop(true, true)
903					.removeAttr('hidden')
904					.removeClass('d-none hidden')
905					.show();
906			}
907			this.options.pointClickEvent.apply(this.element, [properties, coordinates]);
908		},
909		_createItem: function(point, pointId) {
910			this.availableFiltrs.filtr.cod[point.pay_card || point.point_cash] = true;
911			this.availableFiltrs.filtr.pay_card[point.pay_card] = true;
912			this.availableFiltrs.filtr.round_the_clock[point.round_the_clock] = true;
913			this.availableFiltrs.filtr.primerka[point.primerka] = true;
914			this.availableFiltrs.filtr.free_cost[point.cost == 0 || point.fullPrepayFreeDelivery] = true;
915			this.availableFiltrs.filtr_delivery[point.point_resourse] = true;
916			this.map.addPoint(point, pointId);
917		},
918		_groupList: function(setCenter) {
919			var deferred = $.Deferred();
920			if ( typeof setCenter === 'undefined' ) {
921				deferred.resolve();
922				return deferred;
923			}
924
925			var points = this.map.getPoints();
926			var maxDistances = [1000, 3000, 10000, Infinity];
927			var maxDistance = maxDistances[Math.min(setCenter, maxDistances.length - 1)];
928			var latitudes = [this.distanceFrom[0]];
929			var longitudes = [this.distanceFrom[1]];
930
931			for ( var i = 0; i < points.length; i++ ) {
932				var coordinates = this.map.getPointCoordinates(points[i]);
933				if ( this.map.getDistance(coordinates, this.distanceFrom) < maxDistance ) {
934					latitudes.push(coordinates[0]);
935					longitudes.push(coordinates[1]);
936				}
937			}
938
939			this._setMapCenter(
940				[
941					[Math.min.apply(null, latitudes), Math.min.apply(null, longitudes)],
942					[Math.max.apply(null, latitudes), Math.max.apply(null, longitudes)]
943				],
944				[
945					latitudes.reduce(function(a, b) { return a + b; }) / latitudes.length,
946					longitudes.reduce(function(a, b) { return a + b; }) / longitudes.length
947				]
948			).done(function() {
949				deferred.resolve();
950			});
951			return deferred;
952		}
953	});
954
955	if ( !adapterPrototype.optimizedViewportLoading ) {
956		var originalInit = adapterPrototype.init;
957		adapterPrototype.init = function(options) {
958			originalInit.apply(this, arguments);
959			enableLovePointPrefetch(this);
960			if ( !this.iconContentLayout && window.ymaps && ymaps.templateLayoutFactory ) {
961				this.iconContentLayout = ymaps.templateLayoutFactory.createClass(
962					'<div class="map-mark"><div class="map-mark-label">$[properties.delivery_name]<br>$[properties.cost_label]</div></div>'
963				);
964			}
965			var adapter = this;
966			if ( this.map && this.map.events ) {
967				this.map.events.add('actionend', function() {
968					if (
969						adapter.shopsMap
970						&& typeof adapter.shopsMap._scheduleVisibleBackPointsLoad === 'function'
971					) {
972						adapter.shopsMap._scheduleVisibleBackPointsLoad();
973					}
974				});
975			}
976		};
977		adapterPrototype.optimizedViewportLoading = true;
978	}
979
980	adapterPrototype.addGeoJson = function(geoJson, append) {
981		if ( !geoJson || !geoJson.features ) {
982			return;
983		}
984		for ( var i = 0;
984 i < geoJson.features.length; i++ ) {
985			var pointOptions = {
986				iconLayout: this.hidePointLabels ? 'default#image' : 'default#imageWithContent',
987				draggable: false,
988				hideIconOnBallon: false,
989				iconImageHref: this.iDomain + this.iconsPoint.src,
990				iconImageSize: this.iconsPoint.size,
991				iconImageOffset: this.iconsPoint.offset
992			};
993			if ( !this.hidePointLabels ) {
994				pointOptions.iconContentLayout = this.iconContentLayout;
995			}
996			geoJson.features[i].options = pointOptions;
997		}
998		if ( append ) {
999			this.objectManager.add(geoJson);
1000			if ( typeof this.refreshFilterControl === 'function' ) {
1001				this.refreshFilterControl();
1002			}
1003			return;
1004		}
1005		this.pointsInfo = geoJson;
1006		this.addPoints();
1007	};
1008	adapterPrototype.getBounds = function() {
1009		return this.map.getBounds();
1010	};
1011	adapterPrototype.fitToViewport = function() {
1012		if ( this.map && this.map.container ) {
1013			this.map.container.fitToViewport();
1014		}
1015	};
1016	adapterPrototype.getPointById = function(id) {
1017		return this.objectManager.objects.getById(id);
1018	};
1019
1020	if ( !adapterPrototype.optimizedRemoveAllPatched && typeof adapterPrototype.addPoints === 'function' ) {
1021		var originalAddPoints = adapterPrototype.addPoints;
1022		adapterPrototype.addPoints = function() {
1023			var events = this.listBoxControl && this.listBoxControl.events;
1024			if ( events && typeof events.removeAll !== 'function' ) {
1025				events.removeAll = function() {
1026					return this;
1027				};
1028			}
1029			var result = originalAddPoints.apply(this, arguments);
1030			ensureCoffeeMapFilter(this);
1031			return result;
1032		};
1033		adapterPrototype.optimizedRemoveAllPatched = true;
1034	}
1035
1036	function getWidget() {
1037		var popup = document.getElementById('map-popup');
1038		return popup && (
1039			popup.backShopsMapWidget
1040			|| $('#map-popup').data('map-shopsMap')
1041			|| $('#map-popup').data('shopsMap')
1042		);
1043	}
1044
1045	function isMagnificMapPopup() {
1046		return $('#map-popup').hasClass('popupWindow')
1047			&& $.magnificPopup
1048			&& typeof $.magnificPopup.open === 'function';
1049	}
1050
1051	function showMapPopup() {
1052		if ( isMagnificMapPopup() ) {
1053			$.magnificPopup.open({
1054				items: {
1055					src: '#map-popup',
1056					type: 'inline'
1057				}
1058			});
1059			return;
1060		}
1061		$('#map-popup').modal('show');
1062	}
1063
1064	function prepareCoffeeMapLoading() {
1065		if ( !usesResilientInitialMapLoading() ) {
1066			return;
1067		}
1068
1069		var popup = $('#map-popup');
1070		var mapHolder = popup.find('.map-holder').first();
1071		var mapLoading = popup.find('#mapLoading');
1072
1073		if ( isCoffeeShop() && mapHolder.length && mapLoading.length && !mapLoading.parent().is(mapHolder) ) {
1074			mapLoading.prependTo(mapHolder);
1075		}
1076		if ( isProShop() ) {
1077			mapLoading.find('.loader-text').text('\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u043f\u0443\u043d\u043a\u0442\u043e\u0432 \u0432\u044b\u0434\u0430\u0447\u0438');
1078		}
1079		mapLoading
1080			.removeClass('retry-ready')
1081			.addClass('loading back-initial-loading');
1082	}
1083
1084	function hideCoffeeMapLoading() {
1085		if ( usesResilientInitialMapLoading() ) {
1086			$('#map-popup #mapLoading').removeClass('loading back-initial-loading');
1087		}
1088	}
1089
1090	function setCoffeeMapLoadingText(text) {
1091		if ( !usesResilientInitialMapLoading() ) {
1092			return;
1093		}
1094		$('#map-popup #mapLoading .loader-text').html(text);
1095	}
1096
1097	function showCoffeeMapRetry(widget, text) {
1098		if ( !usesResilientInitialMapLoading() ) {
1099			return;
1100		}
1101		var mapLoading = $('#map-popup #mapLoading');
1102		mapLoading
1103			.data('retry-widget', widget)
1104			.addClass('loading back-initial-loading retry-ready');
1105		setCoffeeMapLoadingText(text);
1106	}
1107
1108	function waitForCoffeeMapRender(widget, requestGeneration, attempt) {
1109		if ( !usesResilientInitialMapLoading() || widget._backInitialRequestGeneration !== requestGeneration ) {
1110			return;
1111		}
1112
1113		attempt = attempt || 0;
1114		var mapRendered = widget._backInitialLoaded
1115			&& $('#map-popup #map-canvas').find('[class*="ymaps-"]').length > 0;
1116
1117		if ( mapRendered ) {
1118			widget.refreshBackViewport();
1119			hideCoffeeMapLoading();
1120			return;
1121		}
1122
1123		if ( attempt < 80 ) {
1124			setTimeout(function() {
1125				waitForCoffeeMapRender(widget, requestGeneration, attempt + 1);
1126			}, 50);
1127			return;
1128		}
1129
1130		showCoffeeMapRetry(widget, 'Не удалось отобразить карту.<br>Нажмите, чтобы повторить');
1131	}
1132
1133	function refreshVisibleMap() {
1134		var widget = getWidget();
1135		if ( widget && widget._backInitialLoaded ) {
1136			setTimeout(function() {
1137				widget.refreshBackViewport();
1138			}, 0);
1139		}
1140	}
1141
1142	document.addEventListener('click', function(event) {
1143		var target = event.target.closest ? event.target.closest('.shops_map_call') : null;
1144		if ( !target ) {
1145			return;
1146		}
1147		prepareCoffeeMapLoading();
1148		// PRO uses the legacy Helius popup. Let its delegated handler keep the
1149		// active delivery row and apply callback in sync; _setOptions above takes
1150		// care of reusing or reloading the optimized map on subsequent opens.
1151		if ( isProShop() ) {
1152			var proWidget = getWidget();
1153			if ( proWidget ) {
1154				resetBackPointSelection(proWidget);
1155			}
1156			return;
1157		}
1158		// Billabong keeps its own modal-holder opener and live delivery-row
1159		// callback. The shared bridge still handles reuse and reload in _setOptions.
1160		if ( isBillabongShop() ) {
1161			var billabongWidget = getWidget();
1162			if ( billabongWidget ) {
1163				resetBackPointSelection(billabongWidget);
1164			}
1165			return;
1166		}
1167		var widget = getWidget();
1168		if ( !widget ) {
1169			return;
1170		}
1171
1172		event.preventDefault();
1173		event.stopPropagation();
1174		if ( typeof event.stopImmediatePropagation === 'function' ) {
1175			event.stopImmediatePropagation();
1176		}
1177
1178		var options = $(target).data('map-options') || {};
1179		if ( usesLiveMapOpenerContext() ) {
1180			$('#delivery .shops_map_call').removeClass('opened');
1181		}
1182		$(target).removeClass('disabled').addClass('opened');
1183		showMapPopup();
1184		if ( widget.canReuseBackMap(options) ) {
1185			setTimeout(function() {
1186				widget.refreshBackViewport();
1187				hideCoffeeMapLoading();
1188			}, 0);
1189		} else {
1190			widget.reloadBackMap(options);
1191		}
1192	}, true);
1193
1194	$('#map-popup').on('shown.bs.modal.optimizedShopsMap', refreshVisibleMap);
1195	$(document).on('click.optimizedShopsMap', '#map-popup #mapLoading.retry-ready', function() {
1196		var widget = $(this).data('retry-widget') || getWidget();
1197		if ( !widget ) {
1198			return;
1199		}
1200		widget._backInitialRetryCount = 0;
1201		widget.getPoints();
1202	});
1203	$(document)
1204		.off('click.optimizedShopsMapPointRetry', '#map-popup .js-default-pvz-details-retry')
1205		.on('click.optimizedShopsMapPointRetry', '#map-popup .js-default-pvz-details-retry', function(event) {
1206			var widget = getWidget();
1207			var pointId = $(this).data('point-id');
1208
1209			event.preventDefault();
1210			event.stopPropagation();
1211			if ( !widget || typeof pointId === 'undefined' ) {
1212				return;
1213			}
1214			resetPointDetailsRetry(widget, pointId);
1215			clearPointDetailsError(widget);
1216			widget.pointClickEvent(pointId);
1217		});
1218	$(document).on('mfpOpen.optimizedShopsMap', function() {
1219		if ( $.magnificPopup.instance && $.magnificPopup.instance.content ) {
1220			var content = $.magnificPopup.instance.content;
1221			if ( !content.is('#map-popup') && !content.find('#map-popup').length ) {
1222				return;
1223			}
1224		}
1225		refreshVisibleMap();
1226	});
1227})(window.jQuery);

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.