MediaWiki:Common.js: Difference between revisions

No edit summary
Tags: Reverted Mobile edit Mobile web edit
No edit summary
Tag: Manual revert
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 fixes — paste at bottom of MediaWiki:Common.js
  Injects a <style> tag AFTER readerToolbar's inline style
  so it wins the cascade without touching the extension.
  ═══════════════════════════════════════════════════════════════ */
( function () {
  if ( window.innerWidth > 768 ) return;
  /* ── 1. Inject mobile CSS after readerToolbar's <style> ─────── */
  function injectMobileCSS() {
    if ( document.getElementById( 'gr-mobile-css' ) ) return;
    var s = document.createElement( 'style' );
    s.id = 'gr-mobile-css';
    s.textContent = [
      /* Font scale */
      'html { font-size: 18px !important; }',
      /* Full-width layout — kill x-scroll everywhere */
      'html body, html body .mw-page-container, html body .vector-page-container,',
      'html body .mw-content-container, html body .vector-body-container,',
      'html body .vector-page-content, html body .mw-body,',
      'html body .mw-body-content, html body #mw-content-text {',
      '  max-width: 100% !important; width: 100% !important;',
      '  overflow-x: hidden !important; box-sizing: border-box !important;',
      '  margin: 0 !important; padding: 0 !important; }',
      /* Content padding */
      'html body #mw-content-text { padding: 8px 14px 48px !important; }',
      /* Content font */
      'html body .mw-parser-output {',
      '  font-size: 18px !important; line-height: 1.8 !important;',
      '  max-width: 100% !important; overflow-x: hidden !important; }',
      /* Headings full width */
      'html body .mw-parser-output h2, html body .mw-parser-output .mw-heading2 h2 {',
      '  font-size: 1.1em !important; width: 100% !important; margin-top: 1.2em !important; }',
      'html body .mw-parser-output h3, html body .mw-parser-output .mw-heading3 h3 {',
      '  font-size: 1em !important; width: 100% !important; }',
      /* Hide Vector sidebar TOC — replaced by overlay */
      'html body .vector-column-start, html body #vector-toc,',
      'html body .vector-toc-landmark, html body .mw-table-of-contents-container,',
      'html body .vector-sticky-pinned-container,',
      'html body .vector-pinnable-element.vector-toc-pinnable-element {',
      '  display: none !important; }',
      /* Home grid — single column, no x-scroll */
      'html body .gr-home { overflow-x: hidden !important; max-width: 100% !important; }',
      'html body .gr-home-grid {',
      '  flex-direction: column !important; flex-wrap: nowrap !important;',
      '  gap: 14px !important; overflow-x: hidden !important; width: 100% !important; }',
      'html body .gr-home-card {',
      '  flex: 0 0 auto !important; width: 100% !important;',
      '  max-width: 100% !important; min-width: unset !important;',
      '  box-sizing: border-box !important; }',
      /* Toolbar */
      'html body #gr-static-bar {',
      '  height: auto !important; min-height: 52px !important;',
      '  padding: 6px 8px !important; flex-wrap: nowrap !important;',
      '  overflow-x: auto !important; overflow-y: hidden !important;',
      '  scrollbar-width: none !important; gap: 4px !important; }',
      'html body #gr-static-bar::-webkit-scrollbar { display: none !important; }',
      'html body .gr-controls .gr-btn {',
      '  height: 44px !important; min-width: 44px !important;',
      '  font-size: 15px !important; padding: 0 10px !important; flex-shrink: 0 !important; }',
      'html body .gr-controls .gr-icon-btn { width: 44px !important; padding: 0 !important; }',
      'html body .gr-script-sel {',
      '  height: 40px !important; font-size: 14px !important;',
      '  min-width: 90px !important; max-width: 120px !important; }',
      'html body .gr-controls .gr-sep { display: none !important; }',
      'html body .gr-btn-staging { display: none !important; }',
      /* TOC overlay styles */
      '.gr-mob-toc-btn {',
      '  display: flex !important; position: fixed !important;',
      '  bottom: 148px !important; left: 16px !important; z-index: 9100 !important;',
      '  background: #fff !important; border: 1.5px solid #b5451b !important;',
      '  border-radius: 24px !important; padding: 10px 16px !important;',
      '  font-size: 15px !important; font-family: system-ui, sans-serif !important;',
      '  color: #b5451b !important; font-weight: 600 !important;',
      '  box-shadow: 0 3px 14px rgba(0,0,0,0.15) !important;',
      '  cursor: pointer !important; align-items: center !important;',
      '  gap: 6px !important; white-space: nowrap !important; }',
      '.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; line-height: 1 !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;',
      "  font-family: 'Adishila','Noto Serif Devanagari',system-ui,sans-serif !important; }",
      '.gr-mob-toc-body .vector-toc-level-2 a {',
      '  padding-left: 16px !important; font-size: 15px !important; color: #555 !important; }',
      /* Annotation toggle button position */
      'html body #gra-toggle {',
      '  bottom: 80px !important; right: 14px !important;',
      '  width: 54px !important; height: 54px !important; }',
    ].join( '\n' );
    document.head.appendChild( s );
  }
  /* ── 2. Mobile TOC overlay ────────────────────────────────────── */
  function initMobileToc() {
    if ( document.getElementById( 'gr-mob-toc-btn' ) ) return;
    var tocList = document.querySelector(
      '.vector-toc-contents, .vector-toc .vector-toc-list'
    );
    if ( !tocList ) { setTimeout( initMobileToc, 500 ); return; }
    /* Check if page actually has TOC items */
    var hasItems = tocList.querySelector( '.vector-toc-list-item' );
    /* Backdrop */
    var bd = document.createElement( 'div' );
    bd.className = 'gr-mob-toc-backdrop';
    document.body.appendChild( bd );
    /* Panel */
    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 );
    /* Toggle button — only show if page has TOC */
    var btn = document.createElement( 'button' );
    btn.className = 'gr-mob-toc-btn';
    btn.id = 'gr-mob-toc-btn';
    btn.innerHTML = '☰ &nbsp;Contents';
    if ( !hasItems ) btn.style.display = 'none';
    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.addEventListener( 'click', open );
    cls.addEventListener( 'click', close );
    bd.addEventListener( 'click', close );
    body.querySelectorAll( 'a' ).forEach( function ( a ) {
      a.addEventListener( 'click', close );
    } );
  }
  /* ── 3. Mobile hamburger menu ─────────────────────────────────── */
  function initMobileMenu() {
    if ( document.getElementById( 'gr-mob-menu-btn' ) ) return;
    var headerStart = document.querySelector( '.vector-header-start' );
    if ( !headerStart ) return;
    /* Hamburger button — prepend to header start */
    var btn = document.createElement( 'button' );
    btn.id = 'gr-mob-menu-btn';
    btn.className = 'gr-mob-menu-btn';
    btn.setAttribute( 'aria-label', 'Open menu' );
    btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>';
    headerStart.insertBefore( btn, headerStart.firstChild );
    /* Backdrop */
    var bd = document.createElement( 'div' );
    bd.className = 'gr-mob-menu-backdrop';
    document.body.appendChild( bd );
    /* Panel */
    var panel = document.createElement( 'div' );
    panel.className = 'gr-mob-menu-panel';
    /* Header */
    var hdr = document.createElement( 'div' );
    hdr.className = 'gr-mob-menu-header';
    var ttl = document.createElement( 'div' );
    ttl.className = 'gr-mob-menu-header-title';
    ttl.textContent = 'Anandamakaranda';
    var cls = document.createElement( 'button' );
    cls.className = 'gr-mob-menu-close';
    cls.textContent = '✕';
    hdr.appendChild( ttl ); hdr.appendChild( cls );
    panel.appendChild( hdr );
    /* Body — collect links from vector-header-end */
    var body = document.createElement( 'div' );
    body.className = 'gr-mob-menu-body';
    /* Username / user page */
    var userName = window.mw ? mw.config.get( 'wgUserName' ) : null;
    if ( userName ) {
      var userLink = document.createElement( 'a' );
      userLink.className = 'gr-mob-menu-item gr-mob-menu-item-user';
      userLink.href = '/User:' + encodeURIComponent( userName );
      userLink.textContent = '👤 ' + userName;
      body.appendChild( userLink );
      var div0 = document.createElement( 'div' );
      div0.className = 'gr-mob-menu-divider';
      body.appendChild( div0 );
    }
    /* Collect links from header-end */
    var headerEnd = document.querySelector( '.vector-header-end' );
    if ( headerEnd ) {
      var links = headerEnd.querySelectorAll( 'a' );
      links.forEach( function ( a ) {
        var item = document.createElement( 'a' );
        item.className = 'gr-mob-menu-item';
        item.href = a.href;
        item.textContent = a.textContent.trim();
        if ( item.textContent ) body.appendChild( item );
      } );
    }
    /* Static links */
    var statics = [
      { label: 'Help', href: '/Help:Contents' },
      { label: 'About', href: '/Anandamakaranda:About' },
    ];
    var div1 = document.createElement( 'div' );
    div1.className = 'gr-mob-menu-divider';
    body.appendChild( div1 );
    /* Login / logout */
    var isLoggedIn = userName !== null;
    var authItem = document.createElement( 'a' );
    authItem.className = 'gr-mob-menu-item';
    if ( isLoggedIn ) {
      var logoutLink = document.querySelector( '#pt-logout a, a[href*="action=logout"]' );
      authItem.href = logoutLink ? logoutLink.href : '/index.php?title=Special:UserLogout';
      authItem.textContent = 'Log out';
    } else {
      authItem.href = '/index.php?title=Special:UserLogin';
      authItem.textContent = 'Log in';
    }
    body.appendChild( authItem );
    panel.appendChild( body );
    document.body.appendChild( panel );
    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.addEventListener( 'click', open );
    cls.addEventListener( 'click', close );
    bd.addEventListener( 'click', close );
    body.querySelectorAll( 'a' ).forEach( function(a){ a.addEventListener( 'click', close ); } );
  }
  /* ── Boot ─────────────────────────────────────────────────────── */
  injectMobileCSS();  /* CSS first — synchronous */
  /* ── Fix Minerva header: remove top gap, ensure orange, show logo ── */
  function fixMinervaHeader() {
    if ( !document.body.classList.contains( 'skin-minerva' ) ) return;
    /* Remove any top margin/padding on body — readerToolbar may have set it */
    document.body.style.paddingTop = '';
    document.body.style.marginTop = '0';
    /* Also watch for readerToolbar setting paddingTop after us */
    var _bodyObserver = new MutationObserver( function() {
      if ( document.body.style.paddingTop && document.body.style.paddingTop !== '0px' ) {
        document.body.style.paddingTop = '';
      }
    } );
    _bodyObserver.observe( document.body, { attributes: true, attributeFilter: ['style'] } );
    /* Force orange header */
    var headerEl = document.querySelector( 'header.header-container' );
    if ( headerEl ) headerEl.style.background = '#b5451b';
    /* Add quill logo before site name */
    var brandingLink = document.querySelector( '.branding-box a' );
    if ( brandingLink && !brandingLink.querySelector( '.gr-mob-logo' ) ) {
      var quillSvg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width="28" height="28" fill="none" style="margin-right:8px;flex-shrink:0;">' +
        '<path stroke="#fff" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" d="M32 4C26 4 14 10 8 28L6 34l6-1c4-1 8-4 11-8"/>' +
        '<path stroke="#fff" stroke-width="1.8" stroke-linecap="round" d="M32 4C28 12 22 20 12 26"/>' +
        '<path stroke="#fff" stroke-width="1.8" stroke-linecap="round" d="M8 28c2-2 4-3 6-2s3 3 2 5"/>' +
        '<path stroke="#fff" stroke-width="1.5" stroke-linecap="round" d="M12 26L6 34" opacity="0.6"/></svg>';
      var logoEl = document.createElement( 'span' );
      logoEl.className = 'gr-mob-logo';
      logoEl.innerHTML = quillSvg;
      brandingLink.style.cssText = 'display:flex;align-items:center;text-decoration:none;';
      brandingLink.insertBefore( logoEl, brandingLink.firstChild );
      /* Style the text span */
      var textSpan = brandingLink.querySelector( 'span:not(.gr-mob-logo)' );
      if ( textSpan ) {
        textSpan.style.cssText = 'color:#fff;font-size:17px;font-weight:700;font-family:system-ui,sans-serif;line-height:1.2;';
      }
    }
  }
  if ( document.readyState === 'loading' ) {
    document.addEventListener( 'DOMContentLoaded', fixMinervaHeader );
  } else {
    fixMinervaHeader();
  }
  initMobileMenu();
  if ( document.readyState === 'loading' ) {
    document.addEventListener( 'DOMContentLoaded', function () {
      setTimeout( initMobileToc, 600 );
    } );
  } else {
    setTimeout( initMobileToc, 600 );
  }
  if ( window.mw ) {
    mw.hook( 'wikipage.content' ).add( function () {
      setTimeout( initMobileToc, 600 );
      setTimeout( function() { expandAllSections(); watchSections(); }, 100 );
      setTimeout( expandAllSections, 600 );
    } );
  }
  /* ── Force expand all Minerva collapsed sections ──────────────
  * MobileFrontend JS runs AFTER our code and re-collapses sections.
  * We use a MutationObserver to immediately re-expand anything
  * that gets hidden, plus run on load and on MW hooks.
  * ─────────────────────────────────────────────────────────── */
  function expandAllSections() {
    if ( !document.body.classList.contains( 'skin-minerva' ) ) return;
    /* THE KEY FIX: MobileFrontend uses hidden="until-found" attribute
    * which CSS display:block cannot override. Must remove the attribute. */
    document.querySelectorAll( '[hidden]' ).forEach( function(el) {
      var h = el.getAttribute( 'hidden' );
      if ( h === 'until-found' || h === '' || h === 'hidden' ) {
        if ( el.closest( '[class*="mf-section-"], .collapsible-block' )
          || el.classList.contains( 'collapsible-block' )
          || /mf-section-/.test( el.className ) ) {
          el.removeAttribute( 'hidden' );
        }
      }
    } );
    /* Also remove hidden from all mf-section and collapsible-block directly */
    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.style.setProperty( 'content-visibility', 'visible', 'important' );
      el.removeAttribute( 'aria-hidden' );
    } );
    /* Mark headings as open, disable pointer events to prevent re-collapse */
    document.querySelectorAll( '.section-heading, .collapsible-heading' ).forEach( function(el) {
      el.setAttribute( 'aria-expanded', 'true' );
      el.style.setProperty( 'pointer-events', 'none', 'important' );
    } );
    /* Hide collapse indicators */
    document.querySelectorAll(
      '.section-heading .indicator, .collapsible-heading .indicator'
    ).forEach( function(el) {
      el.style.setProperty( 'display', 'none', 'important' );
    } );
  }
  /* Persistent observer — fight back when MF re-collapses */
  var _sectionObserver = null;
  function watchSections() {
    if ( !document.body.classList.contains( 'skin-minerva' ) ) return;
    if ( _sectionObserver ) return;
    var _timer = null;
    _sectionObserver = new MutationObserver( function( mutations ) {
      var needExpand = mutations.some( function(m) {
        /* Watch for hidden attribute being added or style=display:none */
        if ( m.type === 'attributes' ) {
          if ( m.attributeName === 'hidden' ) return true;
          if ( m.attributeName === 'style' && m.target.style.display === 'none' ) return true;
        }
        return false;
      } );
      if ( needExpand ) {
        clearTimeout( _timer );
        _timer = setTimeout( expandAllSections, 30 );
      }
    } );
    /* Observe the content area for attribute changes including hidden */
    var contentEl = document.querySelector( '#mw-content-text, #mw-mf-page-center' );
    if ( contentEl ) {
      _sectionObserver.observe( contentEl, {
        subtree: true, attributes: true,
        attributeFilter: ['style', 'class', 'hidden', 'aria-hidden']
      } );
    }
  }
  /* Run on load */
  if ( document.readyState === 'loading' ) {
    document.addEventListener( 'DOMContentLoaded', function() {
      setTimeout( function() { expandAllSections(); watchSections(); }, 100 );
      setTimeout( expandAllSections, 500 );
      setTimeout( expandAllSections, 1200 );
    } );
  } else {
    setTimeout( function() { expandAllSections(); watchSections(); }, 100 );
    setTimeout( expandAllSections, 500 );
    setTimeout( expandAllSections, 1200 );
  }


}() );
}() );