Jump to content

User:Udays108/common.js: Difference between revisions

From BioMicro Center
Udays108 (talk | contribs)
No edit summary
Udays108 (talk | contribs)
No edit summary
 
(13 intermediate revisions by the same user not shown)
Line 5: Line 5:
  * Strategy:
  * Strategy:
  * 1. Extract wiki article content from MediaWiki's DOM
  * 1. Extract wiki article content from MediaWiki's DOM
  * 2. Inject the complete about.html shell structure
  * 2. Inject the complete shell structure
  * 3. Place article content inside the shell
  * 3. Place article content inside the shell
  * MediaWiki still runs underneath for editing, search, login, etc.
  * MediaWiki still runs underneath for editing, search, login, etc.
Line 13: Line 13:
   'use strict';
   'use strict';


   /* ── Nav definition (matches about.html exactly) ──────────── */
   /* ── Extract nav from MediaWiki's sidebar DOM ─────────────── */
   var NAV = [
   function extractNavFromDom() {
    {
    var SKIP_IDS = {
       label: 'About',
       'p-tb': 1, 'p-personal': 1, 'p-search': 1,
      href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:AboutBMC',
       'p-logo': 1, 'p-cactions': 1, 'p-views': 1,
      match: [ 'AboutBMC' ]
       'p-lang': 1, 'p-namespaces': 1
    },
    };
    {
 
       label: 'News',
    var portlets = Array.prototype.slice.call(
      href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:News',
      document.querySelectorAll( '[id^="p-"]' )
      match: [ 'News', 'Seminar' ],
    );
       items: [
 
        { label: 'Latest News',       href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:News' },
     var nav = [];
        { label: 'Seminars',          href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:News' },
     var currentUrl = window.location.href.split( '?' )[0];
        { label: 'Classes & Training', href: 'https://igb.mit.edu/mini-courses', ext: true }
 
      ]
    portlets.forEach( function ( portlet ) {
     },
       if ( SKIP_IDS[ portlet.id ] ) return;
     {
 
      label: 'Services',
       var links = Array.prototype.slice.call(
      href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:Assisted_Services',
         portlet.querySelectorAll( 'li a' )
       match: [ 'Assisted_Services', 'Walkup', 'Service', 'Consumable', 'SingleCell', 'SpTx', 'Sequencing', 'Analysis' ],
      ).map( function ( a ) {
       items: [
         return { label: a.textContent.trim(), href: a.href };
        { label: 'Walkup',      href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:Walkup_Services' },
      } ).filter( function ( item ) { return item.label && item.href; } );
         { label: 'Assisted',    href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:Assisted_Services' },
 
         { label: 'Consumables', href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:Consumables' },
       if ( !links.length ) return;
        { label: 'Training',    href: 'https://igb.mit.edu/mini-courses', ext: true },
 
        { label: 'Informatics', href: 'https://igb.mit.edu/', ext: true }
       var headingEl = portlet.querySelector( '[id$="-label"], h3, h2' );
       ]
       var sectionLabel = headingEl ? headingEl.textContent.trim() : '';
    },
 
    {
      var isActive = links.some( function ( link ) {
       label: 'Submission',
         return link.href.split( '?' )[0] === currentUrl;
      href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:Submission',
       } );
      match: [ 'Submission', 'Submit', 'Pricing' ],
 
       items: [
       if ( links.length === 1 ) {
        { groupLabel: 'MIT Users' },
        nav.push( {
        { label: 'Submit a Sample', href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:Submission' },
          label:   links[0].label,
        { label: 'MIT Pricing',    href: 'https://bmcwiki.mit.edu/index.php/MIT:Pricing' },
          href:     links[0].href,
        { divider: true },
          isActive: links[0].href.split( '?' )[0] === currentUrl
        { groupLabel: 'Non-MIT Users' },
        } );
         { label: 'External Submission', href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:Submission' },
       } else {
        { label: 'External Pricing',    href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:Pricing' }
         nav.push( {
       ]
          label:   sectionLabel || links[0].label,
    },
          href:     links[0].href,
    {
          items:   links,
       label: 'Staff',
          isActive: isActive
      href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:People',
         } );
      match: [ 'People', 'Staff' ]
       }
    },
     } );
    {
 
      label: 'Resources',
    return nav;
      href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:FAQ',
   }
      match: [ 'FAQ', 'Resource', 'Form', 'Acknowledgement', 'Consulting' ],
       items: [
         { label: 'FAQs',                    href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:FAQ' },
        { label: 'Consulting',              href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:Consulting' },
        { label: 'Grant Support & Pricing', href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:Pricing' },
         { label: 'Acknowledgements',        href: 'https://bmcwiki.mit.edu/index.php/BioMicroCenter:Acknowledgement' }
       ]
     }
   ];


   /* ── Build topbar HTML ─────────────────────────────────────── */
   /* ── Build topbar HTML ─────────────────────────────────────── */
Line 105: Line 96:
       } else {
       } else {
         var a = document.createElement( 'a' );
         var a = document.createElement( 'a' );
         a.href = item.href;
         a.href = item.page ? mw.util.getUrl( item.page ) : item.href;
         a.textContent = item.label;
         a.textContent = item.label;
         if ( item.ext ) a.target = '_blank';
         if ( item.ext ) a.target = '_blank';
Line 115: Line 106:


   /* ── Build full header ─────────────────────────────────────── */
   /* ── Build full header ─────────────────────────────────────── */
   function buildHeader( currentPage ) {
   function buildHeader( currentPage, nav ) {
     /* Logo */
     /* Logo */
     var logo = document.createElement( 'a' );
     var logo = document.createElement( 'a' );
     logo.href = 'https://bmcwiki.mit.edu/index.php/BioMicroCenter';
     logo.href = mw.util.getUrl( 'BioMicroCenter' );
     logo.className = 'bmc-logo';
     logo.className = 'bmc-logo';
     logo.innerHTML =
     logo.innerHTML =
       '<img class="bmc-logo-img" src="data:image/jpeg;base64,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" alt="MIT BioMicro Center" />';
       '<img class="bmc-logo-img" src="https://bmcwiki.mit.edu/images/c/c9/Logo.png" alt="MIT BioMicro Center" />';


     /* Nav */
     /* Nav */
     var ul = document.createElement( 'ul' );
     var ul = document.createElement( 'ul' );
     NAV.forEach( function ( item ) {
     nav.forEach( function ( item ) {
       var li = document.createElement( 'li' );
       var li = document.createElement( 'li' );
       var isActive = ( item.match || [] ).some( function ( m ) {
       if ( item.isActive ) li.classList.add( 'bmc-active' );
        return currentPage.indexOf( m ) !== -1;
      } );
      if ( isActive ) li.classList.add( 'bmc-active' );
       if ( item.items ) li.classList.add( 'bmc-dropdown' );
       if ( item.items ) li.classList.add( 'bmc-dropdown' );


       var a = document.createElement( 'a' );
       var a = document.createElement( 'a' );
       a.href = item.href;
       a.href = item.page ? mw.util.getUrl( item.page ) : ( item.href || '#' );
       a.textContent = item.label;
       a.textContent = item.label;
       li.appendChild( a );
       li.appendChild( a );
Line 144: Line 132:
     } );
     } );


     var nav = document.createElement( 'nav' );
     var navEl = document.createElement( 'nav' );
     nav.className = 'bmc-nav';
     navEl.className = 'bmc-nav';
     nav.appendChild( ul );
     navEl.appendChild( ul );


     /* Search */
     /* Search */
Line 156: Line 144:
       '<input type="hidden" name="title" value="Special:Search">' +
       '<input type="hidden" name="title" value="Special:Search">' +
       '<input type="search" name="search" placeholder="Search wiki…" aria-label="Search">' +
       '<input type="search" name="search" placeholder="Search wiki…" aria-label="Search">' +
       '<button type="submit">&#9906;</button>';
       '<button type="submit" aria-label="Search"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></button>';


     var right = document.createElement( 'div' );
     var right = document.createElement( 'div' );
     right.className = 'bmc-header-right';
     right.className = 'bmc-header-right';
     right.appendChild( nav );
     right.appendChild( navEl );
     right.appendChild( searchForm );
     right.appendChild( searchForm );


Line 180: Line 168:
     crumb.className = 'bmc-breadcrumb';
     crumb.className = 'bmc-breadcrumb';
     crumb.innerHTML =
     crumb.innerHTML =
       '<a href="https://bmcwiki.mit.edu/index.php/BioMicroCenter">Home</a>' +
       '<a href="' + mw.util.getUrl( 'BioMicroCenter' ) + '">Home</a>' +
       '<span class="sep">›</span>' +
       '<span class="sep">›</span>' +
       document.createTextNode( titleText ).textContent; // plain text, no XSS
       document.createTextNode( titleText ).textContent; // plain text, no XSS
Line 252: Line 240:
   /* ── Build footer ──────────────────────────────────────────── */
   /* ── Build footer ──────────────────────────────────────────── */
   function buildFooter() {
   function buildFooter() {
    var u = mw.util.getUrl;
     var inner = document.createElement( 'div' );
     var inner = document.createElement( 'div' );
     inner.className = 'bmc-footer-inner';
     inner.className = 'bmc-footer-inner';
Line 267: Line 256:
         '<h4>Services</h4>' +
         '<h4>Services</h4>' +
         '<ul>' +
         '<ul>' +
           '<li><a href="https://bmcwiki.mit.edu/index.php/BioMicroCenter:Sequencing">Bulk Sequencing</a></li>' +
           '<li><a href="' + u( 'BioMicroCenter:Sequencing' ) + '">Bulk Sequencing</a></li>' +
           '<li><a href="https://bmcwiki.mit.edu/index.php/BioMicroCenter:SingleCell">Single Cell</a></li>' +
           '<li><a href="' + u( 'BioMicroCenter:SingleCell' ) + '">Single Cell</a></li>' +
           '<li><a href="https://bmcwiki.mit.edu/index.php/BioMicroCenter:SpTx">Spatial Genomics</a></li>' +
           '<li><a href="' + u( 'BioMicroCenter:SpTx' ) + '">Spatial Genomics</a></li>' +
           '<li><a href="https://igb.mit.edu/" target="_blank">Informatics</a></li>' +
           '<li><a href="https://igb.mit.edu/" target="_blank">Informatics</a></li>' +
         '</ul>' +
         '</ul>' +
Line 276: Line 265:
         '<h4>Resources</h4>' +
         '<h4>Resources</h4>' +
         '<ul>' +
         '<ul>' +
           '<li><a href="https://bmcwiki.mit.edu/index.php/BioMicroCenter:FAQ">FAQs</a></li>' +
           '<li><a href="' + u( 'BioMicroCenter:FAQ' ) + '">FAQs</a></li>' +
           '<li><a href="https://bmcwiki.mit.edu/index.php/BioMicroCenter:Consulting">Consulting</a></li>' +
           '<li><a href="' + u( 'BioMicroCenter:Consulting' ) + '">Consulting</a></li>' +
           '<li><a href="https://bmcwiki.mit.edu/index.php/BioMicroCenter:Pricing">Grant Support &amp; Pricing</a></li>' +
           '<li><a href="' + u( 'BioMicroCenter:Pricing' ) + '">Grant Support &amp; Pricing</a></li>' +
           '<li><a href="https://bmcwiki.mit.edu/index.php/BioMicroCenter:Acknowledgement">Acknowledgements</a></li>' +
           '<li><a href="' + u( 'BioMicroCenter:Acknowledgement' ) + '">Acknowledgements</a></li>' +
         '</ul>' +
         '</ul>' +
       '</div>';
       '</div>';
Line 288: Line 277:
       '<span>&copy; ' + new Date().getFullYear() + ' MIT BioMicro Center</span>' +
       '<span>&copy; ' + new Date().getFullYear() + ' MIT BioMicro Center</span>' +
       '<span>' +
       '<span>' +
         '<a href="https://bmcwiki.mit.edu/index.php/Special:UserLogin">Log in</a>' +
         '<a href="' + u( 'Special:UserLogin' ) + '">Log in</a>' +
         ' &nbsp;|&nbsp; ' +
         ' &nbsp;|&nbsp; ' +
         '<a href="https://accessibility.mit.edu" target="_blank">Accessibility</a>' +
         '<a href="https://accessibility.mit.edu" target="_blank">Accessibility</a>' +
Line 348: Line 337:
       } );
       } );
     } );
     } );
  }
  /* ── Edit bar for logged-in users ─────────────────────────── */
  function buildEditBar( currentPage ) {
    if ( !mw.config.get( 'wgUserId' ) ) return null;
    var bar = document.createElement( 'div' );
    bar.className = 'bmc-edit-bar';
    var editHref = mw.util.getUrl( currentPage, { action: 'edit' } );
    var histHref = mw.util.getUrl( currentPage, { action: 'history' } );
    var talkHref = mw.util.getUrl( 'Talk:' + currentPage );
    bar.innerHTML =
      '<a href="' + editHref + '">Edit</a>' +
      '<a href="' + histHref + '">History</a>' +
      '<a href="' + talkHref + '">Talk</a>';
    return bar;
   }
   }


   /* ── Main: build and inject the shell ─────────────────────── */
   /* ── Main: build and inject the shell ─────────────────────── */
   function buildShell() {
   function buildShell( nav ) {
     var currentPage = mw.config.get( 'wgPageName' ) || '';
     var currentPage = mw.config.get( 'wgPageName' ) || '';


Line 377: Line 381:
     contentClone.querySelectorAll( 'img' ).forEach( function ( img ) {
     contentClone.querySelectorAll( 'img' ).forEach( function ( img ) {
       var src = img.getAttribute( 'src' ) || '';
       var src = img.getAttribute( 'src' ) || '';
       if ( /BMC_Header|bmc_logo_square|Bmc_logo/i.test( src ) ) {
       if ( /BMC_Header|bmc_logo_square|Bmc_logo|\/Logo\.png/i.test( src ) ) {
         var container = img.closest( '.thumb' ) || img.closest( 'figure' ) || img.closest( 'td' );
         var container = img.closest( '.thumb' ) || img.closest( 'figure' ) || img.closest( 'td' );
         if ( container ) {
         if ( container ) {
Line 389: Line 393:
     /* 4. Build shell elements */
     /* 4. Build shell elements */
     var topbar = buildTopbar();
     var topbar = buildTopbar();
     var header = buildHeader( currentPage );
     var header = buildHeader( currentPage, nav );
     var hero  = buildHero( titleText );
     var hero  = buildHero( titleText );


Line 410: Line 414:
     var main = document.createElement( 'main' );
     var main = document.createElement( 'main' );
     main.className = 'bmc-content';
     main.className = 'bmc-content';
    var editBar = buildEditBar( currentPage );
    if ( editBar ) { main.appendChild( editBar ); }
     main.appendChild( contentClone );
     main.appendChild( contentClone );
     layout.appendChild( main );
     layout.appendChild( main );
Line 446: Line 452:
       fixRowspanSeparators( main );
       fixRowspanSeparators( main );
     } );
     } );


   }
   }
Line 452: Line 459:
   mw.hook( 'wikipage.content' ).add( function () {
   mw.hook( 'wikipage.content' ).add( function () {
     /* Skip special pages (edit forms, history, etc.) to not break them */
     /* Skip special pages (edit forms, history, etc.) to not break them */
     var ns = mw.config.get( 'wgNamespaceNumber' );
     var ns     = mw.config.get( 'wgNamespaceNumber' );
     var action = mw.config.get( 'wgAction' );
     var action = mw.config.get( 'wgAction' );
     if ( action !== 'view' ) return;
     if ( action !== 'view' ) return;
     if ( ns < 0 ) return; /* Special: pages */
     if ( ns < 0 ) return; /* Special: pages */


     buildShell();
    var nav = extractNavFromDom();
     buildShell( nav );
   } );
   } );


} )();
} )();

Latest revision as of 17:46, 21 May 2026

/**
 * BioMicro Center Wiki — Full DOM Replacement
 * Paste into: User:USERNAME/common.js
 *
 * Strategy:
 * 1. Extract wiki article content from MediaWiki's DOM
 * 2. Inject the complete shell structure
 * 3. Place article content inside the shell
 * MediaWiki still runs underneath for editing, search, login, etc.
 */

( function () {
  'use strict';

  /* ── Extract nav from MediaWiki's sidebar DOM ─────────────── */
  function extractNavFromDom() {
    var SKIP_IDS = {
      'p-tb': 1, 'p-personal': 1, 'p-search': 1,
      'p-logo': 1, 'p-cactions': 1, 'p-views': 1,
      'p-lang': 1, 'p-namespaces': 1
    };

    var portlets = Array.prototype.slice.call(
      document.querySelectorAll( '[id^="p-"]' )
    );

    var nav = [];
    var currentUrl = window.location.href.split( '?' )[0];

    portlets.forEach( function ( portlet ) {
      if ( SKIP_IDS[ portlet.id ] ) return;

      var links = Array.prototype.slice.call(
        portlet.querySelectorAll( 'li a' )
      ).map( function ( a ) {
        return { label: a.textContent.trim(), href: a.href };
      } ).filter( function ( item ) { return item.label && item.href; } );

      if ( !links.length ) return;

      var headingEl = portlet.querySelector( '[id$="-label"], h3, h2' );
      var sectionLabel = headingEl ? headingEl.textContent.trim() : '';

      var isActive = links.some( function ( link ) {
        return link.href.split( '?' )[0] === currentUrl;
      } );

      if ( links.length === 1 ) {
        nav.push( {
          label:    links[0].label,
          href:     links[0].href,
          isActive: links[0].href.split( '?' )[0] === currentUrl
        } );
      } else {
        nav.push( {
          label:    sectionLabel || links[0].label,
          href:     links[0].href,
          items:    links,
          isActive: isActive
        } );
      }
    } );

    return nav;
  }

  /* ── Build topbar HTML ─────────────────────────────────────── */
  function buildTopbar() {
    var d = document.createElement( 'div' );
    d.className = 'bmc-topbar';
    d.innerHTML =
      '<div class="inner">' +
        '<a href="mailto:biomicro@mit.edu">biomicro@mit.edu</a>' +
        '<span>|</span>' +
        '617-715-4533' +
        '<span>|</span>' +
        'Building 68-322' +
      '</div>';
    return d;
  }

  /* ── Build nav dropdown item ───────────────────────────────── */
  function buildDropdownMenu( items ) {
    var menu = document.createElement( 'div' );
    menu.className = 'bmc-dropdown-menu';
    items.forEach( function ( item ) {
      if ( item.divider ) {
        var div = document.createElement( 'div' );
        div.className = 'bmc-divider';
        menu.appendChild( div );
      } else if ( item.groupLabel ) {
        var gl = document.createElement( 'div' );
        gl.className = 'bmc-group-label';
        gl.textContent = item.groupLabel;
        menu.appendChild( gl );
      } else {
        var a = document.createElement( 'a' );
        a.href = item.page ? mw.util.getUrl( item.page ) : item.href;
        a.textContent = item.label;
        if ( item.ext ) a.target = '_blank';
        menu.appendChild( a );
      }
    } );
    return menu;
  }

  /* ── Build full header ─────────────────────────────────────── */
  function buildHeader( currentPage, nav ) {
    /* Logo */
    var logo = document.createElement( 'a' );
    logo.href = mw.util.getUrl( 'BioMicroCenter' );
    logo.className = 'bmc-logo';
    logo.innerHTML =
      '<img class="bmc-logo-img" src="https://bmcwiki.mit.edu/images/c/c9/Logo.png" alt="MIT BioMicro Center" />';

    /* Nav */
    var ul = document.createElement( 'ul' );
    nav.forEach( function ( item ) {
      var li = document.createElement( 'li' );
      if ( item.isActive ) li.classList.add( 'bmc-active' );
      if ( item.items ) li.classList.add( 'bmc-dropdown' );

      var a = document.createElement( 'a' );
      a.href = item.page ? mw.util.getUrl( item.page ) : ( item.href || '#' );
      a.textContent = item.label;
      li.appendChild( a );

      if ( item.items ) {
        li.appendChild( buildDropdownMenu( item.items ) );
      }
      ul.appendChild( li );
    } );

    var navEl = document.createElement( 'nav' );
    navEl.className = 'bmc-nav';
    navEl.appendChild( ul );

    /* Search */
    var searchForm = document.createElement( 'form' );
    searchForm.className = 'bmc-search-form';
    searchForm.method = 'get';
    searchForm.action = '/index.php';
    searchForm.innerHTML =
      '<input type="hidden" name="title" value="Special:Search">' +
      '<input type="search" name="search" placeholder="Search wiki…" aria-label="Search">' +
      '<button type="submit" aria-label="Search"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></button>';

    var right = document.createElement( 'div' );
    right.className = 'bmc-header-right';
    right.appendChild( navEl );
    right.appendChild( searchForm );

    /* Inner wrapper */
    var inner = document.createElement( 'div' );
    inner.className = 'bmc-header-inner';
    inner.appendChild( logo );
    inner.appendChild( right );

    var header = document.createElement( 'header' );
    header.className = 'bmc-header';
    header.appendChild( inner );
    return header;
  }

  /* ── Build page hero ───────────────────────────────────────── */
  function buildHero( titleText ) {
    var crumb = document.createElement( 'div' );
    crumb.className = 'bmc-breadcrumb';
    crumb.innerHTML =
      '<a href="' + mw.util.getUrl( 'BioMicroCenter' ) + '">Home</a>' +
      '<span class="sep">›</span>' +
      document.createTextNode( titleText ).textContent; // plain text, no XSS

    var h1 = document.createElement( 'h1' );
    h1.textContent = titleText;

    var inner = document.createElement( 'div' );
    inner.className = 'inner';
    inner.appendChild( crumb );
    inner.appendChild( h1 );

    var hero = document.createElement( 'div' );
    hero.className = 'bmc-page-hero';
    hero.appendChild( inner );
    return hero;
  }

  /* ── Build sidebar TOC from MediaWiki #toc or heading scan ─── */
  function buildSidebarToc( mwToc, contentEl ) {
    var toc = document.createElement( 'div' );
    toc.className = 'bmc-toc';

    var h3 = document.createElement( 'h3' );
    h3.textContent = 'On this page';
    toc.appendChild( h3 );

    var list;

    if ( mwToc ) {
      /* Clone only the list from the MediaWiki TOC */
      var mwList = mwToc.querySelector( 'ul' );
      if ( mwList ) {
        list = mwList.cloneNode( true );
        /* Mark sub-items */
        list.querySelectorAll( 'li li' ).forEach( function ( li ) {
          li.classList.add( 'bmc-sub' );
        } );
      }
    } else if ( contentEl ) {
      /* Fall back to scanning h2/h3 headings directly */
      var headings = Array.prototype.slice.call(
        contentEl.querySelectorAll( 'h2, h3' )
      );
      if ( headings.length > 0 ) {
        list = document.createElement( 'ul' );
        headings.forEach( function ( heading ) {
          var anchor = heading.querySelector( '.mw-headline' );
          if ( !anchor || !anchor.id ) return;
          var li = document.createElement( 'li' );
          if ( heading.tagName === 'H3' ) li.classList.add( 'bmc-sub' );
          var a = document.createElement( 'a' );
          a.href = '#' + anchor.id;
          a.textContent = anchor.textContent.trim();
          li.appendChild( a );
          list.appendChild( li );
        } );
      }
    }

    if ( !list || list.childNodes.length === 0 ) return null;

    toc.appendChild( list );

    var aside = document.createElement( 'aside' );
    aside.className = 'bmc-sidebar';
    aside.appendChild( toc );
    return aside;
  }

  /* ── Build footer ──────────────────────────────────────────── */
  function buildFooter() {
    var u = mw.util.getUrl;
    var inner = document.createElement( 'div' );
    inner.className = 'bmc-footer-inner';
    inner.innerHTML =
      '<div>' +
        '<h4>MIT BioMicro Center</h4>' +
        '<ul>' +
          '<li>Building 68-322</li>' +
          '<li>Cambridge, MA 02139</li>' +
          '<li><a href="mailto:biomicro@mit.edu">biomicro@mit.edu</a></li>' +
          '<li>617-715-4533</li>' +
        '</ul>' +
      '</div>' +
      '<div>' +
        '<h4>Services</h4>' +
        '<ul>' +
          '<li><a href="' + u( 'BioMicroCenter:Sequencing' ) + '">Bulk Sequencing</a></li>' +
          '<li><a href="' + u( 'BioMicroCenter:SingleCell' ) + '">Single Cell</a></li>' +
          '<li><a href="' + u( 'BioMicroCenter:SpTx' ) + '">Spatial Genomics</a></li>' +
          '<li><a href="https://igb.mit.edu/" target="_blank">Informatics</a></li>' +
        '</ul>' +
      '</div>' +
      '<div>' +
        '<h4>Resources</h4>' +
        '<ul>' +
          '<li><a href="' + u( 'BioMicroCenter:FAQ' ) + '">FAQs</a></li>' +
          '<li><a href="' + u( 'BioMicroCenter:Consulting' ) + '">Consulting</a></li>' +
          '<li><a href="' + u( 'BioMicroCenter:Pricing' ) + '">Grant Support &amp; Pricing</a></li>' +
          '<li><a href="' + u( 'BioMicroCenter:Acknowledgement' ) + '">Acknowledgements</a></li>' +
        '</ul>' +
      '</div>';

    var bottom = document.createElement( 'div' );
    bottom.className = 'bmc-footer-bottom';
    bottom.innerHTML =
      '<span>&copy; ' + new Date().getFullYear() + ' MIT BioMicro Center</span>' +
      '<span>' +
        '<a href="' + u( 'Special:UserLogin' ) + '">Log in</a>' +
        ' &nbsp;|&nbsp; ' +
        '<a href="https://accessibility.mit.edu" target="_blank">Accessibility</a>' +
      '</span>';

    var footer = document.createElement( 'footer' );
    footer.className = 'bmc-footer';
    footer.appendChild( inner );
    footer.appendChild( bottom );
    return footer;
  }

  /* ── Smooth scroll for anchor links ───────────────────────── */
  function initSmoothScroll( container ) {
    container.addEventListener( 'click', function ( e ) {
      var a = e.target.closest( 'a[href^="#"]' );
      if ( !a ) return;
      var id = decodeURIComponent( a.getAttribute( 'href' ).slice( 1 ) );
      var target = document.getElementById( id );
      if ( target ) {
        e.preventDefault();
        target.scrollIntoView( { behavior: 'smooth', block: 'start' } );
        history.pushState( null, '', '#' + id );
      }
    } );
  }

  /* ── Fix row separators on tables with rowspan cells ──────── */
  function fixRowspanSeparators( container ) {
    container.querySelectorAll( '.wikitable' ).forEach( function ( table ) {
      var tbody = table.querySelector( 'tbody' ) || table;
      var rows = Array.from( tbody.querySelectorAll( 'tr' ) );
      if ( !rows.length ) return;

      rows.forEach( function ( row, rowIdx ) {
        row.querySelectorAll( 'td[rowspan], th[rowspan]' ).forEach( function ( cell ) {
          var span = parseInt( cell.getAttribute( 'rowspan' ), 10 );
          if ( !span || span <= 1 ) return;

          cell.style.position = 'relative';

          for ( var i = 1; i < span; i++ ) {
            var targetRow = rows[ rowIdx + i ];
            if ( !targetRow ) break;

            var cellRect  = cell.getBoundingClientRect();
            var rowRect   = targetRow.getBoundingClientRect();
            var offsetTop = rowRect.top - cellRect.top;

            var sep = document.createElement( 'div' );
            sep.style.cssText =
              'position:absolute;left:0;right:0;' +
              'top:' + offsetTop + 'px;' +
              'height:1px;background:#e0e0e0;' +
              'pointer-events:none;z-index:1;';
            cell.appendChild( sep );
          }
        } );
      } );
    } );
  }

  /* ── Edit bar for logged-in users ─────────────────────────── */
  function buildEditBar( currentPage ) {
    if ( !mw.config.get( 'wgUserId' ) ) return null;
    var bar = document.createElement( 'div' );
    bar.className = 'bmc-edit-bar';
    var editHref = mw.util.getUrl( currentPage, { action: 'edit' } );
    var histHref = mw.util.getUrl( currentPage, { action: 'history' } );
    var talkHref = mw.util.getUrl( 'Talk:' + currentPage );
    bar.innerHTML =
      '<a href="' + editHref + '">Edit</a>' +
      '<a href="' + histHref + '">History</a>' +
      '<a href="' + talkHref + '">Talk</a>';
    return bar;
  }

  /* ── Main: build and inject the shell ─────────────────────── */
  function buildShell( nav ) {
    var currentPage = mw.config.get( 'wgPageName' ) || '';

    /* 1. Extract wiki content BEFORE any DOM manipulation */
    var mwContentText = document.getElementById( 'mw-content-text' );
    if ( !mwContentText ) return; /* not a content page, bail */

    var contentClone = mwContentText.cloneNode( true );

    /* 2. Extract page title */
    var titleEl = document.getElementById( 'firstHeading' ) ||
      document.querySelector( '.mw-first-heading' );
    var titleText = titleEl
      ? titleEl.textContent.trim()
      : ( mw.config.get( 'wgTitle' ) || 'BioMicro Center' );

    /* 3. Extract MediaWiki TOC from the cloned content */
    var mwToc = contentClone.querySelector( '#toc, .toc' );
    if ( mwToc ) mwToc.parentNode.removeChild( mwToc ); /* remove from content body */

    /* 3b. Hide the BMC logo/header sticker by exact image filename.
           The sticker is BMC_Header_2020_3.png (from the {{BioMicroCenter}} template)
           and bmc_logo_square.png. Target the nearest meaningful container so the
           surrounding content (nav links, table structure) is preserved. */
    contentClone.querySelectorAll( 'img' ).forEach( function ( img ) {
      var src = img.getAttribute( 'src' ) || '';
      if ( /BMC_Header|bmc_logo_square|Bmc_logo|\/Logo\.png/i.test( src ) ) {
        var container = img.closest( '.thumb' ) || img.closest( 'figure' ) || img.closest( 'td' );
        if ( container ) {
          container.style.display = 'none';
        } else {
          img.style.display = 'none';
        }
      }
    } );

    /* 4. Build shell elements */
    var topbar = buildTopbar();
    var header = buildHeader( currentPage, nav );
    var hero   = buildHero( titleText );

    /* Page layout grid */
    var layout = document.createElement( 'div' );
    layout.className = 'bmc-page-layout';

    /* Sidebar — skip on the home page */
    var isHome = ( currentPage === 'BioMicroCenter' ||
                   currentPage === 'Main_Page' );
    var sidebar = isHome ? null : buildSidebarToc( mwToc, contentClone );
    if ( sidebar ) {
      layout.appendChild( sidebar );
    } else {
      /* No TOC — collapse to single-column via inline style */
      layout.style.gridTemplateColumns = '1fr';
    }

    /* Main content area */
    var main = document.createElement( 'main' );
    main.className = 'bmc-content';
    var editBar = buildEditBar( currentPage );
    if ( editBar ) { main.appendChild( editBar ); }
    main.appendChild( contentClone );
    layout.appendChild( main );

    var footer = buildFooter();

    /* 5. Build wrapper */
    var wrapper = document.createElement( 'div' );
    wrapper.id = 'bmc-wrapper';
    wrapper.appendChild( topbar );
    wrapper.appendChild( header );
    wrapper.appendChild( hero );
    wrapper.appendChild( layout );
    wrapper.appendChild( footer );

    /* 6. Inject into body (prepend so it appears first) */
    document.body.insertBefore( wrapper, document.body.firstChild );

    /* 7. Add class immediately so the CSS rule kicks in and hides
          all original MW elements (works for any skin version) */
    document.body.classList.add( 'bmc-active' );

    /* 8. Smooth scroll */
    initSmoothScroll( main );
    initSmoothScroll( sidebar || main );

    /* 9. Auto-style plain tables in content */
    main.querySelectorAll(
      '.mw-parser-output table:not(.wikitable):not(.infobox):not(.navbox)'
    ).forEach( function ( t ) {
      t.classList.add( 'wikitable' );
    } );

    /* 10. Fix row separators on rowspan tables */
    requestAnimationFrame( function () {
      fixRowspanSeparators( main );
    } );


  }

  /* ── Entry point ───────────────────────────────────────────── */
  mw.hook( 'wikipage.content' ).add( function () {
    /* Skip special pages (edit forms, history, etc.) to not break them */
    var ns     = mw.config.get( 'wgNamespaceNumber' );
    var action = mw.config.get( 'wgAction' );
    if ( action !== 'view' ) return;
    if ( ns < 0 ) return; /* Special: pages */

    var nav = extractNavFromDom();
    buildShell( nav );
  } );

} )();