MediaWiki:Common.js: Difference between revisions

No edit summary
Tags: Manual revert Reverted
Undo revision 5755 by Chandrashekars (talk)
Tag: Undo
Line 1,242: Line 1,242:
   /* ── Expose storeQueryForLink for readerToolbar to call ── */
   /* ── Expose storeQueryForLink for readerToolbar to call ── */
   window.grStoreSearchHL = storeQueryForLink;
   window.grStoreSearchHL = storeQueryForLink;
}() );
/* ═══════════════════════════════════════════════════════════════
  Mobile addon — paste at bottom of MediaWiki:Common.js
  Only runs on Minerva (mobile) skin
  ═══════════════════════════════════════════════════════════════ */
( function () {
  if ( !document.body.classList.contains( 'skin-minerva' ) ) return;
  /* ── 1. CSS injected into <head> — beats all other stylesheets ── */
  function injectCSS() {
    if ( document.getElementById( 'gr-mob-css' ) ) return;
    var s = document.createElement( 'style' );
    s.id = 'gr-mob-css';
    s.textContent =
      /* Body padding — readerToolbar sets this, we clear it */
      'body,#mw-mf-viewport,#mw-mf-page-center{padding-top:0!important;margin-top:0!important;}' +
      'html,body,#mw-mf-viewport,#mw-mf-page-center{overflow-x:hidden!important;max-width:100vw!important;}' +
      /* Header orange + sticky */
      'header.header-container{background:#b5451b!important;position:sticky!important;top:0!important;z-index:300!important;}' +
      '.minerva-header{background:#b5451b!important;min-height:54px!important;}' +
      /* Header: keep only hamburger + branding, hide search */
      '.minerva-header .search-toggle,.minerva-header .minerva-user-notifications{display:none!important;}' +
      /* Logo row: icon + title wrap cleanly */
      '.branding-box a{display:flex!important;align-items:center!important;' +
        'text-decoration:none!important;max-width:calc(100vw - 80px)!important;}' +
      '.branding-box a::before{content:"";display:block;width:30px;height:30px;flex-shrink:0;' +
        'background:url("/favicon.png") center/contain no-repeat;' +
        'margin-right:8px;}' +
      '.branding-box a span{color:#fff!important;font-size:16px!important;font-weight:700!important;' +
        'font-family:system-ui,sans-serif!important;line-height:1.2!important;' +
        'flex:1 1 auto!important;min-width:0!important;}' +
      /* Header icons white */
      '.minerva-header svg path,.minerva-header svg rect,.minerva-header svg circle{fill:#fff!important;}' +
      '.minerva-header label{color:#fff!important;}' +
      /* Hide Page/Discussion tabs + icon toolbar */
      '.minerva-tabs,.mw-portlet-associated-pages,.page-actions-menu,' +
        '#page-secondary-actions,.last-modified-bar,.minerva-anon-talk-link{display:none!important;}' +
      /* Drawer: hide default items, show ours */
      /* #mw-mf-page-left is kept — we replace its content in JS */ '' +
      '#gr-mob-menu-items{display:block!important;}' +
      /* Drawer footer: hide About/Disclaimers */
      '.mw-footer.minerva-footer,.footer-places,.footer-info,' +
        '.minerva-footer-logo,#footer-places-about,' +
        '#footer-places-disclaimers,#footer-places-privacy{display:none!important;}' +
      /* ReaderToolbar below header */
      '#gr-static-bar{position:sticky!important;top:54px!important;z-index:200!important;}' +
      /* Sections expanded */
      '.mf-section-0,.mf-section-1,.mf-section-2,.mf-section-3,.mf-section-4,' +
        '.mf-section-5,.mf-section-6,.mf-section-7,.mf-section-8,.mf-section-9,' +
        '.mf-section-10{display:block!important;visibility:visible!important;}' +
      '.collapsible-block{display:block!important;}' +
      '.section-heading .indicator,.collapsible-heading .indicator{display:none!important;}' +
      '.section-heading,.collapsible-heading{pointer-events:none!important;}' +
      /* Home grid single column */
      '.gr-home-grid{flex-direction:column!important;flex-wrap:nowrap!important;' +
        'gap:12px!important;width:100%!important;}' +
      '.gr-home-card{width:100%!important;max-width:100%!important;' +
        'min-width:unset!important;box-sizing:border-box!important;flex:none!important;}' +
      '.gr-home-toggle{flex-wrap:wrap!important;}' +
      /* Content */
      '.mw-parser-output{font-size:18px!important;line-height:1.8!important;}' +
      '.mw-parser-output h2,.mw-parser-output h3{width:100%!important;}' +
      '.bhashyam-block{margin-left:8px!important;}' +
      '#footer,.mw-footer,.catlinks,#catlinks{display:none!important;}' +
      /* TOC overlay */
      '.gr-mob-toc-panel{position:fixed!important;top:0!important;left:0!important;' +
        'bottom:0!important;width:82vw!important;max-width:340px!important;' +
        'background:#fff!important;z-index:10400!important;' +
        'box-shadow:4px 0 28px rgba(0,0,0,0.22)!important;overflow-y:auto!important;' +
        'padding:0 0 40px!important;transform:translateX(-110%)!important;' +
        'transition:transform 0.26s cubic-bezier(0.4,0,0.2,1)!important;display:block!important;}' +
      '.gr-mob-toc-panel.open{transform:translateX(0)!important;}' +
      '.gr-mob-toc-backdrop{display:none!important;position:fixed!important;inset:0!important;' +
        'background:rgba(0,0,0,0.4)!important;z-index:10399!important;}' +
      '.gr-mob-toc-backdrop.open{display:block!important;}' +
      '.gr-mob-toc-header{position:sticky!important;top:0!important;background:#fff!important;' +
        'display:flex!important;align-items:center!important;justify-content:space-between!important;' +
        'padding:16px 16px 12px!important;border-bottom:1px solid #f0ebe6!important;z-index:1!important;}' +
      '.gr-mob-toc-title{font-size:13px!important;font-weight:700!important;text-transform:uppercase!important;' +
        'letter-spacing:0.08em!important;color:#b5451b!important;font-family:system-ui,sans-serif!important;}' +
      '.gr-mob-toc-close{background:none!important;border:none!important;' +
        'font-size:22px!important;color:#999!important;cursor:pointer!important;padding:4px 8px!important;}' +
      '.gr-mob-toc-body{padding:12px 16px!important;}' +
      '.gr-mob-toc-body a{display:block!important;font-size:16px!important;line-height:1.6!important;' +
        'color:#2c1810!important;text-decoration:none!important;padding:8px 0!important;' +
        'border-bottom:1px solid #f5f0ed!important;}';
    document.head.appendChild( s );
  }
  /* ── 2. Expand hidden sections ── */
  function expandSections() {
    document.querySelectorAll( '[class*="mf-section-"], .collapsible-block' )
      .forEach( function ( el ) {
        el.removeAttribute( 'hidden' );
        el.style.setProperty( 'display', 'block', 'important' );
        el.style.setProperty( 'visibility', 'visible', 'important' );
        el.removeAttribute( 'aria-hidden' );
      } );
    document.querySelectorAll( '.section-heading, .collapsible-heading' )
      .forEach( function ( el ) {
        el.setAttribute( 'aria-expanded', 'true' );
        el.style.setProperty( 'pointer-events', 'none', 'important' );
      } );
  }
  /* Persistent observer — fight MF re-collapsing */
  function watchSections() {
    var t = null;
    var obs = new MutationObserver( function ( ms ) {
      if ( ms.some( function(m){ return m.attributeName === 'hidden'; } ) ) {
        clearTimeout(t); t = setTimeout( expandSections, 30 );
      }
    } );
    var root = document.querySelector( '#mw-content-text' ) || document.body;
    obs.observe( root, { subtree:true, attributes:true, attributeFilter:['hidden','aria-hidden'] } );
  }
  /* ── 3. Body padding observer ── */
  function watchBodyPadding() {
    new MutationObserver( function () {
      if ( document.body.style.paddingTop && document.body.style.paddingTop !== '0px' ) {
        document.body.style.paddingTop = '';
      }
    } ).observe( document.body, { attributes:true, attributeFilter:['style'] } );
  }
  /* ── 4. Inject custom links into drawer ── */
  function injectMenuLinks() {
    /* Already injected */
    if ( document.getElementById( 'gr-mob-menu-items' ) ) return;
    /* The drawer container is .navigation-drawer (the <nav> element)
      Our links go AFTER the #mw-mf-page-left div (which we hide via CSS) */
    var navDrawer = document.querySelector( '.navigation-drawer' );
    if ( !navDrawer ) return;
    var wrap = document.createElement( 'div' );
    wrap.id = 'gr-mob-menu-items';
    wrap.style.cssText = 'width:100%;background:#fff;margin-top:8px;';
    var itemStyle = 'display:flex;align-items:center;gap:14px;padding:15px 20px;' +
      'font-size:16px;color:#2c1810;text-decoration:none;' +
      'font-family:system-ui,sans-serif;border-bottom:1px solid #f0ebe6;background:#fff;';
    function makeItem( href, emoji, label ) {
      var a = document.createElement( 'a' );
      a.href = href;
      a.style.cssText = itemStyle;
      a.innerHTML =
        '<span>' + label + '</span>';
      return a;
    }
    wrap.appendChild( makeItem( '/Main_Page', '', 'Home' ) );
    wrap.appendChild( makeItem( '/My_wiki:Help', '', 'Help' ) );
    wrap.appendChild( makeItem( '/My_wiki:About', '', 'About' ) );
    var userName = window.mw ? mw.config.get( 'wgUserName' ) : null;
    if ( userName ) {
      var la = document.querySelector( 'a[href*="action=logout"]' );
      wrap.appendChild( makeItem(
        la ? la.href : '/index.php?title=Special:UserLogout', '', 'Log out'
      ) );
    } else {
      wrap.appendChild( makeItem( '/index.php?title=Special:UserLogin', '', 'Log in' ) );
    }
    /* Replace contents of #mw-mf-page-left — it's inside the
      toggle-list mechanism so it only shows when drawer is open */
    var pageLeft = document.getElementById( 'mw-mf-page-left' );
    if ( pageLeft ) {
      /* Clear default items and insert ours */
      while ( pageLeft.firstChild ) pageLeft.removeChild( pageLeft.firstChild );
      pageLeft.style.removeProperty( 'display' ); /* un-hide it */
      pageLeft.appendChild( wrap );
    } else {
      /* Fallback */
      navDrawer.appendChild( wrap );
    }
    /* Hide footer portlets */
    document.querySelectorAll(
      '.mw-footer.minerva-footer,.footer-places,.footer-info,' +
      '.minerva-footer-logo,#footer-places-about,' +
      '#footer-places-disclaimers,#footer-places-privacy'
    ).forEach( function(el) { el.style.setProperty('display','none','important'); } );
  }
  /* ── 5. Mobile TOC overlay ── */
  var _tocDone = false;
  function initToc() {
    if ( _tocDone ) return;
    var tocList = document.querySelector( '.vector-toc-contents, .vector-toc .vector-toc-list' );
    if ( !tocList ) return;
    if ( !tocList.querySelector( 'li' ) ) return;
    _tocDone = true;
    var bd = document.createElement( 'div' );
    bd.className = 'gr-mob-toc-backdrop';
    document.body.appendChild( bd );
    var panel = document.createElement( 'div' );
    panel.className = 'gr-mob-toc-panel';
    var hdr = document.createElement( 'div' );
    hdr.className = 'gr-mob-toc-header';
    var ttl = document.createElement( 'div' );
    ttl.className = 'gr-mob-toc-title';
    ttl.textContent = 'विषयसूची';
    var cls = document.createElement( 'button' );
    cls.className = 'gr-mob-toc-close';
    cls.textContent = '✕';
    hdr.appendChild( ttl ); hdr.appendChild( cls );
    panel.appendChild( hdr );
    var body = document.createElement( 'div' );
    body.className = 'gr-mob-toc-body';
    body.appendChild( tocList.cloneNode( true ) );
    panel.appendChild( body );
    document.body.appendChild( panel );
    var btn = document.createElement( 'button' );
    btn.id = 'gr-mob-toc-btn';
    btn.innerHTML = '☰ &nbsp;Contents';
    btn.style.cssText = 'position:fixed;bottom:148px;left:16px;z-index:9100;' +
      'background:#fff;border:1.5px solid #b5451b;border-radius:24px;' +
      'padding:10px 16px;font-size:15px;font-family:system-ui,sans-serif;' +
      'color:#b5451b;font-weight:600;box-shadow:0 3px 14px rgba(0,0,0,0.15);cursor:pointer;';
    document.body.appendChild( btn );
    function open()  { panel.classList.add('open'); bd.classList.add('open'); document.body.style.overflow='hidden'; }
    function close() { panel.classList.remove('open'); bd.classList.remove('open'); document.body.style.overflow=''; }
    btn.onclick = open; cls.onclick = close; bd.onclick = close;
    body.querySelectorAll('a').forEach(function(a){ a.onclick = close; });
  }
  /* ── Boot ── */
  injectCSS();
  watchBodyPadding();
  function boot() {
    expandSections();
    watchSections();
    injectMenuLinks();
    [100, 400, 900, 1800].forEach(function(ms){ setTimeout(expandSections, ms); });
    setTimeout(initToc, 700);
  }
  if ( document.readyState === 'loading' ) {
    document.addEventListener( 'DOMContentLoaded', boot );
  } else {
    boot();
  }
  if ( window.mw ) {
    mw.hook( 'wikipage.content' ).add(function() {
      setTimeout(function(){ expandSections(); injectMenuLinks(); initToc(); }, 300);
    });
  }
}() );
/**
* grantha-mobile-fixes.js
* Add this to MediaWiki:Common.js (or load as a separate gadget)
*
* Fixes:
*  1. Patches Minerva's ToggleList to guard against null parentNode
*    (prevents "Cannot read properties of null" crash on Main_Page)
*  2. Keeps #gr-home-toggle visible below #gr-static-bar on mobile
*  3. Wires the grantha/author tab toggle if it hasn't been wired yet
*/
( function () {
  'use strict';
  /* ══════════════════════════════════════════════════════════════
  * 1. TOGGLELIST NULL GUARD
  * Minerva's initMobile.js runs ToggleList.init() which calls
  * el.parentNode on every .collapsible-block it finds.
  * On Main_Page, some of these are dynamically injected and may
  * not yet be in the live DOM when ToggleList runs.
  * We patch by removing any element that has no parentNode
  * before ToggleList.init() fires.
  * ══════════════════════════════════════════════════════════════ */
  mw.hook( 'wikipage.content' ).add( function () {
    /* Wait one tick so Minerva's own DOMReady handlers run first */
    setTimeout( function () {
      var blocks = document.querySelectorAll( '.collapsible-block, .toggle-list' );
      Array.prototype.forEach.call( blocks, function ( el ) {
        /* If the element has no parentNode it's detached — remove it
          before ToggleList tries to call parentNode on it          */
        if ( !el.parentNode ) {
          try { el.remove(); } catch(e) {}
        }
      } );
    }, 0 );
  } );
  /* ══════════════════════════════════════════════════════════════
  * 2. MAIN PAGE: keep #gr-home-toggle below the reader bar
  * ══════════════════════════════════════════════════════════════ */
  if ( mw.config.get( 'wgPageName' ) !== 'Main_Page' ) return;
  mw.loader.using( 'mediawiki.util' ).done( function () {
    $( function () {
      applyHomeToggleOffset();
      /* Re-apply when bar height might change (orientation change, etc.) */
      window.addEventListener( 'resize', applyHomeToggleOffset, { passive: true } );
      /* Re-apply after a short delay in case the bar hasn't rendered yet */
      setTimeout( applyHomeToggleOffset, 300 );
      setTimeout( applyHomeToggleOffset, 800 );
    } );
  } );
  function applyHomeToggleOffset() {
    var bar = document.getElementById( 'gr-static-bar' );
    if ( !bar ) return;
    var barRect = bar.getBoundingClientRect();
    /* barRect.bottom = distance from viewport top to bar bottom.
      On load (before any scroll) this equals the bar's actual top offset
      plus its height. After scrolling (Minerva header gone) it's just
      the bar height since bar.top → 0.                                  */
    var barBottom = Math.round( barRect.bottom );
    /* #gr-home is the outermost container */
    var homeEl = document.getElementById( 'gr-home' );
    /* #gr-home-toggle is the tab switcher row */
    var toggleEl = document.getElementById( 'gr-home-toggle' );
    /* Also target the mw-parser-output first child as a fallback */
    var firstChild = document.querySelector(
      '#mw-content-text .mw-parser-output > .gr-home, ' +
      '#mw-content-text .mw-parser-output > *:first-child'
    );
    /* scroll-margin-top: so that if someone scrolls-to-top the toggle
      is visible below the bar (not behind it)                        */
    [ homeEl, toggleEl, firstChild ].forEach( function ( el ) {
      if ( el ) el.style.scrollMarginTop = ( barBottom + 4 ) + 'px';
    } );
    /* The body already has padding-top set by readerToolbar.js.
      But on Main_Page the toggle might STILL be hidden if the
      Minerva header is taller. Force the content area's top
      padding to be at least barBottom.                                */
    var contentText = document.getElementById( 'mw-content-text' );
    if ( contentText ) {
      /* Only add extra padding if we're on mobile */
      var isMob = window.innerWidth < 768 || !!document.getElementById( 'mw-mf-viewport' );
      if ( isMob ) {
        /* Check if the toggle is actually obscured */
        if ( toggleEl ) {
          var toggleRect = toggleEl.getBoundingClientRect();
          /* If toggle top < barBottom, the bar is covering it */
          if ( toggleRect.top < barBottom ) {
            var currentPT = parseInt( window.getComputedStyle( document.body ).paddingTop, 10 ) || 0;
            var needed    = currentPT + ( barBottom - toggleRect.top ) + 4;
            document.body.style.paddingTop = needed + 'px';
          }
        }
      }
    }
  }
  /* ══════════════════════════════════════════════════════════════
  * 3. WIRE THE GRANTHA / AUTHOR TAB TOGGLE
  * In case the toggle script hasn't loaded yet or failed
  * ══════════════════════════════════════════════════════════════ */
  $( function () {
    var $toggle  = $( '#gr-home-toggle' );
    var $viewG    = $( '#gr-view-grantha' );
    var $viewA    = $( '#gr-view-author' );
    var $btnG    = $( '#gr-toggle-grantha' );
    var $btnA    = $( '#gr-toggle-author' );
    if ( !$toggle.length || !$viewG.length || !$viewA.length ) return;
    /* Only wire if not already wired */
    if ( $toggle.data( 'gr-wired' ) ) return;
    $toggle.data( 'gr-wired', true );
    function showView( which ) {
      if ( which === 'grantha' ) {
        $viewG.show(); $viewA.hide();
        $btnG.addClass( 'gr-toggle-active' );
        $btnA.removeClass( 'gr-toggle-active' );
      } else {
        $viewA.show(); $viewG.hide();
        $btnA.addClass( 'gr-toggle-active' );
        $btnG.removeClass( 'gr-toggle-active' );
      }
      try { localStorage.setItem( 'grantha_home_tab', which ); } catch(e){}
    }
    $btnG.on( 'click keydown', function(e){
      if ( e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ' ) return;
      showView( 'grantha' );
    });
    $btnA.on( 'click keydown', function(e){
      if ( e.type === 'keydown' && e.key !== 'Enter' && e.key !== ' ' ) return;
      showView( 'author' );
    });
    /* Restore last-used tab */
    try {
      var saved = localStorage.getItem( 'grantha_home_tab' );
      if ( saved === 'author' ) showView( 'author' );
      else showView( 'grantha' );
    } catch(e) { showView( 'grantha' ); }
  } );


}() );
}() );