PageSourceSearch

https://ryokoukankou.com/singapore/js/map-article-ui.js?v=20260914-5

js ryokoukankou.com collected 2026-09-24 23:22:05 UTC 12,178 bytes, 213 lines download raw bytes

1(function (window, document) {
2    'use strict';
3    function init() {
4        var canvas = document.getElementById('map_latercanvas');
5        if (!canvas || canvas.dataset.articleUi) return;
6        canvas.dataset.articleUi = '1';
7        var origin = [Number(window.Mainlatitude), Number(window.Mainlongitude)];
8        var categories = [
9            ['sightseeing', '観光', 'mapsigli', 'flgsight', 'gmaporgclick'],
10            ['restaurant', 'レストラン', 'mapresli', 'flgres', 'gmapresorgclick'],
11            ['hotel', 'ホテル', 'maphotli', 'flghot', 'gmaphotorgclick']
12        ];
13        var layouts = [];
14        var refreshers = [];
15        function isFullscreen() { return canvas.style.position === 'fixed'; }
16        var historyId = 'map-' + Date.now() + '-' + Math.random().toString(36).slice(2);
17        var handlingHistory = false, closingHistory = false, reopenAfterBack = false;
18        function ownsHistory(state) { return state && state.ryokouArticleMap === historyId; }
19        window.addEventListener('popstate', function (event) {
20            closingHistory = false;
21            handlingHistory = true;
22            try {
23                if (ownsHistory(event.state)) {
24                    if (!isFullscreen()) window.maptobig();
25                } else if (isFullscreen()) {
26                    ['map-area-guide', 'map-search-dialog'].forEach(function (id) {
27                        var dialog = document.getElementById(id);
28                        if (dialog && dialog.open) dialog.close();
29                    });
30                    window.maptosmall();
31                }
32            } finally { handlingHistory = false; }
33            if (reopenAfterBack) { reopenAfterBack = false; window.maptobig(); }
34        });
35        function layout() { layouts.forEach(function (fn) { fn(); }); }
36        function centerOrigin() {
37            var map = window.map;
38            if (!map || !map.invalidateSize || !origin.every(Number.isFinite)) return;
39            map.invalidateSize({pan: false});
40            map.setView(origin, map.getZoom(), {animate: false});
41        }
42        // Legacy fullscreen used physical screen dimensions, which exceed the viewport.
43        if (typeof window.maptobig === 'function') {
44            var openFullscreen = window.maptobig;
45            window.maptobig = function () {
46                if (closingHistory) { reopenAfterBack = true; return; }
47                if (isFullscreen()) return;
48                openFullscreen.apply(this, arguments);
49                if (!handlingHistory && window.history && window.history.pushState) {
50                    window.history.pushState({ryokouArticleMap: historyId}, '', window.location.href);
51                }
52                canvas.style.width = '100%';
53                canvas.style.height = '100dvh';
54                if (!canvas.style.height || canvas.style.height.indexOf('dvh') === -1) canvas.style.height = window.innerHeight + 'px';
55                window.setTimeout(function () { centerOrigin(); layout(); }, 0);
56            };
57        }
58        if (typeof window.maptosmall === 'function') {
59            var closeFullscreen = window.maptosmall;
60            window.maptosmall = function () {
61                if (!isFullscreen()) return;
62                closeFullscreen.apply(this, arguments);
63                if (!handlingHistory && ownsHistory(window.history.state)) {
64                    closingHistory = true;
65                    window.history.back();
66                }
67                layout();
68            };
69        }
70        var image = document.getElementById('mapfirstimg');
71        if (image && typeof window.mapdtopenclc === 'function' && typeof window.ryokouLoadMapLibraries === 'function') {
72            image.onclick = function () {
73                window.mapdtopenclc();
74                window.ryokouLoadMapLibraries(function () {
75                    if (!isFullscreen()) window.mapbig();
76                    refreshers.forEach(function (fn) { fn(); });
77                });
78            };
79        }
80        categories.forEach(function (category) {
81            var button = document.getElementById(category[2]);
82            var list = document.getElementById(category[4]);
83            if (!button || !list) return;
84            function updateButton() {
85                var active = window[category[3]] === 1;
86                button.classList.toggle('map-category-active', active);
87                button.setAttribute('aria-pressed', String(active));
88            }
89            refreshers.push(updateButton);
90            button.addEventListener('click', updateButton);
91            button.setAttribute('role', 'button');
92            button.setAttribute('tabindex', '0');
93            button.addEventListener('keydown', function (event) {
94                if (event.key === 'Enter' || event.key === ' ') {
95                    event.preventDefault(); button.click();
96                }
97            });
98            new MutationObserver(updateButton).observe(button, {
99                childList: true, subtree: true, attributes: true, attributeFilter: ['style']
100            });
101            updateButton();
102
103            var key = 'ryokou-map-sort-' + category[0];
104            var mode = 'distance';
105            try { mode = window.localStorage.getItem(key) || mode; } catch (_) {}
106            var modes = ['distance', 'rating', 'price', 'priceDesc'].concat(category[0] === 'hotel' ? ['stars'] : []);
107            if (modes.indexOf(mode) === -1) mode = 'distance';
108            var toolbar = document.createElement('div');
109            toolbar.className = 'map-sort-toolbar';
110            toolbar.hidden = true;
111            var select = document.createElement('select');
112            select.id = 'map-sort-' + category[0];
113            select.setAttribute('aria-label', category[1] + 'の並び順');
114            [['distance', '距離順'], ['rating', 'おすすめ順'], ['price', '安い順'], ['priceDesc', '高い順']].concat(category[0] === 'hotel' ? [['stars', '星ランク順']] : []).forEach(function (option) {
115                select.add(new Option(option[1], option[0]));
116            });
117            select.value = mode;
118            toolbar.appendChild(select);
119            list.parentNode.insertBefore(toolbar, list);
120            function positionToolbar() {
121                toolbar.hidden = list.style.display === 'none' || !list.children.length;
122                if (toolbar.hidden) return;
123                if (isFullscreen()) {
124                    var rect = list.getBoundingClientRect();
125                    toolbar.style.position = 'fixed';
126                    toolbar.style.left = Math.max(0, rect.left) + 'px';
127                    var right = Math.min(rect.right, window.innerWidth);
128                    var item = list.querySelector('li');
129                    if (window.innerWidth >= 768 && item) {
130                        // Align with the card grid, whose last column ends before the full-width UL.
131                        var style = window.getComputedStyle(item);
132                        var listStyle = window.getComputedStyle(list);
133                        var padding = parseFloat(listStyle.paddingLeft) || 0;
134                        var available = list.clientWidth - padding - (parseFloat(listStyle.paddingRight) || 0);
135                        var width = style.width.endsWith('%') ? available * parseFloat(style.width) / 100 : parseFloat(style.width);
136                        if (style.boxSizing !== 'border-box') width += (parseFloat(style.paddingLeft) || 0) + (parseFloat(style.paddingRight) || 0) + (parseFloat(style.borderLeftWidth) || 0) + (parseFloat(style.borderRightWidth) || 0);
137                        var marginLeft = parseFloat(style.marginLeft) || 0, marginRight = parseFloat(style.marginRight) || 0;
138                        var step = width + marginLeft + marginRight;
139                        if (step > 0) {
140                            var columns = Math.max(1, Math.floor((available + 0.1) / step));
141                            right = Math.min(right, rect.left + list.clientLeft + padding + columns * step - marginRight);
142                            right += parseFloat(window.getComputedStyle(toolbar).paddingRight) || 0;
143                        }
144                    }
145                    toolbar.style.width = Math.max(0, Math.min(right, window.innerWidth) - Math.max(0, rect.left)) + 'px';
146                    toolbar.style.top = Math.max(0, rect.top - toolbar.offsetHeight) + 'px';
147                } else {
148                    toolbar.style.position = 'relative';
149                    toolbar.style.left = '';
150                    toolbar.style.top = '';
151                    toolbar.style.width = '100%';
152                }
153            }
154            layouts.push(positionToolbar);
155            function value(element, field) {
156                if (field === 'distance') {
157                    var match = element.textContent.match(/距離\s*[::]\s*([\d,]+)\s*m/);
158                    return match ? Number(match[1].replace(/,/g, '')) : null;
159                }
160                var raw = element.getAttribute('data-map-' + (field === 'priceDesc' ? 'price' : f
160ield));
161                return raw !== null && raw !== '' && Number.isFinite(Number(raw)) ? Number(raw) : null;
162            }
163            var observer = new MutationObserver(function (records) {
164                if (records.some(function (record) { return record.type === 'childList'; })) sort();
165                else positionToolbar();
166            });
167            function observeList() { observer.observe(list, {childList: true, attributes: true, attributeFilter: ['style']}); }
168            function sort() {
169                var items = Array.from(list.children).filter(function (item) { return item.tagName === 'LI'; });
170                var sorted = items.map(function (item, index) { return {item: item, index: index}; });
171                sorted.sort(function (a, b) {
172                    var av = value(a.item, mode), bv = value(b.item, mode);
173                    if (av === null && bv !== null) return 1;
174                    if (bv === null && av !== null) return -1;
175                    if (av !== bv) return ['rating', 'priceDesc', 'stars'].indexOf(mode) !== -1 ? bv - av : av - bv;
176                    var ad = value(a.item, 'distance'), bd = value(b.item, 'distance');
177                    return (ad === null ? Infinity : ad) - (bd === null ? Infinity : bd) || a.index - b.index;
178                });
179                if (sorted.some(function (entry, index) { return entry.item !== items[index]; })) {
180                    observer.disconnect();
181                    var fragment = document.createDocumentFragment();
182                    sorted.forEach(function (entry) { fragment.appendChild(entry.item); });
183                    list.appendChild(fragment);
184                    observeList();
185                }
186                positionToolbar();
187            }
188            select.addEventListener('change', function () {
189                mode = select.value;
190                try { window.localStorage.setItem(key, mode); } catch (_) {}
191                sort(); list.scrollTop = 0;
192            });
193            observeList(); sort();
194        });
195        new MutationObserver(function () {
196            canvas.querySelectorAll('.leaflet-popup-content').forEach(function (popup) {
197                var walker = document.createTreeWalker(popup, NodeFilter.SHOW_TEXT), text;
198                while ((text = walker.nextNode())) {
199                    var updated = text.nodeValue.replace(/(お勧め度[::]\s*)(☆+)/g, function (_, label, stars) {
200                        return label + stars.replace(/☆/g, '⭐️');
201                    });
202                    if (updated !== text.nodeValue) text.nodeValue = updated;
203                }
204            });
205        }).observe(canvas, {childList: true, subtree: true, characterData: true});
206        window.addEventListener('resize', function () {
207            if (isFullscreen() && window.map && window.map.invalidateSize) window.map.invalidateSize();
208            layout();
209        });
210    }
211    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
212    else init();
213}(window, document));

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.