PageSourceSearch

https://www.hikemates.com/assets/pages/search-input-sgH0vAe.js

js hikemates.com collected 2026-10-08 23:51:45 UTC 13,139 bytes, 307 lines download raw bytes

1function debounce(fn, wait) {
2    let timeout;
3
4    return function debounced(...args) {
5        const context = this;
6        clearTimeout(timeout);
7        timeout = setTimeout(() => fn.apply(context, args), wait);
8    };
9}
10
11const template = `
12    <div id="search" :class="['search', { 'is-active': isActive }]">
13        <div class="search__overlay" v-cloak v-if="isActive" @click="isActive = false"></div>
14        <form class="search__form" :class="{ 'is-active': isActive }" method="get" action="/search" @submit="saveQuery" @keydown.enter.prevent="submitFormIfKeyboard" ref="form">
15            <div class="search__head">
16                <button class="search__mobile-close" type="button" @click="isActive = false" v-if="isActive" v-cloak><svg class="icon icon-close"><use xlink:href="#icon-close"></use></svg></button>
17                <label class="search__mobile-open" for="search-input"><svg class="icon icon-search"><use xlink:href="#icon-search"></use></svg></label>
18                <input :value="query" @input="loading = true; query = $event.target.value" @focus="isActive = true" id="search-input" class="search__input" type="search" name="q" placeholder="Vyhľadaj trasu - skús Rysy" autocomplete="off">
19                <button type="submit" class="button submit">Vyhľadať</button>
20            </div>
21
22            <div class="search__live-panel" v-cloak v-if="isActive">
23                <div class="search__live-results" :class="{ 'is-loading': loading }">
24                    <span class="search__fail" v-if="query.length > 2 && !loading && !resultsCount">
25                        <svg id="SVGDoc" width="105" height="55" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 105 55"><g opacity="0.1"><path d="M95.00083,40.32v0l-10.11083,14.7v0h20.16v0z" fill="#000000" fill-opacity="1"></path><path d="M27.1516,33.44828v0v20.18874v0l-13.12343,-19.00967v0l-13.92817,20.35442v0h27.0516v0h0.92855v0h54.22763v0l-28.97121,-54.98177v0z" fill="#000000" fill-opacity="1"></path></g></svg>
26                        <strong>Žiadne výsledky</strong>
27                        <small>Skús zadať iný výraz</small>
28                    </span>
29                    <ul v-if="resultsCount">
30                        <li v-for="result in results" :key="result.id">
31                            <div v-if="result.type === 'city'">
32                                <svg class="icon icon-city"><use xlink:href="#icon-city"></use></svg>
33                                <strong class="headline">{{ result.name }} <span>+ 50km</span><small>trasy v okruhu</small></strong>
34                                <ul class="simple menu">
35                                    <li><a @click="saveQuery(result.name)" :href="'/search?osm_id=' + result.id + '&radius=20'" class="is-default">do 20km</a></li>
36                                    <li><a @click="saveQuery(result.name)" :href="'/search?osm_id=' + result.id + '&radius=50'">do 50km</a></li>
37                                    <li><a @click="saveQuery(result.name)" :href="'/search?osm_id=' + result.id + '&radius=120'">do 120km</a></li>
38                                </ul>
39                            </div>
40                            <a v-else-if="result.type === 'district'" @click="saveQuery(null)" :href="'/search?d=' + result.name">
41                                <svg class="icon icon-region"><use xlink:href="#icon-region"></use></svg>
42                                <strong class="headline" v-if="result.name.includes('okres')">{{ result.name }}<small>trasy v okrese</small></strong>
43                                <strong class="headline" v-else>{{ result.name }}<small>trasy v meste</small></strong>
44                            </a>
45                            <a v-else-if="result.type === 'random'" href="/search">
46                                <svg class="icon icon-search-random"><use xlink:href="#icon-search-random"></use></svg>
47                                <strong class="headline">{{ result.name }}</strong>
48                            </a>
49                            <div v-else-if="result.type === 'location'">
50                                <svg class="icon icon-search-location"><use xlink:href="#icon-search-location"></use></svg>
51                                <strong class="headline">{{ result.name }} <span>+ 50km</span><small>trasy v okruhu</small></strong>
52                                <ul class="simple menu">
53                                    <li><a @click.prevent="searchNearMe(20)" href="#" class="is-default">do 20km</a></li>
54                                    <li><a @click.prevent="searchNearMe(50)" href="#">do 50km</a></li>
55                                    <li><a @click.prevent="searchNearMe(120)" href="#">do 120km</a></li>
56                                </ul>
57                                <small v-if="locationLoading">Zisťujem tvoju polohu…</small>
58                                <small v-if="locationError" class="search__location-error">{{ locationError }}</small>
59                            </div>
60                            <div v-else-if="result.type === 'loader'" class="result is-loading"></div>
61                            <a v-else :href="'/hike/' + result.url" target="_blank">
62                                <svg class="icon icon-steps"><use xlink:href="#icon-steps"></use></svg>
63                                <strong class="headline">{{ result.name }} {{ result.subtitle }}<small>{{ result.mountain_range }}</small></strong>
64                            </a>
65                        </li>
66                        <li>
67                            <a class="all" v-if="hikesCount" @click="submitForm">Zobraziť všetky výsledky ({{ hikesCount }})</a>
68                        </li>
69                    </ul>
70                </div>
71            </div>
72        </form>
73    </div>
74`;
75
76const SearchInputComponent = {
77    template,
78    data() {
79        return {
80            query: '',
81            resultsCount: 0,
82            hikesCount: 0,
83            results: [],
84            loading: false,
85            isActive: false,
86            request: null,
87            locationLoading: false,
88            locationError: null,
89            initialResults: [
90                {
91                    id: -999999,
92                    name: 'Kamkoľvek',
93                    type: 'random',
94                },
95                {
96                    id: -99999999,
97                    name: 'Tvoja poloha',
98                    type: 'location',
99                },
100                {
101                    id: -9999999,
102                    type: 'loader',
103                },
104            ],
105            hintsLoaded: false,
106        };
107    },
108    methods: {
109        send() {
110            if (this.request) this.cancel();
111            this.makeRequest();
112        },
113
114        cancel() {
115            this.request.cancel();
116            this.request = null;
117        },
118
119        makeRequest: debounce(function makeRequest() {
120            const axiosSource = axios.CancelToken.source();
121            const config = {
122                method: 'get',
123                url: '/search-live',
124                params: { q: this.query },
125                headers: { 'X-Requested-With': 'XMLHttpRequest' },
126                cancelToken: axiosSource.token,
127            };
128            this.request = { cancel: axiosSource.cancel };
129
130            axios(config).then((response) => {
131                this.results = response.data.results;
132                this.resultsCount = response.data.count;
133                this.hikesCount = response.data.hikes_count;
134                this.loading = false;
135                this.request = null;
136            }).catch(this.logResponseErrors);
137        }, 150),
138
139        logResponseErrors(err) {
140            if (axios.isCancel(err)) {
141                return;
142            }
143
144            this.loading = false;
145            this.results = [];
146        },
147
148        saveQuery(title) {
149            if (title !== null) {
150                localStorage.setItem('search-title', title);
151            }
152            localStorage.setItem('search-query', this.query);
153            return true;
154        },
155
156        submitFormIfKeyboard() {
157            if (whatInput.ask() === 'keyboard' || whatInput.ask() === 'mouse') {
158                this.submitForm();
159            }
160        },
161
162        submitForm() {
163            this.saveQuery(null);
164            this.$refs.form.submit();
165        },
166
167        /*
168         * Ask for geolocation permission and navigate to the search results for the
169         * given radius around the user. Permission is requested only here, when the
170         * user actively picks "Tvoja poloha" - never proactively on page load.
171         */
172        searchNearMe(radius) {
173            if (!navigator.geolocation) {
174                this.locationError = 'Geolokácia nie je podporovaná tvojím prehliadačom.';
175                return;
176            }
177
178            this.locationError = null;
179            this.locationLoading = true;
180
181            const options = {
182                enableHighAccuracy: true,
183                timeout: 10000,
184                maximumAge: 5000,
185            };
186
187            navigator.geolocation.getCurrentPosition((position) => {
188                this.saveQuery('Tvoja poloha');
189                window.location.href = '/search?lat=' + position.coords.latitude + '&lon=' + position.coords.longitude + '&radius=' + radius;
190            }, (err) => {
191                this.locationLoading = false;
192                this.locationError = 'Nepodarilo sa zistiť tvoju polohu. Skontroluj povolenia v prehliadači.';
193                console.log(err.message);
194            }, options);
195        },
196
197        loadHints() {
198            if (this.request) {
199                this.request.cancel();
200                this.request = null;
201            }
202
203            const axiosSource = axios.CancelToken.source();
204            const config = {
205                method: 'get',
206                url: '/search-hint',
207                headers: { 'X-Requested-With': 'XMLHttpRequest' },
208                cancelToken: axiosSource.token,
209            };
210            this.request = { cancel: axiosSource.cancel };
211
212            axios(config).then((response) => {
213                const results = response.data.results;
214
215                // The server only includes a "Tvoja poloha" entry when it already knows
216                // the user's position, which we deliberately don't send here - see
217                // searchNearMe(). Add the entry ourselves so it's always offered.
218                if (!results.some((result) => result.type === 'location')) {
219                    results.splice(1, 0, {
220                        id: -99999999,
221                        name: 'Tvoja poloha',
222                        type: 'location',
223                    });
224                }
225
226                this.results = results;
227                this.initialResults = results;
228                this.resultsCount = 1;
229                this.hikesCount = 0;
230                this.loading = false;
231                this.request = null;
232                this.hintsLoaded = true;
233            }).catch(this.logResponseErrors);
234        },
235
236        showInitialResults() {
237            this.results = this.initialResults;
238            this.resultsCount = 1;
239            this.hikesCount = 0;
240
241            if (!this.hintsLoaded) {
242                this.loadHints();
243            }
244        },
245
246        getUrlVars() {
247            const vars = {};
248            window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, (match, key, value) => {
249                vars[key] = value;
250                return match;
251            });
252            return vars;
253        },
254    },
255    watch: {
256        query(val) {
257            this.results = [];
258            this.resultsCount = 0;
259            this.hikesCount = 0;
260
261            if (val.length > 2) {
262                this.send();
263            } else if (val.length === 0) {
264                this.loading = false;
265                this.showInitialResults();
266            } else {
267                this.loading = false;
268            }
269        },
270
271        isActive(val) {
272            document.body.style.overflow = val ? 'hidden' : '';
273
274            if (val && !this.query.length) {
275                this.showInitialResults();
276            }
277        },
278    },
279    mounted() {
280        if (localStorage.getItem('search-query')) {
281            this.query = localStorage.getItem('search-query');
282            localStorage.removeItem('search-query');
283            return;
284        }
285
286        const urlParams = this.getUrlVars();
287        if (urlParams.q !== undefined) {
288            this.query = decodeURI(urlParams.q);
289        } else if (urlParams.d !== undefined) {
290            this.query = decodeURI(urlParams.d);
291        } else if (urlParams.osm_id !== undefined) {
292            if ($('#search_osm_name').length && $('#search_osm_name').val() !== undefined) {
293                this.query = $('#search_osm_name').val();
294            }
295        }
296    },
297};
298
299const Vue = window.Vue;
300
301if (!Vue) {
302    throw new Error('Vue is not available on window. Ensure base template loads Vue before search-input module.');
303}
304
305new Vue({
306    render: (h) => h(SearchInputComponent),
307}).$mount('#search');

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.