PageSourceSearch

https://pro-jazz.com/templates/default/js/live_search.js?1790790899

js pro-jazz.com collected 2026-10-02 14:20:56 UTC 10,750 bytes, 250 lines download raw bytes

1/**
2 * Pro-Jazz Live Search
3 * Instant dropdown search with thumbnails and keyboard navigation
4 */
5(function($) {
6    'use strict';
7
8    var timer = null;
9    var currentXHR = null;
10    var lastQuery = '';
11    var $activeInput = null;
12    var $dropdown = null;
13    var activeIndex = -1;
14
15    var icons = {
16        mic: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="12" y1="19" x2="12" y2="23"></line><line x1="8" y1="23" x2="16" y2="23"></line></svg>',
17        disc: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="3"></circle></svg>',
18        video: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="23 7 16 12 23 17 23 7"></polygon><rect x="1" y="5" width="15" height="14" rx="2" ry="2"></rect></svg>',
19        news: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-2 2Zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2"></path><path d="M18 14h-8"></path><path d="M15 18h-5"></path><path d="M10 6h8v4h-8V6Z"></path></svg>',
20        tag: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7.01" y2="7"></line></svg>',
21        note: '<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/></svg>'
22    };
23
24    function initDropdown() {
25        if ($('#live-search-dropdown').length) {
26            $dropdown = $('#live-search-dropdown');
27            return;
28        }
29        $dropdown = $('<div id="live-search-dropdown" class="live-search-dropdown" style="display:none;"></div>');
30        $('body').append($dropdown);
31
32        $dropdown.on('mouseenter', '.live-search-item', function() {
33            $dropdown.find('.live-search-item').removeClass('is-selected');
34            $(this).addClass('is-selected');
35            activeIndex = parseInt($(this).data('index'), 10);
36        });
37    }
38
39    function positionDropdown() {
40        if (!$activeInput || !$dropdown || !$dropdown.is(':visible')) return;
41        var offset = $activeInput.offset();
42        var height = $activeInput.outerHeight();
43        var width = $activeInput.outerWidth();
44        var minW = 340;
45        var maxW = 480;
46        var targetW = Math.max(width, minW);
47        if (targetW > maxW && $(window).width() > 600) targetW = maxW;
48        if ($(window).width() <= 480) {
49            targetW = $(window).width() - 20;
50            offset.left = 10;
51        }
52
53        $dropdown.css({
54            top: (offset.top + height + 4) + 'px',
55            left: offset.left + 'px',
56            width: targetW + 'px'
57        });
58    }
59
60    function showLoading() {
61        if (!$dropdown) initDropdown();
62        positionDropdown();
63        $dropdown.html('<div class="live-search-loading"><span class="live-search-spinner"></span> Поиск...</div>').show();
64        if ($activeInput) $activeInput.addClass('live-search-busy');
65    }
66
67    function hideDropdown() {
68        if ($dropdown) {
69            $dropdown.hide().empty();
70        }
71        if ($activeInput) {
72            $activeInput.removeClass('live-search-busy');
73        }
74        activeIndex = -1;
75    }
76
77    function renderResults(data, query) {
78        if (!$dropdown) initDropdown();
79        if ($activeInput) $activeInput.removeClass('live-search-busy');
80
81        if (!data || !data.success || !data.categories || data.categories.length === 0) {
82            $dropdown.html('<div class="live-search-empty">По запросу &laquo;<b>' + escapeHtml(query) + '</b>&raquo; ничего не найдено</div>').show();
83            positionDropdown();
84            return;
85        }
86
87        var html = '<div class="live-search-content">';
88        var itemIdx = 0;
89
90        $.each(data.categories, function(ci, cat) {
91            var catIcon = icons[cat.icon] || icons.disc;
92            html += '<div class="live-search-section">';
93            html += '<div class="live-search-section-header">';
94            html += '<span class="live-search-section-title"><span class="cat-icon">' + catIcon + '</span> ' + escapeHtml(cat.title) + '</span>';
95            html += '<a href="' + cat.url + '" class="live-search-section-more">Все (' + cat.total + ') &rarr;</a>';
96            html += '</div>';
97
98            html += '<div class="live-search-section-list">';
99            $.each(cat.items, function(ii, item) {
100                var imgHtml = '';
101                if (item.image) {
102                    imgHtml = '<img src="' + item.image + '" alt="" loading="lazy" class="live-search-thumb" />';
103                } else {
104                    imgHtml = '<div class="live-search-thumb live-search-thumb-empty">' + (icons[cat.icon] || icons.note) + '</div>';
105                }
106
107                html += '<a href="' + item.url + '" class="live-search-item" data-index="' + itemIdx + '">';
108                html += imgHtml;
109                html += '<div class="live-search-info">';
110                html += '<div class="live-search-title">' + item.title_html + '</div>';
111                if (item.date) {
112                    html += '<div class="live-search-meta">' + item.date + '</div>';
113                }
114                html += '</div>';
115                html += '</a>';
116                itemIdx++;
117            });
118
119            html += '</div>';
120            html += '</div>';
121        });
122
123        html += '</div>';
124
125        if (data.total > 0 && data.all_url) {
126            html += '<a href="' + data.all_url + '" class="live-search-footer">';
127            html += 'Все результаты (' + data.total + ') &rarr;';
128            html += '</a>';
129        }
130
131        $dropdown.html(html).show();
132        activeIndex = -1;
133        positionDropdown();
134    }
135
136    function escapeHtml(str) {
137        return $('<div>').text(str || '').html();
138    }
139
140    function doSearch(query) {
141        if (query === lastQuery && $dropdown && $dropdown.is(':visible')) return;
142        lastQuery = query;
143
144        if (currentXHR) {
145            currentXHR.abort();
146            currentXHR = null;
147        }
148
149        showLoading();
150
151        currentXHR = $.ajax({
152            url: '/search/live',
153            type: 'GET',
154            data: { q: query },
155            dataType: 'json',
156            success: function(resp) {
157                currentXHR = null;
158                renderResults(resp, query);
159            },
160            error: function(xhr, status) {
161                if (status === 'abort') return;
162                currentXHR = null;
163                if ($activeInput) $activeInput.removeClass('live-search-busy');
164                $dropdown.html('<div class="live-search-empty">Ошибка при выполнении поиска</div>').show();
165                positionDropdown();
166            }
167        });
168    }
169
170    $(document).ready(function() {
171        initDropdown();
172
173        $(document).on('input keyup focus', 'input[name="q"]', function(e) {
174            var $this = $(this);
175            $activeInput = $this;
176            $this.attr('autocomplete', 'off');
177
178            var val = $.trim($this.val());
179
180            if (e.type === 'keyup') {
181                if (e.keyCode === 27) { // ESC
182                    hideDropdown();
183                    return;
184                }
185                if (e.keyCode === 40) { // DOWN
186                    if ($dropdown && $dropdown.is(':visible')) {
187                        var items = $dropdown.find('.live-search-item');
188                        if (items.length) {
189                            activeIndex = (activeIndex + 1) % items.length;
190                            items.removeClass('is-selected');
191                            var $curr = items.eq(activeIndex).addClass('is-selected');
192                            var container = $dropdown.find('.live-search-content');
193                            if (container.length) {
194                                container.scrollTop($curr.position().top + container.scrollTop() - 60);
195                            }
196                        }
197                        return false;
198                    }
199                }
200                if (e.keyCode === 38) { // UP
201                    if ($dropdown && $dropdown.is(':visible')) {
202                        var items = $dropdown.find('.live-search-item');
203                        if (items.length) {
204                            activeIndex = (activeIndex - 1 + items.length) % items.length;
205                            items.removeClass('is-selected');
206                            var $curr = items.eq(activeIndex).addClass('is-selected');
207                            var container = $dropdown.find('.live-search-content');
208                            if (container.length) {
209                                container.scrollTop($curr.position().top + container.scrollTop() - 60);
210                            }
211                        }
212                        return false;
213                    }
214                }
215                if (e.keyCode === 13) { // ENTER
216                    if ($dropdown && $dropdown.is(':visible') && activeIndex >= 0) {
217                        var $selected = $dropdown.find('.live-search-item.is-selected');
218                        if ($selected.length) {
219                            window.location.href = $selected.attr('href');
220                            return false;
221                        }
222                    }
223                    return true;
224                }
225            }
226
227            if (val.length < 2) {
228                hideDropdown();
229                lastQuery = '';
230                return;
231            }
232
233            clearTimeout(timer);
234            timer = setTimeout(function() {
235                doSearch(val);
236            }, 260);
237        });
238
239        $(document).on('click touchstart', function(e) {
240            if (!$(e.target).closest('#live-search-dropdown, input[name="q"]').length) {
241                hideDropdown();
242            }
243        });
244
245        $(window).on('resize scroll', function() {
246            positionDropdown();
247        });
248    });
249
250})(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.