Jump to content

User:Udays108/common.js

From BioMicro Center
Revision as of 19:39, 8 May 2026 by Udays108 (talk | contribs) (Created page with "============================================================ MIT BioMicro Center — MediaWiki Custom Scripts Skin: Vector-2022 Scope: User:USERNAME/common.js (personal preview only) Promote to MediaWiki:Common.js for site-wide deployment ============================================================: mw.loader.using( [ 'mediawiki.util' ] ).then( function () { 'use strict'; /* ── Helpers ──────────────────...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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.
/* ============================================================
   MIT BioMicro Center — MediaWiki Custom Scripts
   Skin: Vector-2022
   Scope: User:USERNAME/common.js  (personal preview only)
   Promote to MediaWiki:Common.js for site-wide deployment
   ============================================================ */

mw.loader.using( [ 'mediawiki.util' ] ).then( function () {
  'use strict';

  /* ── Helpers ─────────────────────────────────────────────── */

  function el( tag, props, children ) {
    var node = document.createElement( tag );
    Object.keys( props || {} ).forEach( function ( k ) {
      if ( k === 'style' && typeof props[ k ] === 'object' ) {
        Object.assign( node.style, props[ k ] );
      } else if ( k === 'className' ) {
        node.className = props[ k ];
      } else {
        node.setAttribute( k, props[ k ] );
      }
    } );
    ( children || [] ).forEach( function ( c ) {
      if ( typeof c === 'string' ) {
        node.appendChild( document.createTextNode( c ) );
      } else {
        node.appendChild( c );
      }
    } );
    return node;
  }


  /* ── 1. Top Contact Bar ──────────────────────────────────── */
  /* Injects a crimson bar above the header with email/phone/location */

  ( function injectTopbar() {
    var header = document.querySelector( '.vector-header' );
    if ( !header || document.querySelector( '.bmc-topbar' ) ) return;

    var topbar = document.createElement( 'div' );
    topbar.className = 'bmc-topbar';
    topbar.innerHTML =
      '<div class="bmc-topbar-inner">' +
        '<a href="mailto:biomicro@mit.edu">biomicro@mit.edu</a>' +
        '<span class="bmc-sep">|</span>' +
        '617-715-4533' +
        '<span class="bmc-sep">|</span>' +
        'Building 68-322' +
      '</div>';
    header.parentNode.insertBefore( topbar, header );

    /* Inject inline styles — common.css won't reach injected elements
       before the DOM settles on first load */
    mw.util.addCSS(
      '.bmc-topbar{background:#a31f34;color:#fff;font-size:.75rem;' +
      'padding:6px 0;text-align:right;font-family:\'Helvetica Neue\',Arial,sans-serif}' +
      '.bmc-topbar-inner{max-width:1140px;margin:0 auto;padding:0 24px}' +
      '.bmc-topbar a{color:#fff;opacity:.85;text-decoration:none}' +
      '.bmc-topbar a:hover{opacity:1}' +
      '.bmc-sep{margin:0 6px;opacity:.5}'
    );
  }() );


  /* ── 2. Active Navigation Highlight ─────────────────────── */
  /* Marks the sidebar nav link that matches the current wiki page */

  ( function markActiveNav() {
    var pageName = mw.config.get( 'wgPageName' );
    if ( !pageName ) return;

    /* Normalise: spaces → underscores, lower-case for comparison */
    var norm = pageName.replace( / /g, '_' ).toLowerCase();

    var links = document.querySelectorAll(
      '#p-navigation .mw-list-item a, ' +
      '.vector-main-menu .mw-list-item a'
    );

    links.forEach( function ( link ) {
      var href = ( link.getAttribute( 'href' ) || '' ).toLowerCase();
      /* Match on the last path segment (/wiki/PageName) */
      if ( href.indexOf( norm ) !== -1 ) {
        var li = link.closest( 'li' );
        if ( li ) li.classList.add( 'bmc-active' );
      }
    } );
  }() );


  /* ── 3. Scroll Offset Fix (sticky header) ────────────────── */
  /* Vector-2022's sticky header is ~50–68px tall. Without this fix
     clicking a TOC anchor scrolls the heading behind the header. */

  ( function fixScrollOffset() {
    var OFFSET = 88; /* header + topbar combined */

    document.addEventListener( 'click', function ( e ) {
      var anchor = e.target.closest( 'a[href^="#"]' );
      if ( !anchor ) return;

      var targetId = anchor.getAttribute( 'href' ).slice( 1 );
      if ( !targetId ) return;
      var target = document.getElementById( targetId ) ||
                   document.querySelector( '[name="' + CSS.escape( targetId ) + '"]' );
      if ( !target ) return;

      e.preventDefault();
      var top = target.getBoundingClientRect().top + window.pageYOffset - OFFSET;
      window.scrollTo( { top: top, behavior: 'smooth' } );

      /* Update URL hash without triggering browser jump */
      if ( history.pushState ) {
        history.pushState( null, null, '#' + targetId );
      }
    } );
  }() );


  /* ── 4. Tab Panel System ─────────────────────────────────── */
  /* Activates the .tab-btn / .tab-panel system used on the
     pricing page. Does nothing if no .tab-btn elements exist. */

  ( function initTabs() {
    var tabBtns = document.querySelectorAll( '.tab-btn' );
    if ( !tabBtns.length ) return;

    var tabPanels = document.querySelectorAll( '.tab-panel' );

    /* Activate first tab by default if none is active */
    if ( !document.querySelector( '.tab-btn.active' ) ) {
      tabBtns[ 0 ].classList.add( 'active' );
    }
    if ( !document.querySelector( '.tab-panel.active' ) ) {
      tabPanels[ 0 ] && tabPanels[ 0 ].classList.add( 'active' );
    }

    tabBtns.forEach( function ( btn ) {
      btn.addEventListener( 'click', function () {
        var target = btn.dataset.tab;

        tabBtns.forEach( function ( t ) { t.classList.remove( 'active' ); } );
        tabPanels.forEach( function ( p ) { p.classList.remove( 'active' ); } );

        btn.classList.add( 'active' );
        var panel = document.getElementById( 'tab-' + target );
        if ( panel ) panel.classList.add( 'active' );

        /* Scroll to tab bar, accounting for sticky header */
        var tabBarWrap = document.querySelector( '.tab-bar-wrap' );
        if ( tabBarWrap ) {
          var top = tabBarWrap.getBoundingClientRect().top + window.pageYOffset - 68;
          window.scrollTo( { top: top, behavior: 'smooth' } );
        }
      } );
    } );
  }() );


  /* ── 5. BMC Logo Mark ────────────────────────────────────── */
  /* Injects the red square "BMC" mark next to the wiki wordmark
     if the logo element is a plain text/image logo.             */

  ( function injectLogoMark() {
    var logoLink = document.querySelector( '.mw-logo' );
    if ( !logoLink || document.querySelector( '.bmc-logo-mark' ) ) return;

    var mark = document.createElement( 'div' );
    mark.className = 'bmc-logo-mark';
    mark.textContent = 'BMC';

    mw.util.addCSS(
      '.bmc-logo-mark{' +
        'width:36px;height:36px;background:#a31f34;border-radius:4px;' +
        'display:flex;align-items:center;justify-content:center;' +
        'color:#fff;font-weight:700;font-size:.9rem;letter-spacing:-.5px;' +
        'flex-shrink:0;font-family:\'Helvetica Neue\',Arial,sans-serif' +
      '}' +
      '.mw-logo{display:flex!important;align-items:center!important;gap:10px!important}'
    );

    /* Prepend mark before the existing logo image/text */
    logoLink.insertBefore( mark, logoLink.firstChild );
  }() );


  /* ── 6. Wrap Long Tables for Mobile Scroll ───────────────── */
  /* Wraps .wikitable in a scrollable div so wide tables don't
     break the layout on small screens.                         */

  ( function wrapTables() {
    document.querySelectorAll( '.mw-parser-output .wikitable' ).forEach( function ( table ) {
      if ( table.closest( '.wikitable-wrapper' ) ) return;
      var wrapper = document.createElement( 'div' );
      wrapper.className = 'wikitable-wrapper';
      table.parentNode.insertBefore( wrapper, table );
      wrapper.appendChild( table );
    } );

    mw.util.addCSS(
      '.wikitable-wrapper{overflow-x:auto;border-radius:8px;' +
      'border:1px solid #e4e4e4;margin-bottom:26px}'
    );
  }() );

} );