Logo Voyage

MediaWiki:Gadget-TravelTracker.js Voyage Tips and guide

You can check the original Wikivoyage article Here
(function(mw, $) {
    var skin = mw.config.get('skin');
    var userName = mw.config.get('wgUserName');

    function injectHeaderLink() {
        if (userName === null) return;

        mw.loader.using(['mediawiki.util']).done(function() {
            if (skin === 'vector') {
                var $personalMenu = $('#p-personal ul');
                if (!$personalMenu.length) {
                    setTimeout(injectHeaderLink, 250);
                    return;
                }
                if ($('#pt-travelmap').length) return;

                mw.util.addPortletLink(
                    'p-personal',
                    mw.util.getUrl('User:' + userName + '/Visited'),
                    'My Travel Map',
                    'pt-travelmap',
                    'View or update your travel map',
                    null,
                    '#pt-preferences'
                );
            }

            if (skin === 'vector-2022') {
                if ($('#pt-travelmap-text').length) return;
                var $bell = $('#pt-notifications-notice, #pt-notifications-alert').first();
                if (!$bell.length) {
                    setTimeout(injectHeaderLink, 250);
                    return;
                }

                var $textLink = $('<li id="pt-travelmap-text" class="mw-list-item">' +
                    '<a href="' + mw.util.getUrl('User:' + userName + '/Visited') + '" title="My Travel Map" class="cdx-button cdx-button--weight-quiet" style="border:none !important; background:transparent !important; box-shadow:none !important; padding: 0 10px;">' +
                    '<span class="travel-tracker-text-label" style="font-size: 0.85rem; font-weight: 500; letter-spacing: 0.01em;">my travel map</span>' +
                    '</a></li>');

                $bell.before($textLink);

                mw.util.addCSS(
                    '#pt-travelmap-text .cdx-button { min-height: 32px; display: flex; align-items: center; justify-content: center; } ' +
                    '.travel-tracker-text-label { color: #54595d; } ' +
                    '.skin-theme-clientpref-night .travel-tracker-text-label { color: #eaecf0 !important; } ' +
                    '@media (prefers-color-scheme: dark) { .client-js.skin-theme-clientpref-os .travel-tracker-text-label { color: #eaecf0 !important; } }'
                );
            }
        });
    }

    injectHeaderLink();

    var pageName = mw.config.get('wgPageName');
    if (mw.config.get('wgNamespaceNumber') !== 2 || !mw.config.get('wgTitle').match(/\/Visited$/)) return;

    mw.loader.using(['mediawiki.api', 'mediawiki.util']).done(function() {
        var api = new mw.Api();
        var dataModule = 'Module:Countries.json';
        var isExists = mw.config.get('wgArticleId') > 0;
        var continentNames = {
            "af": "Africa", "as": "Asia", "eu": "Europe", 
            "na": "North America", "oc": "Oceania", "sa": "South America", "an": "Antarctica"
        };

        if (!isExists) {
            var $contentArea = $('#mw-content-text');
            if ($contentArea.length) {
                $contentArea.empty().append(
                    '<div class="mw-message-box mw-message-box-notice" style="border-radius: 8px; border-left-width: 5px;">' +
                    '<strong>Welcome to your Travel Tracker!</strong><br>' +
                    'Select the countries/territories you have visited below to generate your personalised map. Make sure to hit the "save changes" button to generate your map.' +
                    '</div>' +
                    '<div id="visited-tool-placeholder"></div>'
                );
            }
        }

        api.get({
            action: 'query', prop: 'revisions', titles: [pageName, dataModule],
            rvprop: 'content', formatversion: 2
        }).done(function(res) {
            var pages = res.query.pages;
            var pageData = pages.find(p => p.title.replace(/ /g, '_') === pageName);
            var moduleData = pages.find(p => p.title === dataModule);
            if (!moduleData || !moduleData.revisions) return;

            var countries = JSON.parse(moduleData.revisions[0].content);
            var content = (pageData && pageData.revisions) ? pageData.revisions[0].content : '';
            var match = content.match(/{{VisitedMap\|([^}]*)}}/);
            var savedQids = match ? match[1].split(',').map(s => s.trim()) : [];

            startBuilding(countries, savedQids);
        });

        function startBuilding(countries, savedQids) {
            var $placeholder = $('#visited-tool-placeholder');
            if (!$placeholder.length) {
                setTimeout(function() { startBuilding(countries, savedQids); }, 200);
                return;
            }
            buildUI($placeholder, countries, savedQids);
        }

        function buildUI($placeholder, countries, savedQids) {
            var $statsBar = $('<div style="background: var(--background-color-neutral-subtle, #f8f9fa); border: 1px solid var(--border-color-base, #eaecf0); border-radius: 8px; padding: 15px 20px; margin: 20px 0; display: flex; justify-content: space-between; align-items: center; font-family: sans-serif;"></div>');
            var $counterText = $('<span style="font-size: 1.1em; font-weight: 600; color: var(--color-emphasized, #202122);"></span>');
            
            $statsBar.append($counterText);
            $placeholder.empty().append($statsBar);

            var updateCounter = function() {
                var count = $('#visited-tool-placeholder input:checked').length;
                $counterText.text('Update your travel map – ' + count + ' countries/territories visited');
            };

            var grouped = {};
            countries.forEach(function(c) {
                var cont = c.c || "ot";
                if (!grouped[cont]) grouped[cont] = [];
                grouped[cont].push(c);
            });

            var sortedCodes = Object.keys(grouped).sort(function(a, b) {
                return (continentNames[a] || a).localeCompare(continentNames[b] || b);
            });

            sortedCodes.forEach(function(code) {
                var name = continentNames[code] || "Other territories";
                var $section = $('<div class="continent-card" style="margin-bottom: 25px; background: var(--background-color-base, #fff); border: 1px solid var(--border-color-base, #eaecf0); border-radius: 8px; overflow: hidden;">');
                $section.append('<div style="background: var(--background-color-neutral-subtle, #f8f9fa); padding: 10px 15px; font-weight: bold; border-bottom: 1px solid var(--border-color-base, #eaecf0); color: var(--color-subtle, #54595d);">' + name + '</div>');
                
                var $list = $('<div style="column-count: 4; column-gap: 20px; padding: 15px;">');

                grouped[code].sort((a, b) => a.n.localeCompare(b.n)).forEach(function(c) {
                    var isChecked = savedQids.indexOf(c.q) !== -1 ? 'checked' : '';
                    var $item = $('<div style="transition: background 0.2s; border-radius: 4px; padding: 4px 8px; break-inside: avoid;">' +
                        '<label style="display: flex; align-items: center; cursor: pointer; font-size: 0.9em; font-family: sans-serif; color: var(--color-base, #202122);">' +
                        '<input type="checkbox" value="' + c.q + '" ' + isChecked + ' style="margin-right: 8px; cursor: pointer;"> ' + c.n + '</label></div>');
                    
                    $item.find('input').on('change', updateCounter);
                    $item.hover(function() { $(this).css('background', 'var(--background-color-progressive-subtle, #f0f4ff)'); }, function() { $(this).css('background', 'transparent'); });
                    $list.append($item);
                });

                $section.append($list);
                $placeholder.append($section);
            });

            var $saveBtn = $('<button id="save-visited">Save changes</button>').css({
                'background-color': 'var(--background-color-progressive, #36c)', 'color': '#fff', 'border': 'none', 'border-radius': '4px',
                'padding': '10px 24px', 'font-size': '1em', 'font-weight': '600', 'font-family': 'sans-serif',
                'cursor': 'pointer', 'transition': 'background 0.2s', 'box-shadow': '0 2px 4px rgba(0,0,0,0.1)'
            });
            
            $statsBar.append($saveBtn);
            $placeholder.append('<div id="save-status" style="text-align: right; margin-top: 5px; font-size: 0.85em; font-weight: bold; min-height: 1.2em; font-family: sans-serif;"></div>');

            updateCounter();

            $saveBtn.on('mouseenter', function() { $(this).css('background-color', 'var(--background-color-progressive--hover, #447ff5)'); });
            $saveBtn.on('mouseleave', function() { $(this).css('background-color', 'var(--background-color-progressive, #36c)'); });

            $saveBtn.on('click', function(e) {
                e.preventDefault();
                var selected = [];
                $('#visited-tool-placeholder input:checked').each(function() { selected.push($(this).val()); });
                var $btn = $(this);
                $btn.addClass('mw-ui-disabled').css('background-color', 'var(--background-color-disabled, #a2a9b1)').text('Saving...');

                api.postWithToken('csrf', {
                    action: 'edit', title: pageName, text: '{{VisitedMap|' + selected.join(',') + '}}',
                    summary: (isExists ? 'Updating' : 'Creating') + ' travel map via [[Wikivoyage:Travel Tracker|TravelTracker gadget]]'
                }).done(function() {
                    $('#save-status').text('Success! Reloading...').css('color', 'var(--color-success, #00af89)');
                    setTimeout(function() { location.reload(); }, 800);
                });
            });
        }
    });
})(mediaWiki, jQuery);


Discover



Powered by GetYourGuide