MediaWiki:Common.js: Difference between revisions
Created page with "→Any JavaScript here will be loaded for all users on every page load.: window.wpDarkModeAutoToggle = true;"  |
No edit summary  |
||
| (15 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
/* Any JavaScript here will be loaded for all users on every page load. */ | /* Any JavaScript here will be loaded for all users on every page load. */ | ||
/* Load FontAwesome 6.6.0 (Includes Butterfly & Bluesky) */ | |||
mw.loader.load('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css', 'text/css'); | |||
/* Force all external links to open in a new tab */ | |||
$( function() { | |||
  $( 'a.external' ).attr( 'target', '_blank' ); | |||
}); | |||
window.wpDarkModeAutoToggle = true; | window.wpDarkModeAutoToggle = true; | ||
/* --- Inject Hero Search Bar on Main Page (Fixed Suggestions) --- */ | |||
$(function() { | |||
  // Only run this if the placeholder exists | |||
  if ($('#bc-hero-search').length) { | |||
    var searchHTML = | |||
      '<form action="/index.php" id="searchform">' + | |||
      '<input type="hidden" name="title" value="Special:Search">' + | |||
      '<div style="position: relative; max-width: 600px; margin: 0 auto;">' + | |||
      | |||
      // ADDED: class="mw-searchInput" | |||
      // ADDED: accesskey="f" (Standard MediaWiki hotkey) | |||
      '<input id="heroSearchInput" class="mw-searchInput" name="search" type="search" placeholder="Search for In-Bond, ACE Manifest..." autocomplete="off" accesskey="f" style="width: 100%; padding: 15px 120px 15px 25px; border-radius: 50px; border: none; font-size: 16px; box-shadow: 0 4px 6px rgba(0,0,0,0.2); outline: none;">' + | |||
      | |||
      '<button type="submit" name="go" value="Go" style="position: absolute; right: 5px; top: 5px; bottom: 5px; background: #266065; color: white; border: none; border-radius: 50px; padding: 0 25px; font-weight: bold; cursor: pointer; box-shadow: 0 2px 5px rgba(0,0,0,0.2);">Search</button>' + | |||
      '</div>' + | |||
      '</form>'; | |||
      | |||
    $('#bc-hero-search').html(searchHTML); | |||
    // --- MANUALLY BIND SUGGESTIONS --- | |||
    // We wait for the module to load, then force the bind | |||
    mw.loader.using( 'mediawiki.searchSuggest', function () { | |||
      $( '#heroSearchInput' ).searchSuggest(); | |||
    }); | |||
  } | |||
}); | |||
/* --- INJECT CUSTOM SIDEBAR MENU --- */ | |||
$(function() { | |||
  // Define the menu HTML | |||
  var customMenu = | |||
    '<nav class="mw-portlet mw-portlet-bc-tools vector-menu vector-menu-portal portal" aria-labelledby="p-bctools-label" role="navigation">' + | |||
    '<h3 id="p-bctools-label" class="vector-menu-heading"><span>BorderConnect Tools</span></h3>' + | |||
    '<div class="vector-menu-content">' + | |||
    '<ul class="vector-menu-content-list">' + | |||
      '<li><a href="https://www.borderconnect.com/auth/login" target="_blank">Login to System</a></li>' + | |||
      '<li><a href="https://www.borderprint.com" target="_blank">Order Labels (BorderPrint)</a></li>' + | |||
      '<li><a href="https://www.borderconnect.com/contact" target="_blank">Contact Support</a></li>' + | |||
    '</ul>' + | |||
    '</div>' + | |||
    '</nav>'; | |||
  // Insert it above the "Tools" (tb) section or "Interaction" section | |||
  // Try '#p-tb' (Tools) or '#p-navigation' (Navigation) | |||
  $('#p-navigation').after(customMenu); | |||
}); | |||
/* --- SMART VIDEO MODAL (Auto-Inject) --- */ | |||
$(function() { | |||
  | |||
  // 1. Check if Modal HTML exists. If not, create it. | |||
  if ($('#bc-video-modal').length === 0) { | |||
    $('body').append(` | |||
      <div id="bc-video-modal" class="bc-modal-overlay"> | |||
        <div class="bc-modal-content"> | |||
          <div class="bc-modal-close">×</div> | |||
          <iframe id="bc-modal-iframe" width="100%" height="100%" src="" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe> | |||
        </div> | |||
      </div> | |||
    `); | |||
  } | |||
  // 2. Setup Triggers (Thumbnail & Click) | |||
  $('.bc-video-trigger').each(function() { | |||
    var $trigger = $(this); | |||
    var videoID = $trigger.data('video-id'); | |||
    | |||
    // Inject Image Tag if missing | |||
    if ($trigger.find('img').length === 0) { | |||
      var thumbUrl = "https://img.youtube.com/vi/" + videoID + "/maxresdefault.jpg"; | |||
      $trigger.prepend('<img src="' + thumbUrl + '" alt="Video Thumbnail">'); | |||
    } | |||
  }); | |||
  // 3. Handle Click (Using 'body' delegation to ensure it always catches the click) | |||
  $('body').on('click', '.bc-video-trigger', function() { | |||
    var videoID = $(this).data('video-id'); | |||
    var embedUrl = "https://www.youtube.com/embed/" + videoID + "?autoplay=1&rel=0&modestbranding=1"; | |||
    | |||
    $('#bc-modal-iframe').attr('src', embedUrl); | |||
    $('#bc-video-modal').addClass('active'); | |||
    $('body').css('overflow', 'hidden'); // Stop background scrolling | |||
  }); | |||
  // 4. Close Logic | |||
  function closeModal() { | |||
    $('#bc-video-modal').removeClass('active'); | |||
    $('#bc-modal-iframe').attr('src', ''); | |||
    $('body').css('overflow', ''); | |||
  } | |||
  $('body').on('click', '.bc-modal-close', closeModal); | |||
  | |||
  // Close on background click | |||
  $('body').on('click', '#bc-video-modal', function(e) { | |||
    if (e.target === this) closeModal(); | |||
  }); | |||
  | |||
  // Close on Escape Key | |||
  $(document).keydown(function(e) { | |||
    if (e.key === "Escape") closeModal(); | |||
  }); | |||
}); | |||
/* ============================================================ | |||
 --- FORCE TOC THEME UPDATE --- | |||
 Watches the HTML tag for theme changes and forces a class | |||
 onto the TOC to ensure it switches styles instantly. | |||
 ============================================================ */ | |||
$(function() { | |||
  // 1. Identify the TOC (Standard or Vector 2022) | |||
  var tocSelector = '#toc, .vector-toc, .mw-parser-output .toc'; | |||
  // 2. The function that decides if we are Dark or Light | |||
  function updateTOC() { | |||
    var $html = $('html'); | |||
    var $toc = $(tocSelector); | |||
    | |||
    // Check if "Night" class is active | |||
    var isNight = $html.hasClass('skin-theme-clientpref-night'); | |||
    | |||
    // Check if "OS" class is active AND system is Dark | |||
    var isOS = $html.hasClass('skin-theme-clientpref-os'); | |||
    var systemDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; | |||
    if (isNight || (isOS && systemDark)) { | |||
      // FORCE DARK MODE | |||
      $toc.addClass('force-dark-mode'); | |||
    } else { | |||
      // REVERT TO LIGHT | |||
      $toc.removeClass('force-dark-mode'); | |||
    } | |||
  } | |||
  // 3. Run immediately on load | |||
  updateTOC(); | |||
  // 4. Watch for changes (User toggles the switch) | |||
  var observer = new MutationObserver(function(mutations) { | |||
    mutations.forEach(function(mutation) { | |||
      if (mutation.attributeName === "class") { | |||
        updateTOC(); | |||
      } | |||
    }); | |||
  }); | |||
  // Start observing the <html class="..."> attribute | |||
  observer.observe(document.documentElement, { | |||
    attributes: true | |||
  }); | |||
}); | |||
/* ============================================================ | |||
 --- SEARCH ICON COLOR FIX (JS UPDATE FOR STUBBORN CLASSES) --- | |||
 ============================================================ */ | |||
$(document).ready(function() { | |||
  // 1. Function to force the icon to white | |||
  function forceSearchIconWhite() { | |||
    // Targets standard icons AND the specific -vue class you found | |||
    $('.cdx-text-input__icon, .cdx-search-input__start-icon, .cdx-text-input__icon-vue') | |||
      .css({ | |||
        'filter': 'brightness(0) invert(1)', | |||
        'color': '#ffffff', | |||
        'fill': '#ffffff', | |||
        'opacity': '1' | |||
      }); | |||
  } | |||
  // 2. Function to reset to dark (when typing/focused) | |||
  function resetSearchIconDark() { | |||
    $('.cdx-text-input__icon, .cdx-search-input__start-icon, .cdx-text-input__icon-vue') | |||
      .css({ | |||
        'filter': 'none', | |||
        'color': '#546e7a', // Dark Grey | |||
        'fill': '#546e7a', | |||
        'opacity': '0.5' | |||
      }); | |||
  } | |||
  // 3. Attach Events using delegation | |||
  $('body').on('focus', '.cdx-text-input__input', function() { | |||
    resetSearchIconDark(); | |||
  }); | |||
  // This catches the moment the user clicks away | |||
  $('body').on('blur', '.cdx-text-input__input', function() { | |||
    forceSearchIconWhite(); | |||
  }); | |||
  // 4. Run on load and also after a tiny delay | |||
  // (MediaWiki sometimes renders the search bar slightly late) | |||
  forceSearchIconWhite(); | |||
  setTimeout(forceSearchIconWhite, 500); | |||
  // 5. Watch for any input changes to prevent "reverting" while typing/clicking | |||
  $('.cdx-text-input__input').on('input change', function() { | |||
    if (!$(this).is(':focus')) { | |||
      forceSearchIconWhite(); | |||
    } | |||
  }); | |||
}); | |||
/* ============================================================ | |||
 --- NATIVE SCROLL MONITOR (FIXED) --- | |||
 ============================================================ */ | |||
document.addEventListener('scroll', function() { | |||
  // Check if we have scrolled down more than 50 pixels | |||
  if (window.scrollY > 50) { | |||
    document.body.classList.add('bc-scrolled'); | |||
  } else { | |||
    document.body.classList.remove('bc-scrolled'); | |||
  } | |||
}); | |||
// Run once immediately on load to catch refresh state | |||
if (window.scrollY > 50) { | |||
  document.body.classList.add('bc-scrolled'); | |||
} | |||