PageSourceSearch

https://mts-home.online/wp-content/themes/mts-home.online/js/citylink.js

js mts-home.online collected 2026-10-08 11:38:25 UTC 14,027 bytes, 428 lines download raw bytes

1// // Скрипт для подтягивания списка городов с таблицы
2// var MAX_ROWS = 500;
3//     var file = "https://docs.google.com/spreadsheets/d/1nYkPC8SeAaSl91JUWrhjXUbVldpieGHLBZIswg-Ewh0";
4//     var query = "/gviz/tq?gid=0";
5//     var url = file + query;
6    
7//      regions = [];
8
9//     var google = {
10//         visualization: { Query: { setResponse: function () {} } },
11//     };
12
13//     function updatePage(data) {
14//         if (!data || !data.table) {
15//             console.log("No data table.");
16//             return;
17//         }
18
19//         var table = data.table;
20
21//         if (!table.rows) {
22//             console.log("No table rows.");
23//             return;
24//         }
25
26//         var row_count = table.rows.length;
27//         var cols = Math.ceil(row_count / MAX_ROWS);
28
29//         for (var row of table.rows) {
30//             if (row.c && row.c[0] && row.c[1]) {
31//                 var name_human = row.c[0];
32//                 var name_search = row.c[1];
33//                 var enabled = row.c[2];
34//                 var link = row.c[3];
35// 				var region_id = row.c[6];
36
37//                 if (
38//                     !name_human.v ||
39//                     !name_human.v.length ||
40//                     !name_search.v ||
41//                     !name_search.v.length ||
42//                     !link.v ||
43//                     !link.v.length
44//                 ) {
45//                     continue;
46//                 }
47
48//                 regions.push({
49//                     name_human: name_human.v,
50//                     name_search: name_search.v,
51//                     link: link.v,
52//                     enabled: enabled.v,
53// 					region_id: region_id.v,
54//                 });
55//             }
56//         }
57
58//         drawRegions(
59//             regions
60//                 .sort((a, b) => a.name_human.localeCompare(b.name_human))
61//                 .filter((x) => x.enabled)
62//         );
63//     }
64
65//     function drawRegions(regions_arr) {
66//         var trs = [];
67
68//         $(".city_list tbody").empty();
69
70//         for (var i = 0; i < MAX_ROWS; i++) {
71//             var tr = $("<tr></tr>");
72//             $(".city_list tbody").append(tr);
73//             trs.push(tr);
74//         }
75
76//         var letter = "";
77//         while (regions_arr.length) {
78//             for (var tr of trs) {
79//                 if (!regions_arr.length) {
80//                     break;
81//                 }
82
83//                 if (letter !== regions_arr[0].name_human[0]) {
84//                     letter = regions_arr[0].name_human[0];
85//                     tr.append(`
86//                                 <td>
87//                                     <p class="region_title">${letter}</p>
88//                                 </td>`);
89//                     continue;
90//                 }
91
92//                 var next_region = regions_arr.shift();
93
94//                 if (next_region) {
95//                     tr.append(`
96//                                 <td>
97//                                     <a class="region_item region_link" href='${next_region.link}' id='${next_region.region_id}'>${next_region.name_human}</a>
98//                                 </td>`);
99//                 }
100//             }
101//         }
102//     }
103
104//     google.visualization.Query.setResponse = function (data) {
105//         updatePage(data);
106//     };
107
108//     $().ready(function () {
109//         $.ajax({
110//             url: url,
111//             dataType: "jsonp",
112//         });
113
114//                 // ВЫБИРАЕТ В ТОМ ЧИСЛЕ ОДНОКОРЕННЫЕ СЛОВА
115//         // $('#region_search').on('input', function(e) {
116//         //     if (regions && regions.length && e.target.value && e.target.value.length > 1) {
117//         //         var regions_filtered = regions.filter(function(x) {
118//         //             return x.name_search.toLowerCase().includes(e.target.value.toLowerCase());
119//         //         })
120//         //         drawRegions(regions_filtered);
121//         //     } else if (!e.target.value) {
122//         //         drawRegions(regions.sort((a, b) => a.name_human.localeCompare(b.name_human)).filter(x => x.enabled))
123//         //     }
124//         // })
125        
126        
127//         // НЕ ВЫБИРАЕТ ОДНОКОРЕННЫЕ, ИДЕТ С НАЧАЛА СЛОВА
128//             $('.popup-select-city__input').on('input', function(e) {
129//         if (regions && regions.length && e.target.value && e.target.value.length > 1) {
130//             var regions_filtered = regions.filter(function(x) {
131//                 return x.name_search.toLowerCase().startsWith(e.target.value.toLowerCase());
132//             });
133//             drawRegions(regions_filtered);
134//         } else if (!e.target.value) {
135//             drawRegions(regions.sort((a, b) => a.name_human.localeCompare(b.name_human)).filter(x => x.enabled));
136//         }
137//         });
138//     });
139    
140    
141    
142    
143    
144    
145    const RegionManager = {
146    cfg: {
147        url: "/wp-content/uploads/select-region/regions.csv",
148        sheet: { id: "1nYkPC8SeAaSl91JUWrhjXUbVldpieGHLBZIswg-Ewh0", gid: "0" },
149        batch: 40,
150        key: "regions_v5", // Обновил версию кеша
151        cookie: "regions_v",
152        domain: ".mts-home.online", 
153        ttl: 7200000, 
154    },
155    state: {
156        all: [],      
157        active: [],   
158        ltr: "", 
159        loading: false,
160        raf: 0,
161        searchRaw: "",
162        $table: null,
163        isFirstBatch: true 
164    },
165
166    init() {
167        this.state.$table = $(".city_list tbody");
168        this.bind();
169
170        const cache = this.loadCache();
171        const cookieV = this.getCookie(this.cfg.cookie);
172
173        if (cache) {
174            this.state.all = cache.all;
175            this.state.active = cache.active;
176            this.render(this.state.active); 
177            
178            if (cache.v !== cookieV) {
179                console.log("[Regions] Update bg");
180                this.fetchCsv(true); 
181            }
182        } else {
183            this.renderMsg("Загрузка списка...");
184            this.fetchCsv(false);
185        }
186    },
187
188    async fetchCsv(isBg) {
189        if (this.state.loading) return;
190        this.state.loading = true;
191        this.state.isFirstBatch = true; 
192
193        this.state.all = [];
194        this.state.active = [];
195
196        try {
197            const resp = await fetch(this.cfg.url);
198            
199            // Если сервер вернул 404 или 500 — сразу ошибка с нужным текстом
200            if (!resp.ok) throw new Error("CSV not found");
201            
202            const reader = resp.body.getReader();
203            const dec = new TextDecoder("utf-8");
204            let buf = "";
205            let batch = [];
206
207            while (true) {
208                const { done, value } = await reader.read();
209                if (done) break;
210                
211                buf += dec.decode(value, { stream: true });
212                const lines = buf.split(/\r?\n/);
213                buf = lines.pop(); 
214
215                for (const line of lines) {
216                    if (!line.trim()) continue;
217                    const row = this.parse(line);
218                    if (!row) continue;
219
220                    this.state.all.push(row); 
221                    
222                    if (row.en) {
223                        this.state.active.push(row); 
224                        batch.push(row); 
225                    }
226                }
227
228                if (!isBg && !this.state.searchRaw && batch.length >= this.cfg.batch) {
229                    this.flushBatch(batch);
230                    batch = [];
231                }
232            }
233            
234            if (buf.trim()) {
235                const row = this.parse(buf);
236                if (row) {
237                    this.state.all.push(row);
238                    if (row.en) {
239                        this.state.active.push(row);
240                        batch.push(row);
241                    }
242                }
243            }
244
245            // === ВАЖНЫЙ ФИКС ===
246            if (this.state.all.length === 0) {
247                throw new Error("CSV is empty"); 
248            }
249
250            if (!isBg && !this.state.searchRaw && batch.length) {
251                this.flushBatch(batch);
252            }
253            
254            // Вывод об успешной загрузке из CSV
255            console.log(`[regions] Loaded from CSV: ${this.state.all.length} rows`);
256
257            this.finishLoad(true);
258
259        } catch (e) {
260            // Вывод ошибки для Fallback-сценария
261            console.warn(`[regions] CSV fallback triggered: ${e.message}`);
262            await this.fetchGoogle();
263        }
264    },
265
266    flushBatch(batch) {
267        if (this.state.isFirstBatch) {
268            this.state.$table.empty(); 
269            this.state.ltr = "";       
270            this.state.isFirstBatch = false;
271        }
272        this.appendDraw(batch);
273    },
274
275    finishLoad(success) {
276        this.state.loading = false;
277        if (this.state.all.length > 0) {
278            this.saveCache();
279        }
280        
281        if (this.state.searchRaw.length >= 2) {
282            this.runSearch(this.state.searchRaw);
283        } else if (!this.state.$table.children().length) {
284            this.render(this.state.active);
285        }
286    },
287
288    parse(line) {
289        const cols = line.split(','); 
290        if (cols.length < 4) return null;
291        
292        const clean = v => (v || "").replace(/^"|"$/g, "").trim();
293        const enVal = clean(cols[2]).toLowerCase();
294        
295        return {
296            n: clean(cols[0]),
297            s: clean(cols[1]).toLowerCase(), 
298            en: ["1", "true", "yes"].includes(enVal),
299            l: clean(cols[3]),
300            id: clean(cols[4])
301        };
302    },
303
304    render(list) {
305        this.state.$table.empty();
306        this.state.ltr = "";
307        
308        if (!list.length) {
309            const msg = this.state.loading ? "Ищем..." : "Ничего не найдено";
310            this.renderMsg(msg);
311            return;
312        }
313        this.appendDraw(list);
314    },
315
316    appendDraw(list) {
317        const html = [];
318        for (const r of list) {
319            const l = r.n[0].toUpperCase();
320            if (l !== this.state.ltr) {
321                this.state.ltr = l;
322                html.push(`<tr><td class="region_title">${l}</td></tr>`);
323            }
324            html.push(`<tr><td><a class="region_item region_link" href="${r.l}" id="${r.id}">${r.n}</a></td></tr>`);
325        }
326        this.state.$table.append(html.join(""));
327    },
328
329    renderMsg(txt) {
330        this.state.$table.html(`<tr><td class="region_title">${txt}</td></tr>`);
331    },
332
333    bind() {
334        const handle = (e, method) => {
335            const q = e.target.value.trim().toLowerCase();
336            this.state.searchRaw = q;
337
338            if (this.state.raf) cancelAnimationFrame(this.state.raf);
339            
340            this.state.raf = requestAnimationFrame(() => {
341                if (q.length < 2) {
342                    this.render(this.state.active);
343                    return;
344                }
345                this.runSearch(q, method);
346            });
347        };
348
349        $('#region_search').on('input', e => handle(e, 'includes'));
350        $('.popup-select-city__input').on('input', e => handle(e, 'startsWith'));
351    },
352
353    runSearch(q, method = 'includes') {
354        const source = this.state.all; 
355        const filtered = source.filter(i => {
356            return method === 'startsWith' ? i.s.startsWith(q) : i.s.includes(q);
357        });
358        this.render(filtered);
359    },
360
361    async fetchGoogle() {
362        if (this.state.all.length === 0) {
363             this.renderMsg("Загрузка из резерва...");
364        }
365
366        const url = `https://docs.google.com/spreadsheets/d/${this.cfg.sheet.id}/gviz/tq?gid=${this.cfg.sheet.gid}&tq=select%20*&tqx=out:json`;
367        try {
368            const txt = await (await fetch(url)).text();
369            const json = JSON.parse(txt.match(/setResponse\(([\s\S]*)\);/)[1]);
370            const rows = json.table.rows || [];
371            
372            let tempAll = [];
373            
374            rows.forEach(r => {
375                const item = {
376                    n: r.c[0]?.v, 
377                    s: (r.c[1]?.v||"").toLowerCase(), 
378                    en: !!r.c[2]?.v, 
379                    l: r.c[3]?.v, 
380                    id: r.c[6]?.v
381                };
382                if (item.n && item.l) {
383                    tempAll.push(item);
384                }
385            });
386
387            tempAll.sort((a, b) => a.n.localeCompare(b.n));
388
389            this.state.all = tempAll;
390            this.state.active = tempAll.filter(x => x.en);
391            
392            // Вывод об успешной загрузке из Google
393            console.log(`[regions] Loaded from Google: ${this.state.all.length} rows`);
394
395            this.finishLoad(true);
396            
397            if (this.state.searchRaw.length >= 2) this.runSearch(this.state.searchRaw);
398            else this.render(this.state.active);
399
400        } catch(e) {
401            console.error(e);
402            this.renderMsg("Ошибка загрузки");
403        }
404    },
405
406    saveCache() {
407        try {
408            localStorage.setItem(this.cfg.key, JSON.stringify({
409                v: ""+Date.now(),
410                all: this.state.all,
411                active: this.state.active
412            }));
413            document.cookie = `${this.cfg.cookie}=${Date.now()}; path=/; domain=${this.cfg.domain}; max-age=86400; SameSite=Lax`;
414        } catch(e){}
415    },
416    
417    loadCache() {
418        try {
419            const d = JSON.parse(localStorage.getItem(this.cfg.key));
420            if (!d || Date.now() - parseInt(d.v) > this.cfg.ttl) return null;
421            return d;
422        } catch(e) { return null; }
423    },
424    
425    getCookie(n) { const m = document.cookie.match(new RegExp(`(^| )${n}=([^;]+)`)); return m ? m[2] : null; }
426};
427
428$(() => RegionManager.init());

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.