PageSourceSearch

https://skopelos.com/wp-content/mu-plugins/skp-unified-switcher/switcher.js?ver=2.0.1

js skopelos.com collected 2026-10-02 21:52:09 UTC 13,005 bytes, 329 lines download raw bytes

1/**
2 * SKP Unified Language Switcher — Production JS
3 * Dropdown interaction. Zero dependencies.
4 *
5 * v1.1 — Portal pattern for mobile bottom-sheet
6 * v1.2 — Cookie persistence: remembers selected language across page loads.
7 *         GTranslate pages bypass WordPress so PHP can't detect Tier 2
8 *         languages. Cookie + JS toggle update solves this.
9 * v1.3 — JS-constructed URLs: GTranslate mangles href attributes by
10 *         prepending the current lang prefix. Option clicks now build
11 *         clean URLs from scratch instead of following mangled hrefs.
12 * v1.4 — URL is authoritative for the displayed language. Previously a
13 *         stale skp_lang=el cookie repainted the toggle to GR on English
14 *         pages (visit /el/, strip prefix, land on / → EN flag "hiccuped"
15 *         to GR). Non-prefixed path now always renders EN and clears any
16 *         non-en cookie. Also removes the dead /el/ regex branch —
17 *         detectLangFromURL() already handles el.
18 */
19(function () {
20    'use strict';
21
22    var MQ_MOBILE   = '(max-width: 640px)';
23    var COOKIE_NAME = 'skp_lang';
24    var COOKIE_DAYS = 30;
25    var FLAG_BASE   = 'https://flagcdn.com/w40/';
26    var backdrop    = null;
27
28    /* ============================================================
29     * Language data (must match PHP arrays)
30     * ============================================================ */
31    var LANG_MAP = {
32        en: 'gb', el: 'gr', fr: 'fr', de: 'de', it: 'it', nl: 'nl',
33        ro: 'ro', pt: 'pt', es: 'es', tr: 'tr', sv: 'se', ru: 'ru',
34        pl: 'pl', sl: 'si', af: 'za', sq: 'al', az: 'az', be: 'by',
35        bs: 'ba', bg: 'bg', hr: 'hr', cs: 'cz', da: 'dk', et: 'ee',
36        fi: 'fi', ka: 'ge', iw: 'il', hu: 'hu', ga: 'ie', ja: 'jp',
37        lv: 'lv', lt: 'lt', lb: 'lu', mk: 'mk', fa: 'ir', no: 'no',
38        sr: 'rs', sk: 'sk', uk: 'ua'
39    };
40
41    /* ============================================================
42     * Cookie helpers
43     * ============================================================ */
44    function setCookie(code) {
45        var d = new Date();
46        d.setTime(d.getTime() + COOKIE_DAYS * 86400000);
47        document.cookie = COOKIE_NAME + '=' + encodeURIComponent(code) +
48            ';expires=' + d.toUTCString() +
49            ';path=/;SameSite=Lax';
50    }
51
52    function getCookie() {
53        var match = document.cookie.match(new RegExp('(?:^|;\\s*)' + COOKIE_NAME + '=([^;]*)'));
54        return match ? decodeURIComponent(match[1]) : null;
55    }
56
57    function clearCookie() {
58        document.cookie = COOKIE_NAME + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/;SameSite=Lax';
59    }
60
61    /* ============================================================
62     * Detect current language from URL prefix
63     * Works on GTranslate proxy pages where PHP can't detect
64     * ============================================================ */
65    function detectLangFromURL() {
66        var path = window.location.pathname.replace(/^\/+/, '');
67        var seg = path.split('/')[0];
68        if (seg && LANG_MAP[seg]) return seg;
69        return null;
70    }
71
72    /* ============================================================
73     * Update toggle button flag + code on ALL visible switchers
74     * ============================================================ */
75    function updateAllToggles(code) {
76        var flag = LANG_MAP[code];
77        if (!flag) return;
78
79        document.querySelectorAll('.skp-lang').forEach(function (sw) {
80            var flagImg  = sw.querySelector('.skp-lang__toggle .skp-flag');
81            var codeSpan = sw.querySelector('.skp-lang__toggle .skp-lang__code');
82            if (flagImg)  flagImg.src = FLAG_BASE + flag + '.png';
83            if (codeSpan) codeSpan.textContent = code.toUpperCase();
84
85            // Update active state in dropdown options
86            sw.querySelectorAll('.skp-lang__option').forEach(function (opt) {
87                var isActive = opt.getAttribute('data-lang') === code;
88                opt.classList.toggle('skp-lang__option--active', isActive);
89                opt.setAttribute('aria-selected', isActive ? 'true' : 'false');
90                // Add/remove checkmark
91                var check = opt.querySelector('.skp-lang__check');
92                if (isActive && !check) {
93                    var span = document.createElement('span');
94                    span.className = 'skp-lang__check';
95                    span.innerHTML = '✓';
96                    opt.appendChild(span);
97                } else if (!isActive && check) {
98                    check.remove();
99                }
100            });
101        });
102    }
103
104    /* ============================================================
105     * Mobile helpers (portal pattern)
106     * ============================================================ */
107    function isMobile() {
108        return window.matchMedia(MQ_MOBILE).matches;
109    }
110
111    function getBackdrop() {
112        if (!backdrop) {
113            backdrop = document.createElement('div');
114            backdrop.className = 'skp-lang__backdrop';
115            backdrop.addEventListener('click', function () {
116                document.querySelectorAll('.skp-lang[data-open="true"]').forEach(function (sw) {
117                    sw.dataset.open = 'false';
118                    var t = sw.querySelector('.skp-lang__toggle');
119                    if (t) t.setAttribute('aria-expanded', 'false');
120                });
121                hideBackdrop();
122                returnAllDropdowns();
123            });
124        }
125        return backdrop;
126    }
127
128    function showBackdrop() {
129        var bd = getBackdrop();
130        if (!bd.parentElement) document.body.appendChild(bd);
131        bd.offsetHeight;
132        bd.classList.add('skp-lang__backdrop--active');
133    }
134
135    function hideBackdrop() {
136        if (backdrop) {
137            backdrop.classList.remove('skp-lang__backdrop--active');
138            setTimeout(function () {
139                if (backdrop && backdrop.parentElement && !backdrop.classList.contains('skp-lang__backdrop--active')) {
140                    backdrop.parentElement.removeChild(backdrop);
141                }
142            }, 300);
143        }
144    }
145
146    function returnAllDropdowns() {
147        document.querySelectorAll('.skp-lang__dropdown[data-portaled]').forEach(function (dd) {
148            var switcherId = dd.getAttribute('data-portaled');
149            var sw = document.querySelector('.skp-lang[data-switcher-id="' + switcherId + '"]');
150            if (sw) {
151                dd.removeAttribute('data-portaled');
152                dd.classList.remove('skp-lang__dropdown--portaled');
153                sw.appendChild(dd);
154            }
155        });
156    }
157
158    /* ============================================================
159     * Init
160     * ============================================================ */
161    function init() {
162        // The URL is the single source of truth for the displayed language:
163        // every non-EN surface (WPML /el/, GTranslate /fr/ …) is path-prefixed,
164        // so a non-prefixed path IS the English page. The cookie only records
165        // the last choice — it must never repaint the toggle against the URL.
166        var urlLang    = detectLangFromURL();
167        var cookieLang = getCookie();
168        var activeLang = urlLang || 'en';
169
170        if (urlLang) {
171            setCookie(urlLang);
172        } else if (cookieLang && cookieLang !== 'en') {
173            // Stale cookie from a prefixed page (user stripped /el/, or
174            // clicked the logo from /fr/) — drop it so it can't leak later.
175            clearCookie();
176        }
177
178        // Update toggle UI if language differs from PHP-rendered default
179        if (activeLang && activeLang !== 'en') {
180            updateAllToggles(activeLang);
181        }
182
183        // Init each switcher instance
184        document.querySelectorAll('.skp-lang').forEach(function (sw, idx) {
185            sw.setAttribute('data-switcher-id', 'sw-' + idx);
186            initSwitcher(sw);
187        });
188    }
189
190    function initSwitcher(switcher) {
191        var toggle   = switcher.querySelector('.skp-lang__toggle');
192        var dropdown = switcher.querySelector('.skp-lang__dropdown');
193        if (!toggle || !dropdown) return;
194
195        var switcherId = switcher.getAttribute('data-switcher-id');
196
197        function portalToBody() {
198            if (!isMobile()) return;
199            dropdown.setAttribute('data-portaled', switcherId);
200            dropdown.classList.add('skp-lang__dropdown--portaled');
201            document.body.appendChild(dropdown);
202            showBackdrop();
203            document.body.classList.add('skp-lang-sheet-open');
204        }
205
206        function returnToSwitcher() {
207            if (dropdown.hasAttribute('data-portaled')) {
208                dropdown.removeAttribute('data-portaled');
209                dropdown.classList.remove('skp-lang__dropdown--portaled');
210                switcher.appendChild(dropdown);
211                hideBackdrop();
212                document.body.classList.remove('skp-lang-sheet-open');
213            }
214        }
215
216        function open() {
217            document.querySelectorAll('.skp-lang[data-open="true"]').forEach(function (sw) {
218                if (sw !== switcher) {
219                    sw.dataset.open = 'false';
220                    var t = sw.querySelector('.skp-lang__toggle');
221                    if (t) t.setAttribute('aria-expanded', 'false');
222                }
223            });
224            returnAllDropdowns();
225            switcher.dataset.open = 'true';
226            toggle.setAttribute('aria-expanded', 'true');
227            portalToBody();
228        }
229
230        function close() {
231            switcher.dataset.open = 'false';
232            toggle.setAttribute('aria-expanded', 'false');
233            returnToSwitcher();
234        }
235
236        function isOpen() {
237            return switcher.dataset.open === 'true';
238        }
239
240        // Toggle click
241        toggle.addEventListener('click', function (e) {
242            e.stopPropagation();
243            isOpen() ? close() : open();
244        });
245
246        // Option click — construct clean URL (GTranslate mangles hrefs)
247        dropdown.addEventListener('click', function (e) {
248            var opt = e.target.closest('.skp-lang__option');
249            if (!opt) return;
250            e.preventDefault();
251
252            var targetLang = opt.getAttribute('data-lang');
253            if (!targetLang) return;
254
255            setCookie(targetLang);
256
257            // Strip any current language prefix from path
258            var path = window.location.pathname;
259            var allCodes = Object.keys(LANG_MAP);
260            var stripRegex = new RegExp('^/(' + allCodes.join('|') + ')/');
261            var cleanPath = path.replace(stripRegex, '/');
262
263            // Build target URL
264            var targetUrl;
265            if (targetLang === 'en') {
266                // EN = no prefix (root)
267                targetUrl = cleanPath;
268            } else {
269                // EL (WPML) or Tier 2 (GTranslate) = /{lang}/path
270                targetUrl = '/' + targetLang + cleanPath;
271            }
272
273            close();
274            window.location.href = targetUrl;
275        });
276
277        // Close on outside click
278        document.addEventListener('click', function (e) {
279            if (!e.target.closest('.skp-lang') && !e.target.closest('.skp-lang__dropdown')) {
280                close();
281            }
282        });
283
284        // Keyboard: open
285        toggle.addEventListener('keydown', function (e) {
286            if (['ArrowDown', 'Enter', ' '].indexOf(e.key) !== -1) {
287                e.preventDefault();
288                open();
289                var first = dropdown.querySelector('.skp-lang__option');
290                if (first) first.focus();
291            }
292        });
293
294        // Keyboard: navigate options
295        dropdown.addEventListener('keydown', function (e) {
296            var opts = Array.prototype.slice.call(dropdown.querySelectorAll('.skp-lang__option'));
297            var idx  = opts.indexOf(document.activeElement);
298
299            if (e.key === 'ArrowDown') {
300                e.preventDefault();
301                if (opts[idx + 1]) opts[idx + 1].focus();
302            } else if (e.key === 'ArrowUp') {
303                e.preventDefault();
304                if (idx > 0) opts[idx - 1].focus();
305                else { close(); toggle.focus(); }
306            } else if (e.key === 'Escape') {
307                e.preventDefault();
308                close();
309                toggle.focus();
310            } else if (e.key === 'Enter') {
311                e.preventDefault();
312                if (document.activeElement) document.activeElement.click();
313            }
314        });
315
316        // Make options focusable
317        var options = dropdown.querySelectorAll('.skp-lang__option');
318        for (var i = 0; i < options.length; i++) {
319            options[i].setAttribute('tabindex', '-1');
320        }
321    }
322
323    // Boot
324    if (document.readyState === 'loading') {
325        document.addEventListener('DOMContentLoaded', init);
326    } else {
327        init();
328    }
329})();

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.