PageSourceSearch

https://mbclub.ru/js/MBClub/Partners/catalog.js?_v=62529aa8

js mbclub.ru collected 2026-09-24 23:38:50 UTC 10,666 bytes, 84 lines download raw bytes

1(function ()
2{
3    'use strict';
4    let yandexLoader = null;
5    function escapeHtml(value){const node=document.createElement('span');node.textContent=String(value||'');return node.innerHTML;}
6    function loadYandex(apiKey)
7    {
8        if(window.ymaps){return Promise.resolve(window.ymaps);}if(yandexLoader){return yandexLoader;}
9        yandexLoader=new Promise((resolve,reject)=>{if(!apiKey){reject(new Error('YANDEX_MAPS_KEY_MISSING'));return;}const script=document.createElement('script');script.src='https://api-maps.yandex.ru/2.1/?apikey='+encodeURIComponent(apiKey)+'&lang=ru_RU';script.async=true;script.dataset.mbcYandexMaps='1';script.onload=()=>window.ymaps?window.ymaps.ready(()=>resolve(window.ymaps)):reject(new Error('YANDEX_MAPS_GLOBAL_MISSING'));script.onerror=()=>reject(new Error('YANDEX_MAPS_LOAD_FAILED'));document.head.appendChild(script);});return yandexLoader;
10    }
11    function parsePoints(root){const node=root.querySelector('.js-mbcCatalogMapData');if(!node){return [];}try{return JSON.parse(node.textContent||'[]');}catch(error){console.error('MBClub Partners Yandex map data is invalid',error);return [];}}
12    function balloonContent(point)
13    {
14        const company=escapeHtml(point.company_name||point.company||point.location_name||point.location||'Компания');
15        const location=escapeHtml(point.location_name||point.location||'');
16        const address=escapeHtml([point.city,point.address].filter(Boolean).join(', '));
17        const logo=point.logo_url?'<img class="mbcCatalog-mapBalloonLogo" src="'+escapeHtml(point.logo_url)+'" alt="">':'<span class="mbcCatalog-mapBalloonLogo mbcCatalog-mapBalloonLogo--fallback" aria-hidden="true">'+escapeHtml(point.initial||company.charAt(0))+'</span>';
18        const locationLine=location&&location!==company?'<div class="mbcCatalog-mapBalloonLocation">'+location+'</div>':'';
19        const partnerBadge=point.relationship_status==='partner'?'<span class="mbcCatalog-mapBalloonPartner">Партнёр MBClub</span>':'';
20        const type=point.primary_type?'<span class="mbcCatalog-mapBalloonType">'+escapeHtml(point.primary_type)+'</span>':'';
21        const statusClass=point.open_state==='open'?' mbcCatalog-mapBalloonStatus--open':(point.open_state==='temporarily_closed'?' mbcCatalog-mapBalloonStatus--attention':'');
22        const status=point.open_status?'<div class="mbcCatalog-mapBalloonStatus'+statusClass+'">'+escapeHtml(point.open_status)+'</div>':'';
23        const today=point.today_hours?'<div class="mbcCatalog-mapBalloonHours"><strong>Сегодня:</strong> '+escapeHtml(point.today_hours)+'</div>':'';
24        const services=Array.isArray(point.services)&&point.services.length?'<div class="mbcCatalog-mapBalloonServices">'+point.services.map((service)=>'<span>'+escapeHtml(service)+'</span>').join('')+'</div>':'';
25        const phone=point.phone_uri&&point.phone_display?'<a class="mbcCatalog-mapBalloonPhone" href="'+escapeHtml(point.phone_uri)+'">'+escapeHtml(point.phone_display)+'</a>':'';
26        const companyLink=point.company_url?'<a class="mbcCatalog-mapBalloonLink" href="'+escapeHtml(point.company_url)+'">О компании</a>':'';
27        const call=point.phone_uri?'<a class="mbcCatalog-mapBalloonAction mbcCatalog-mapBalloonAction--secondary" href="'+escapeHtml(point.phone_uri)+'">Позвонить</a>':'';
28        const route=Number.isFinite(Number(point.lat))&&Number.isFinite(Number(point.lng))?'<a class="mbcCatalog-mapBalloonAction mbcCatalog-mapBalloonAction--secondary" href="https://yandex.ru/maps/?rtext=~'+encodeURIComponent(point.lat+','+point.lng)+'&rtt=auto" target="_blank" rel="noopener noreferrer">Маршрут</a>':'';
29        return '<div class="mbcCatalog-mapBalloon"><div class="mbcCatalog-mapBalloonHead">'+logo+'<div class="mbcCatalog-mapBalloonIdentity"><strong class="mbcCatalog-mapBalloonTitle">'+company+'</strong><div class="mbcCatalog-mapBalloonBadges">'+partnerBadge+type+'</div></div></div>'+locationLine+'<div class="mbcCatalog-mapBalloonAddress">'+address+'</div>'+status+today+services+phone+'<div class="mbcCatalog-mapBalloonActions">'+companyLink+call+route+'</div></div>';
30    }
31    function setQuery(values){const url=new URL(window.location.href);
31Object.entries(values).forEach(([key,value])=>{if(value===''||value===null){url.searchParams.delete(key);}else{url.searchParams.set(key,value);}});url.searchParams.delete('page');window.location.assign(url.toString());}
32    function configureClusters(ymaps,clusterer)
33    {
34        const neutralContentLayout=ymaps.templateLayoutFactory.createClass('<div style="font:600 13px Arial,sans-serif;color:#252a31;white-space:nowrap;">{{ properties.geoObjects.length }}</div>');
35        const partnerContentLayout=ymaps.templateLayoutFactory.createClass('<div style="position:relative;display:inline-block;font:600 13px Arial,sans-serif;color:#252a31;white-space:nowrap;">{{ properties.geoObjects.length }}<span style="position:absolute;left:100%;top:-5px;color:#1686e7;font-size:9px;line-height:1;">★</span></div>');
36        clusterer.createCluster=function(center,geoObjects)
37        {
38            const cluster=ymaps.Clusterer.prototype.createCluster.call(this,center,geoObjects);
39            const hasPartner=geoObjects.some((geoObject)=>geoObject.properties.get('mbcIsPartner')===true);
40            cluster.options.set({preset:hasPartner?'islands#blueClusterIcons':'islands#grayClusterIcons',iconContentLayout:hasPartner?partnerContentLayout:neutralContentLayout});
41            cluster.properties.set('hintContent',hasPartner?'В этой группе есть партнёр MBClub':'Группа сервисов');
42            return cluster;
43        };
44    }
45    async function initMap(root)
46    {
47        const mapNode=root.querySelector('.js-mbcCatalogMap');if(!mapNode||mapNode.dataset.ready==='1'||mapNode.dataset.ready==='loading'){return;}const points=parsePoints(root);if(!points.length){return;}mapNode.dataset.ready='loading';
48        try
49        {
50            const ymaps=await loadYandex(root.dataset.yandexMapsKey||'');
51            const map=new ymaps.Map(mapNode,{center:[55.751244,37.618423],zoom:9,controls:['zoomControl','fullscreenControl','searchControl','geolocationControl']},{searchControlProvider:'yandex#search',suppressMapOpenBlock:true});map.behaviors.disable('scrollZoom');
52            const clusterer=new ymaps.Clusterer({preset:'islands#grayClusterIcons',groupByCoordinates:false,clusterDisableClickZoom:false,clusterOpenBalloonOnClick:true,gridSize:64});const markers=[];configureClusters(ymaps,clusterer);
53            points.forEach((point)=>{const isPartner=point.relationship_status==='partner';const preset=isPartner?'islands#blueAutoIcon':(point.verification_state==='verified'?'islands#blackAutoIcon':'islands#grayAutoIcon');const marker=new ymaps.Placemark([point.lat,point.lng],{hintContent:escapeHtml(point.company_name||point.company||point.location_name||point.location),balloonContent:balloonContent(point),mbcIsPartner:isPartner},{preset:preset,balloonMinWidth:300,balloonMaxWidth:380});marker.mbcPoint=point;marker.events.add('click',()=>{root.querySelectorAll('.mbcCatalog-card--mapActive').forEach((card)=>card.classList.remove('mbcCatalog-card--mapActive'));const card=root.querySelector('[data-organic-partner-id="'+point.partner_id+'"]');if(card){card.classList.add('mbcCatalog-card--mapActive');}});markers.push(marker);});
54            clusterer.add(markers);map.geoObjects.add(clusterer);root.mbcMap=map;root.mbcMapMarkers=markers;root.mbcMapFocusReady=false;
55            const focusPending=()=>{root.mbcMapFocusReady=true;const partnerId=root.mbcPendingPartnerId;if(partnerId!==undefined){root.mbcPendingPartnerId=undefined;focusPartnerOnMap(root,partnerId);}};
56            const bounds=clusterer.getBounds();if(bounds){const fit=map.setBounds(bounds,{checkZoomRange:true,zoomMargin:32});if(fit&&typeof fit.then==='function'){fit.then(focusPending,focusPending);}else{window.setTimeout(focusPending,0);}}else{focusPending();}
57            mapNode.dataset.ready='1';
58        }
59        catch(error){mapNode.dataset.ready='error';mapNode.innerHTML='<p class="mbcCatalog-mapUnavailable">Яндекс Карта временно недоступна.</p>';console.error('MBClub Partners Yandex map initialization failed',error);}
60    }
61    function focusPartnerOnMap(root,partnerId,attempt=0)
62    {
63        root.mbcPendingPartnerId=Number(partnerId);
64        switchView(root,'map');
65        if(!root.mbcMapFocusReady)
66        {
67            if(attempt<40){window.setTimeout(()=>focusPartnerOnMap(root,partnerId,attempt+1),75);}return;
68        }
69        const marker=root.mbcMapMarkers?.find((item)=>Number(item.mbcPoint.partner_id)===Number(partnerId));
70        if(!marker)
71        {
72            if(attempt<40){window.setTimeout(()=>focusPartnerOnMap(root,partnerId,attempt+1),75);}return;
73        }
74        root.mbcMap.setCenter(marker.geometry.getCoordinates(),Math.max(root.mbcMap.getZoom(),14),{duration:250});
75        marker.balloon.open();
76        root.mbcPendingPartnerId=undefined;
77    }
78    function switchView(root,view){root.dataset.view=view;root.querySelectorAll('.js-mbcView').forEach((button)=>button.setAttribute('aria-pressed',button.dataset.view===view?'true':'false'));const url=new URL(window.location.href);if(view==='map'){url.searchParams.set('view','map');}else{url.searchParams.delete('view');}window.history.replaceState({},'',url.toString());if(view==='map'){initMap(root).then(()=>window.setTimeout(()=>root.mbcMap?.container.fitToViewport(),30));}}
79    function init(root)
80    {
81        root.querySelectorAll('.js-mbcView').forEach((button)=>button.addEventListener('click',()=>
81switchView(root,button.dataset.view)));root.querySelectorAll('.js-mbcShowOnMap').forEach((button)=>button.addEventListener('click',()=>focusPartnerOnMap(root,button.dataset.partnerId)));root.querySelector('.js-mbcCatalogSort')?.addEventListener('change',(event)=>setQuery({sort:event.target.value}));root.querySelector('.js-mbcCatalogSearch')?.addEventListener('submit',(event)=>{event.preventDefault();setQuery({q:new FormData(event.currentTarget).get('q')?.toString().trim()||''});});root.querySelector('.js-mbcCatalogNear')?.addEventListener('click',()=>{if(!navigator.geolocation){return;}navigator.geolocation.getCurrentPosition((position)=>setQuery({near:'1',lat:position.coords.latitude.toFixed(6),lng:position.coords.longitude.toFixed(6),radius:'30',sort:'distance'}),()=>window.alert('Не удалось определить местоположение.'));});if(root.dataset.view==='map'){initMap(root);}
82    }
83    document.querySelectorAll('.mbcCatalog').forEach(init);
84})();

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.