PageSourceSearch

https://beeline-ru.online/wp-content/themes/beeline/js/citylink.js

js beeline-ru.online collected 2026-10-08 07:17:55 UTC 14,021 bytes, 427 lines download raw bytes

1// // Скрипт для подтягивания списка городов с таблицы
2// var MAX_ROWS = 400;
3//     var file = "https://docs.google.com/spreadsheets/d/15eDJMvlEKcnKe36Q1SeT3B8pA1RkmFJu2uHVd7aM4Vc";
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    const RegionManager = {
145    cfg: {
146        url: "/wp-content/uploads/select-region/regions.csv",
147        sheet: { id: "15eDJMvlEKcnKe36Q1SeT3B8pA1RkmFJu2uHVd7aM4Vc", gid: "0" },
148        batch: 40,
149        key: "regions_v5", // Обновил версию кеша
150        cookie: "regions_v",
151        domain: ".beeline-ru.online", 
152        ttl: 7200000, 
153    },
154    state: {
155        all: [],      
156        active: [],   
157        ltr: "", 
158        loading: false,
159        raf: 0,
160        searchRaw: "",
161        $table: null,
162        isFirstBatch: true 
163    },
164
165    init() {
166        this.state.$table = $(".city_list tbody");
167        this.bind();
168
169        const cache = this.loadCache();
170        const cookieV = this.getCookie(this.cfg.cookie);
171
172        if (cache) {
173            this.state.all = cache.all;
174            this.state.active = cache.active;
175            this.render(this.state.active); 
176            
177            if (cache.v !== cookieV) {
178                console.log("[Regions] Update bg");
179                this.fetchCsv(true); 
180            }
181        } else {
182            this.renderMsg("Загрузка списка...");
183            this.fetchCsv(false);
184        }
185    },
186
187    async fetchCsv(isBg) {
188        if (this.state.loading) return;
189        this.state.loading = true;
190        this.state.isFirstBatch = true; 
191
192        this.state.all = [];
193        this.state.active = [];
194
195        try {
196            const resp = await fetch(this.cfg.url);
197            
198            // Если сервер вернул 404 или 500 — сразу ошибка с нужным текстом
199            if (!resp.ok) throw new Error("CSV not found");
200            
201            const reader = resp.body.getReader();
202            const dec = new TextDecoder("utf-8");
203            let buf = "";
204            let batch = [];
205
206            while (true) {
207                const { done, value } = await reader.read();
208                if (done) break;
209                
210                buf += dec.decode(value, { stream: true });
211                const lines = buf.split(/\r?\n/);
212                buf = lines.pop(); 
213
214                for (const line of lines) {
215                    if (!line.trim()) continue;
216                    const row = this.parse(line);
217                    if (!row) continue;
218
219                    this.state.all.push(row); 
220                    
221                    if (row.en) {
222                        this.state.active.push(row); 
223                        batch.push(row); 
224                    }
225                }
226
227                if (!isBg && !this.state.searchRaw && batch.length >= this.cfg.batch) {
228                    this.flushBatch(batch);
229                    batch = [];
230                }
231            }
232            
233            if (buf.trim()) {
234                const row = this.parse(buf);
235                if (row) {
236                    this.state.all.push(row);
237                    if (row.en) {
238                        this.state.active.push(row);
239                        batch.push(row);
240                    }
241                }
242            }
243
244            // === ВАЖНЫЙ ФИКС ===
245            if (this.state.all.length === 0) {
246                throw new Error("CSV is empty"); 
247            }
248
249            if (!isBg && !this.state.searchRaw && batch.length) {
250                this.flushBatch(batch);
251            }
252            
253            // Вывод об успешной загрузке из CSV
254            console.log(`[regions] Loaded from CSV: ${this.state.all.length} rows`);
255
256            this.finishLoad(true);
257
258        } catch (e) {
259            // Вывод ошибки для Fallback-сценария
260            console.warn(`[regions] CSV fallback triggered: ${e.message}`);
261            await this.fetchGoogle();
262        }
263    },
264
265    flushBatch(batch) {
266        if (this.state.isFirstBatch) {
267            this.state.$table.empty(); 
268            this.state.ltr = "";       
269            this.state.isFirstBatch = false;
270        }
271        this.appendDraw(batch);
272    },
273
274    finishLoad(success) {
275        this.state.loading = false;
276        if (this.state.all.length > 0) {
277            this.saveCache();
278        }
279        
280        if (this.state.searchRaw.length >= 2) {
281            this.runSearch(this.state.searchRaw);
282        } else if (!this.state.$table.children().length) {
283            this.render(this.state.active);
284        }
285    },
286
287    parse(line) {
288        const cols = line.split(','); 
289        if (cols.length < 4) return null;
290        
291        const clean = v => (v || "").replace(/^"|"$/g, "").trim();
292        const enVal = clean(cols[2]).toLowerCase();
293        
294        return {
295            n: clean(cols[0]),
296            s: clean(cols[1]).toLowerCase(), 
297            en: ["1", "true", "yes"].includes(enVal),
298            l: clean(cols[3]),
299            id: clean(cols[4])
300        };
301    },
302
303    render(list) {
304        this.state.$table.empty();
305        this.state.ltr = "";
306        
307        if (!list.length) {
308            const msg = this.state.loading ? "Ищем..." : "Ничего не найдено";
309            this.renderMsg(msg);
310            return;
311        }
312        this.appendDraw(list);
313    },
314
315    appendDraw(list) {
316        const html = [];
317        for (const r of list) {
318            const l = r.n[0].toUpperCase();
319            if (l !== this.state.ltr) {
320                this.state.ltr = l;
321                html.push(`<tr><td class="region_title">${l}</td></tr>`);
322            }
323            html.push(`<tr><td><a class="region_item region_link" href="${r.l}" id="${r.id}">${r.n}</a></td></tr>`);
324        }
325        this.state.$table.append(html.join(""));
326    },
327
328    renderMsg(txt) {
329        this.state.$table.html(`<tr><td class="region_title">${txt}</td></tr>`);
330    },
331
332    bind() {
333        const handle = (e, method) => {
334            const q = e.target.value.trim().toLowerCase();
335            this.state.searchRaw = q;
336
337            if (this.state.raf) cancelAnimationFrame(this.state.raf);
338            
339            this.state.raf = requestAnimationFrame(() => {
340                if (q.length < 2) {
341                    this.render(this.state.active);
342                    return;
343                }
344                this.runSearch(q, method);
345            });
346        };
347
348        $('#region_search').on('input', e => handle(e, 'includes'));
349        $('.popup-select-city__input').on('input', e => handle(e, 'startsWith'));
350    },
351
352    runSearch(q, method = 'includes') {
353        const source = this.state.all; 
354        const filtered = source.filter(i => {
355            return method === 'startsWith' ? i.s.startsWith(q) : i.s.includes(q);
356        });
357        this.render(filtered);
358    },
359
360    async fetchGoogle() {
361        if (this.state.all.length === 0) {
362             this.renderMsg("Загрузка из резерва...");
363        }
364
365        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`;
366        try {
367            const txt = await (await fetch(url)).text();
368            const json = JSON.parse(txt.match(/setResponse\(([\s\S]*)\);/)[1]);
369            const rows = json.table.rows || [];
370            
371            let tempAll = [];
372            
373            rows.forEach(r => {
374                const item = {
375                    n: r.c[0]?.v, 
376                    s: (r.c[1]?.v||"").toLowerCase(), 
377                    en: !!r.c[2]?.v, 
378                    l: r.c[3]?.v, 
379                    id: r.c[6]?.v
380                };
381                if (item.n && item.l) {
382                    tempAll.push(item);
383                }
384            });
385
386            tempAll.sort((a, b) => a.n.localeCompare(b.n));
387
388            this.state.all = tempAll;
389            this.state.active = tempAll.filter(x => x.en);
390            
391            // Вывод об успешной загрузке из Google
392            console.log(`[regions] Loaded from Google: ${this.state.all.length} rows`);
393
394            this.finishLoad(true);
395            
396            if (this.state.searchRaw.length >= 2) this.runSearch(this.state.searchRaw);
397            else this.render(this.state.active);
398
399        } catch(e) {
400            console.error(e);
401            this.renderMsg("Ошибка загрузки");
402        }
403    },
404
405    saveCache() {
406        try {
407            localStorage.setItem(this.cfg.key, JSON.stringify({
408                v: ""+Date.now(),
409                all: this.state.all,
410                active: this.state.active
411            }));
412            document.cookie = `${this.cfg.cookie}=${Date.now()}; path=/; domain=${this.cfg.domain}; max-age=86400; SameSite=Lax`;
413        } catch(e){}
414    },
415    
416    loadCache() {
417        try {
418            const d = JSON.parse(localStorage.getItem(this.cfg.key));
419            if (!d || Date.now() - parseInt(d.v) > this.cfg.ttl) return null;
420            return d;
421        } catch(e) { return null; }
422    },
423    
424    getCookie(n) { const m = document.cookie.match(new RegExp(`(^| )${n}=([^;]+)`)); return m ? m[2] : null; }
425};
426
427$(() => 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.