MediaWiki:Common.js

From Taylor Wiki
Revision as of 18:44, 21 October 2026 by Admin (talk | contribs)

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* Multi-Tier Category Breadcrumbs (Home › Section › Collection/Topic › Page/File) */
( function () {
    var conf = mw.config.get([
        'wgAction',
        'wgNamespaceNumber',
        'wgPageName',
        'wgTitle',
        'wgIsMainPage',
        'wgMainPageTitle',
        'wgCategories'
    ]);
    // Only run on standard page views (skip edit forms, history, special pages)
    if ( !conf || conf.wgAction !== 'view' || conf.wgNamespaceNumber < 0 ) {
        return;
    }
    var mainPageTitle = conf.wgMainPageTitle || 'Main Page';
    var homeUrl = mw.util.getUrl( mainPageTitle );
    var isHome = conf.wgIsMainPage || ( conf.wgPageName === mainPageTitle.replace( / /g, '_' ) );
    if ( isHome ) {
        return;
    }
    var currentTitle = conf.wgTitle.replace( /_/g, ' ' );
    var categories = conf.wgCategories || [];
    // Top-level hub categories
    var TOP_SECTIONS = [
        'Letters',
        'Instruments',
        'Photographs',
        'Documents',
        'Biographies',
        'Family'
    ];
    var topSection = null;
    var subSection = null;
    // 1. Find the top-level section if present
    for ( var i = 0; i < TOP_SECTIONS.length; i++ ) {
        var sec = TOP_SECTIONS[i];
        for ( var c = 0; c < categories.length; c++ ) {
            if ( categories[c].toLowerCase() === sec.toLowerCase() ) {
                topSection = sec;
                break;
            }
        }
        if ( topSection ) {
            break;
        }
    }
    // 2. Find any secondary category (like "Lamson") that isn't the top section
    for ( var k = 0; k < categories.length; k++ ) {
        var catName = categories[k].replace( /_/g, ' ' );
        if ( !topSection || catName.toLowerCase() !== topSection.toLowerCase() ) {
            subSection = catName;
            break;
        }
    }
    // Prevent duplicates if viewing the section or collection page itself
    if ( topSection && currentTitle.toLowerCase() === topSection.toLowerCase() ) {
        topSection = null;
    }
    if ( subSection && currentTitle.toLowerCase() === subSection.toLowerCase() ) {
        subSection = null;
    }
    // Build the breadcrumb bar
    var $nav = $( '<div class="mw-dynamic-breadcrumbs" aria-label="Breadcrumb navigation"></div>' );
    // 1. Home / Main Page
    $nav.append( $( '<a>' ).addClass( 'crumb-link' ).attr( 'href', homeUrl ).text( 'Main Page' ) );
    // 2. Top-level Section (e.g. Letters)
    if ( topSection ) {
        $nav.append( $( '<span>' ).addClass( 'crumb-sep' ).text( '›' ) );
        $nav.append( $( '<a>' ).addClass( 'crumb-link' ).attr( 'href', mw.util.getUrl( topSection ) ).text( topSection ) );
    }
    // 3. Sub-level Collection / Person (e.g. Lamson)
    if ( subSection ) {
        $nav.append( $( '<span>' ).addClass( 'crumb-sep' ).text( '›' ) );
        $nav.append( $( '<a>' ).addClass( 'crumb-link' ).attr( 'href', mw.util.getUrl( subSection ) ).text( subSection ) );
    }
    // 4. Current File or Article Title
    $nav.append( $( '<span>' ).addClass( 'crumb-sep' ).text( '›' ) );
    $nav.append( $( '<span>' ).addClass( 'crumb-current' ).text( currentTitle ) );
    // Inject above title in Timeless skin
    $( function () {
        var $heading = $( '#firstHeading' );
        if ( $heading.length ) {
            $nav.insertBefore( $heading );
        } else {
            $( '#mw-content-text' ).prepend( $nav );
        }
    } );
}() );

/* Open optimal reading resolution preview on the main photo instead of tiny thumb or massive scan */
$( function() {
    if ( mw.config.get( 'wgNamespaceNumber' ) === 6 ) { // File namespace
        // Use direct child selector '>' so we ONLY target the photo itself, not the resolution links below it
        var $previewLink = $( '.fullImageLink > a' );
        var $resLinks = $( '.mw-filepage-resolutioninfo a' );

        if ( $previewLink.length && $resLinks.length ) {
            var bestHref = null;
            var bestWidth = 0;

            $resLinks.each( function() {
                var text = $( this ).text(); // e.g. "2,048 × 1,153 pixels"
                var match = text.replace(/,/g, '').match(/(\d+)\s*×/);
                if ( match ) {
                    var w = parseInt( match[1], 10 );
                    // Pick the crispest preview up to 2560px wide (great for desktop reading)
                    if ( w > bestWidth && w <= 2560 ) {
                        bestWidth = w;
                        bestHref = $( this ).attr( 'href' );
                    }
                }
            } );

            if ( bestHref ) {
                $previewLink.attr( 'href', bestHref );
            }
        }
    }
} );


/* Inject Universal GEWAME Umbrella Header */
$( function () {
    if ( $( '#gewame-universal-bar' ).length ) {
        return;
    }

    var isLoggedIn = document.cookie.indexOf( 'gewame_logged_in=1' ) !== -1;
    var currentUrl = encodeURIComponent( window.location.href );

    var authLink = isLoggedIn
        ? '<a href="https://gewame.com/sso-logout.php?redirect_to=' + currentUrl + '" class="gw-nav-link gw-btn-auth">LOG OUT</a>'
        : '<a href="https://gewame.com/wp-login.php?action=register" class="gw-nav-link gw-btn-auth">REGISTER</a>' +
          '<a href="https://gewame.com/wp-login.php?redirect_to=' + currentUrl + '" class="gw-nav-link gw-btn-auth">LOG IN</a>';

    var headerHtml =
        '<div id="gewame-universal-bar">' +
            '<div class="gw-bar-inner">' +
                '<a href="https://gewame.com/" class="gw-brand" title="GEWAME — The Taylor Archive">' +
                    '<span class="gw-brand-title">GEWAME</span>' +
                    '<span class="gw-brand-sep">|</span>' +
                    '<span class="gw-brand-sub">The Taylor Archive</span>' +
                '</a>' +
                                '<nav class="gw-nav">' +
                    '<a href="https://gewame.com/wiki/" class="gw-nav-link gw-active">WIKI</a>' +
                    '<a href="https://gewame.com/tree/" class="gw-nav-link">FAMILY TREE</a>' +
                    '<a href="https://gewame.com/forum/" class="gw-nav-link">FORUM</a>' +
                    authLink +
                '</nav>' +

            '</div>' +
        '</div>';

    $( 'body' ).prepend( headerHtml );
} );

/* Random Featured Video on the Videos page */
$(function() {
    // Only run on the main Videos page
    if (mw.config.get('wgPageName') !== 'Videos') return;

    var videoList = [
        {
            id: 'f9WcK-Peclk',
            caption: 'Taylor Instrument Companies — Rochester Business Hall of Fame Induction (2012).'
        },
        {
            id: 'tLAQtuhGc0Q',
            caption: 'Starky: Training at Taylor Instrument Company — Oral History.'
        }
        /* Add new videos here as you upload them:
        ,{ id: 'YOUTUBE_ID', caption: 'Short description here.' }
        */
    ];

    var randomVideo = videoList[Math.floor(Math.random() * videoList.length)];
    
    // Update the iframe and caption
    var $iframe = $('#gw-featured-player iframe');
    if ($iframe.length) {
        $iframe.attr('src', 'https://www.youtube-nocookie.com/embed/' + randomVideo.id);
        $('#gw-featured-caption').text(randomVideo.caption);
    }
});
/* ===================================================
   GEWAME Archival Booklet Reader (Captions + Zoom & Pan)
   =================================================== */
$(function() {
    var $readers = $('.gw-book-reader');
    if (!$readers.length) return;

    $readers.each(function() {
        var $reader = $(this);
        var $fallback = $reader.find('.gw-book-fallback');
        var $images = $fallback.find('img');

        if (!$images.length) return;

        // Parse page items (URL + caption)
        var pages = [];
        $images.each(function() {
            var $img = $(this);
            var src = $img.attr('src');
            var caption = $img.attr('alt') || $img.attr('title') || '';
            pages.push({ src: src, caption: caption });
        });

        $fallback.hide();

        var currentIndex = 0;
        var isZoomed = false;
        var zoomScale = 2.4;
        var panX = 0;
        var panY = 0;
        var isDragging = false;
        var dragStartX = 0;
        var dragStartY = 0;

        // Stage markup
        var $stage = $('<div class="gw-book-stage">' +
            '<div class="gw-book-click-left" title="Previous page"></div>' +
            '<div class="gw-book-page-wrapper">' +
                '<img class="gw-book-page-img" src="' + pages[0].src + '" alt="Page 1" draggable="false" />' +
            '</div>' +
            '<div class="gw-book-click-right" title="Next page"></div>' +
        '</div>');

        // Toolbar markup with zoom controls and caption
        var $toolbar = $('<div class="gw-book-toolbar">' +
            '<div class="gw-book-nav-group">' +
                '<button class="gw-book-btn gw-btn-prev">‹ Prev</button>' +
                '<button class="gw-book-btn gw-btn-next">Next ›</button>' +
            '</div>' +
            '<div class="gw-book-center-info">' +
                '<span class="gw-book-counter">Page 1 of ' + pages.length + '</span>' +
                '<span class="gw-book-caption"></span>' +
            '</div>' +
            '<div class="gw-book-zoom-group">' +
                '<button class="gw-book-btn gw-btn-zoom">🔍 Zoom In</button>' +
            '</div>' +
        '</div>' +
        '<div class="gw-book-hint">Tip: Click page or 🔍 Zoom to enlarge, then drag to pan. Use arrow keys or buttons to flip.</div>');

        $reader.append($stage).append($toolbar);

        var $wrapper = $stage.find('.gw-book-page-wrapper');
        var $img = $stage.find('.gw-book-page-img');
        var $counter = $reader.find('.gw-book-counter');
        var $caption = $reader.find('.gw-book-caption');
        var $btnPrev = $reader.find('.gw-btn-prev');
        var $btnNext = $reader.find('.gw-btn-next');
        var $btnZoom = $reader.find('.gw-btn-zoom');

        function resetZoom() {
            isZoomed = false;
            panX = 0;
            panY = 0;
            $reader.removeClass('is-zoomed is-dragging');
            $img.css({
                transform: 'none',
                transition: 'transform 0.2s ease-out'
            });
            $btnZoom.removeClass('active').text('🔍 Zoom In');
            $wrapper.css('cursor', 'zoom-in');
        }

        function setZoom(zoomed) {
            if (zoomed) {
                isZoomed = true;
                panX = 0;
                panY = 0;
                $reader.addClass('is-zoomed');
                $img.css({
                    transform: 'scale(' + zoomScale + ') translate(0px, 0px)',
                    transition: 'transform 0.2s ease-out'
                });
                $btnZoom.addClass('active').text('✖ Reset Zoom');
                $wrapper.css('cursor', 'grab');
            } else {
                resetZoom();
            }
        }

        function updatePage(index) {
            if (index < 0 || index >= pages.length) return;
            resetZoom();
            currentIndex = index;

            $img.css('opacity', '0.35');
            var nextData = pages[currentIndex];

            var temp = new Image();
            temp.onload = function() {
                $img.attr('src', nextData.src).css('opacity', '1');
            };
            temp.src = nextData.src;

            $counter.text('Page ' + (currentIndex + 1) + ' of ' + pages.length);

            // Clean up caption text if it repeats "Page X"
            var captionText = nextData.caption || '';
            if (captionText) {
                $caption.text('— ' + captionText);
            } else {
                $caption.text('');
            }

            $btnPrev.prop('disabled', currentIndex === 0);
            $btnNext.prop('disabled', currentIndex === pages.length - 1);
        }

        // Initialize page 1
        updatePage(0);

        // Navigation clicks
        $btnPrev.on('click', function() { updatePage(currentIndex - 1); });
        $btnNext.on('click', function() { updatePage(currentIndex + 1); });
        $stage.find('.gw-book-click-left').on('click', function() {
            if (!isZoomed) updatePage(currentIndex - 1);
        });
        $stage.find('.gw-book-click-right').on('click', function() {
            if (!isZoomed) updatePage(currentIndex + 1);
        });

        // Zoom toggle
        $btnZoom.on('click', function() {
            setZoom(!isZoomed);
        });

        $wrapper.on('click', function(e) {
            if (!isZoomed) {
                setZoom(true);
            }
        });

        // Drag to pan when zoomed
        $wrapper.on('mousedown touchstart', function(e) {
            if (!isZoomed) return;
            isDragging = true;
            $reader.addClass('is-dragging');

            var clientX = e.type === 'touchstart' ? e.originalEvent.touches[0].clientX : e.clientX;
            var clientY = e.type === 'touchstart' ? e.originalEvent.touches[0].clientY : e.clientY;

            dragStartX = clientX - panX;
            dragStartY = clientY - panY;
            $img.css('transition', 'none'); // Instant drag response
            e.preventDefault();
        });

        $(document).on('mousemove touchmove', function(e) {
            if (!isDragging || !isZoomed) return;
            var clientX = e.type === 'touchmove' ? e.originalEvent.touches[0].clientX : e.clientX;
            var clientY = e.type === 'touchmove' ? e.originalEvent.touches[0].clientY : e.clientY;

            panX = clientX - dragStartX;
            panY = clientY - dragStartY;

            // Restrict bounds based on zoom level
            var limitX = ($stage.width() * (zoomScale - 1)) / (2 * zoomScale);
            var limitY = ($stage.height() * (zoomScale - 1)) / (2 * zoomScale);
            panX = Math.max(-limitX, Math.min(limitX, panX));
            panY = Math.max(-limitY, Math.min(limitY, panY));

            $img.css('transform', 'scale(' + zoomScale + ') translate(' + panX + 'px, ' + panY + 'px)');
        });

        $(document).on('mouseup touchend', function() {
            if (isDragging) {
                isDragging = false;
                $reader.removeClass('is-dragging');
            }
        });

        // Keyboard navigation & Escape to reset zoom
        $(document).on('keydown', function(e) {
            if ($(e.target).is('input, textarea')) return;
            if (e.which === 27) { // Escape key
                resetZoom();
            } else if (!isZoomed) {
                if (e.which === 37) { // Left arrow
                    updatePage(currentIndex - 1);
                } else if (e.which === 39) { // Right arrow
                    updatePage(currentIndex + 1);
                }
            }
        });

        // Mobile swipe between pages (when NOT zoomed in)
        var swipeStartX = 0;
        $stage.on('touchstart', function(e) {
            if (!isZoomed && e.originalEvent.touches.length === 1) {
                swipeStartX = e.originalEvent.touches[0].clientX;
            }
        });
        $stage.on('touchend', function(e) {
            if (!isZoomed && swipeStartX !== 0) {
                var diff = swipeStartX - e.originalEvent.changedTouches[0].clientX;
                if (Math.abs(diff) > 40) {
                    if (diff > 0) updatePage(currentIndex + 1);
                    else updatePage(currentIndex - 1);
                }
                swipeStartX = 0;
            }
        });
    });
});
/* ===================================================
   Internet Archive BookReader Embed Engine
   =================================================== */
$(function() {
    $('.gw-archive-embed').each(function() {
        var $el = $(this);
        var id = $el.data('id');
        if (!id) return;

        var embedUrl = 'https://archive.org/embed/' + encodeURIComponent(id);
        var directUrl = 'https://archive.org/details/' + encodeURIComponent(id) + '/mode/2up';

        var html = '<div class="gw-ia-container">' +
            '<div class="gw-ia-frame-wrap">' +
                '<iframe src="' + embedUrl + '" width="100%" height="100%" frameborder="0" webkitallowfullscreen="true" mozallowfullscreen="true" allowfullscreen style="border:0; display:block;"></iframe>' +
            '</div>' +
            '<div class="gw-ia-toolbar">' +
                '<span style="color:#94a3b8;">📖 Internet Archive BookReader</span>' +
                '<a href="' + directUrl + '" target="_blank" rel="noopener">Open in Fullscreen / Download PDF ↗</a>' +
            '</div>' +
        '</div>';

        $el.html(html);
    });
});
/* ========================================================
   Social Share Bar at the bottom of articles
   ======================================================== */
$(function() {
    // Only show on regular article pages in read view
    if (mw.config.get('wgAction') !== 'view' || mw.config.get('wgNamespaceNumber') < 0) {
        return;
    }

    var pageUrl = encodeURIComponent(window.location.href);
    var pageTitle = encodeURIComponent(mw.config.get('wgTitle') + ' - GEWAME Archive');

    var fbUrl = 'https://www.facebook.com/sharer/sharer.php?u=' + pageUrl;
    var xUrl = 'https://twitter.com/intent/tweet?url=' + pageUrl + '&text=' + pageTitle;
    var liUrl = 'https://www.linkedin.com/sharing/share-offsite/?url=' + pageUrl;
    var mailUrl = 'mailto:?subject=' + pageTitle + '&body=Check%20out%20this%20page%3A%20' + pageUrl;

    var shareHtml = $(
        '<div class="gw-share-bar">' +
            '<span class="gw-share-label">Share this page:</span>' +
            '<div class="gw-share-buttons">' +
                '<a class="gw-share-btn gw-share-fb" href="' + fbUrl + '" target="_blank" rel="noopener noreferrer" title="Share on Facebook">Facebook</a>' +
                '<a class="gw-share-btn gw-share-x" href="' + xUrl + '" target="_blank" rel="noopener noreferrer" title="Share on X">X (Twitter)</a>' +
                '<a class="gw-share-btn gw-share-li" href="' + liUrl + '" target="_blank" rel="noopener noreferrer" title="Share on LinkedIn">LinkedIn</a>' +
                '<a class="gw-share-btn gw-share-email" href="' + mailUrl + '" title="Share via Email">Email</a>' +
                '<button type="button" class="gw-share-btn gw-share-copy" title="Copy page link">Copy Link</button>' +
            '</div>' +
        '</div>'
    );

    // Copy link click handler
    shareHtml.find('.gw-share-copy').on('click', function(e) {
        e.preventDefault();
        var btn = $(this);
        navigator.clipboard.writeText(window.location.href).then(function() {
            var origText = btn.text();
            btn.text('Copied!').addClass('gw-copied');
            setTimeout(function() {
                btn.text(origText).removeClass('gw-copied');
            }, 2000);
        });
    });

    // Append to the bottom of the article content (just before categories)
    $('#mw-content-text').append(shareHtml);
});
$(function() {
    // Look for the marker in captions and add a badge directly onto the thumbnail container
    $('.gallerybox').each(function() {
        if ($(this).find('.gw-unidentified-marker').length > 0) {
            $(this).find('.thumb').css('position', 'relative').append(
                '<div class="gw-photo-overlay-tag">Help Identify</div>'
            );
        }
    });
});