MediaWiki:Common.js: Difference between revisions

From New wiki
Jump to navigation Jump to search
Content deleted Content added
No edit summary
No edit summary
 
(30 intermediate revisions by the same user not shown)
Line 121: Line 121:
/* End of mw.loader.using callback */
/* End of mw.loader.using callback */
} );
} );

// CapSach — Mobile TOC overlay (all skins; phone widths)
// CapSach — Sticky TOC overlay (UNRESTRICTED: Works on iPad/Desktop/Mobile)
(function () {
(function () {
// Don’t run on very wide screens (tablet/desktop have native TOC)
if (window.matchMedia('(min-width: 768px)').matches) return;
// 1. REMOVED the "min-width: 768px" check. Now runs everywhere.


// Only on normal content pages
// Only run on pages where it makes sense (Articles/MainPage)
if (window.mw && mw.config && mw.config.get) {
if (window.mw && mw.config && mw.config.get) {
var isArticle = !!mw.config.get('wgIsArticle');
var isAllowed = mw.config.get('wgIsArticle') || mw.config.get('wgIsMainPage');
if (!isArticle) return;
if (!isAllowed) return;
}
}


Line 153: Line 154:
});
});


// Show only if there are enough headings to be useful (match core default)
// Show only if there are enough headings to be useful
// CHANGED: Lowered requirement to 1 heading so it always shows if there is any structure
if (items.length < 3) return;
if (items.length < 1) return;


// Create trigger button (bottom-left; avoids “Back to top” on bottom-right)
// Create trigger button (bottom-left; avoids “Back to top” on bottom-right)
Line 217: Line 219:
}
}


// Force button display immediately
btn.style.display = 'flex'; // reveal trigger now that we know we have headings
btn.style.display = 'flex';
btn.addEventListener('click', openOverlay);
btn.addEventListener('click', openOverlay);


Line 231: Line 234:


// Navigate and try to ensure mobile-collapsed sections are visible
// Navigate and try to ensure mobile-collapsed sections are visible
// Navigate and try to ensure mobile-collapsed sections are visible
list.addEventListener('click', function (e) {
list.addEventListener('click', function (e) {
var a = e.target.closest('a');
var a = e.target.closest('a');
Line 237: Line 241:


var targetId = a.getAttribute('href').slice(1);
var targetId = a.getAttribute('href').slice(1);

// === NEW LOGIC START: Scroll to Top for "Contents" ===
// If the user clicks the "Contents" header (id="mw-toc-heading"), scroll to top (0,0)
// === FIXED CODE ===
if (targetId === 'mw-toc-heading') {
closeOverlay();
// Delay scroll to let iOS Safari process the overflow change
setTimeout(function() {
try {
window.scrollTo({ top: 0, behavior: 'smooth' });
} catch (e) {
window.scrollTo(0, 0);
}
// Fallback for older iOS Safari
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
}, 100);
if (history.replaceState) {
history.replaceState(null, '', window.location.pathname + window.location.search);
}
return;
}
// === NEW LOGIC END ===

var target = document.getElementById(targetId);
var target = document.getElementById(targetId);
closeOverlay();
closeOverlay();
Line 268: Line 296:
});
});


// 2. REMOVED the "resize" event listener that was hiding the button.
// Re-hide on rotation/resize to tablet/desktop
// The button now persists on all screen sizes.
window.addEventListener('resize', function () {
if (window.matchMedia('(min-width: 768px)').matches) {
btn.style.display = 'none';
closeOverlay();
} else {
btn.style.display = 'flex';
}
}, { passive: true });
})();
})();


/* Script for Inline Expandable Template */
/* DO NOT ADD CODE BELOW THIS LINE */
$(function() {
$('.inline-expand-trigger').on('click', function() {
// 1. Toggle the content visibility
$(this).next('.inline-expand-content').toggle();

// 2. Toggle the arrow icon
const currentText = $(this).text();
$(this).text(
currentText.includes('▸') ? currentText.replace('▸', '◂') : currentText.replace('◂', '▸')
);
});
});

$(document).ready(function() {
// Check if the button already exists to prevent duplicates
if ($('#custom-email-btn').length === 0) {
// Create the email button element
var emailBtn = $('<a>', {
id: 'custom-email-btn',
href: 'mailto:services@axabrain.com',
// Simple accessible title
title: 'Contact AXA BRAIN Services'
});

// Add it to the body of the page
$('body').append(emailBtn);
}
});

/* Open AXA BRAIN AI Assistant when clicking the logo */
$(document).ready(function() {
$('.fullscreen-logo').css('cursor', 'pointer').click(function(e) {
e.preventDefault();
// Method 1: Click the AI Assistant floating icon
var $aiButton = $('img[src*="ai-icon.png"]').closest('div, button, a');
if ($aiButton.length > 0) {
$aiButton.trigger('click');
return;
}
// Method 2: Try the extension's trigger class
var $trigger = $('.ext-aiassistant-trigger, .ext-aiassistant');
if ($trigger.length > 0) {
$trigger.first().trigger('click');
return;
}

console.log("AXA BRAIN Assistant button not found on this page.");
});
});

Latest revision as of 16:55, 20 February 2026

/* Any JavaScript here will be loaded for all users on every page load. */
/**
 * Keep code in MediaWiki:Common.js to a minimum as it is unconditionally
 * loaded for all users on every wiki page. If possible create a gadget that is
 * enabled by default instead of adding it here (since gadgets are fully
 * optimized ResourceLoader modules with possibility to add dependencies etc.)
 *
 * Since Common.js isn't a gadget, there is no place to declare its
 * dependencies, so we have to lazy load them with mw.loader.using on demand and
 * then execute the rest in the callback. In most cases these dependencies will
 * be loaded (or loading) already and the callback will not be delayed. In case a
 * dependency hasn't arrived yet it'll make sure those are loaded before this.
 */

/* global mw, $ */
/* jshint strict:false, browser:true */

mw.loader.using( [ 'mediawiki.util' ] ).done( function () {
	/* Begin of mw.loader.using callback */

	/**
	 * Map addPortletLink to mw.util
	 * @deprecated: Use mw.util.addPortletLink instead.
	 */
	mw.log.deprecate( window, 'addPortletLink', mw.util.addPortletLink, 'Use mw.util.addPortletLink instead' );

	/**
	 * @source www.mediawiki.org/wiki/Snippets/Load_JS_and_CSS_by_URL
	 * @rev 6
	 */
	var extraCSS = mw.util.getParamValue( 'withCSS' ),
		extraJS = mw.util.getParamValue( 'withJS' );

	if ( extraCSS ) {
		if ( extraCSS.match( /^MediaWiki:[^&<>=%#]*\.css$/ ) ) {
			mw.loader.load( '/w/index.php?title=' + extraCSS + '&action=raw&ctype=text/css', 'text/css' );
		} else {
			mw.notify( 'Only pages from the MediaWiki namespace are allowed.', { title: 'Invalid withCSS value' } );
		}
	}

	if ( extraJS ) {
		if ( extraJS.match( /^MediaWiki:[^&<>=%#]*\.js$/ ) ) {
			mw.loader.load( '/w/index.php?title=' + extraJS + '&action=raw&ctype=text/javascript' );
		} else {
			mw.notify( 'Only pages from the MediaWiki namespace are allowed.', { title: 'Invalid withJS value' } );
		}
	}

	/**
	 * Collapsible tables; reimplemented with mw-collapsible
	 * Styling is also in place to avoid FOUC
	 *
	 * Allows tables to be collapsed, showing only the header. See [[Help:Collapsing]].
	 * @version 3.0.0 (2018-05-20)
	 * @source https://www.mediawiki.org/wiki/MediaWiki:Gadget-collapsibleTables.js
	 * @author [[User:R. Koot]]
	 * @author [[User:Krinkle]]
	 * @author [[User:TheDJ]]
	 * @deprecated Since MediaWiki 1.20: Use class="mw-collapsible" instead which
	 * is supported in MediaWiki core. Shimmable since MediaWiki 1.32
	 *
	 * @param {jQuery} $content
	 */
	function makeCollapsibleMwCollapsible( $content ) {
		var $tables = $content
			.find( 'table.collapsible:not(.mw-collapsible)' )
			.addClass( 'mw-collapsible' );

		$.each( $tables, function ( index, table ) {
			// mw.log.warn( 'This page is using the deprecated class collapsible. Please replace it with mw-collapsible.');
			if ( $( table ).hasClass( 'collapsed' ) ) {
				$( table ).addClass( 'mw-collapsed' );
				// mw.log.warn( 'This page is using the deprecated class collapsed. Please replace it with mw-collapsed.');
			}
		} );
		if ( $tables.length > 0 ) {
			mw.loader.using( 'jquery.makeCollapsible' ).then( function () {
				$tables.makeCollapsible();
			} );
		}
	}
	mw.hook( 'wikipage.content' ).add( makeCollapsibleMwCollapsible );

	/**
	 * Add support to mw-collapsible for autocollapse, innercollapse and outercollapse
	 *
	 * Maintainers: TheDJ
	 */
	function mwCollapsibleSetup( $collapsibleContent ) {
		var $element,
			$toggle,
			autoCollapseThreshold = 2;
		$.each( $collapsibleContent, function ( index, element ) {
			$element = $( element );
			if ( $element.hasClass( 'collapsible' ) ) {
				$element.find( 'tr:first > th:first' ).prepend( $element.find( 'tr:first > * > .mw-collapsible-toggle' ) );
			}
			if ( $collapsibleContent.length >= autoCollapseThreshold && $element.hasClass( 'autocollapse' ) ) {
				$element.data( 'mw-collapsible' ).collapse();
			} else if ( $element.hasClass( 'innercollapse' ) ) {
				if ( $element.parents( '.outercollapse' ).length > 0 ) {
					$element.data( 'mw-collapsible' ).collapse();
				}
			}
			// because of colored backgrounds, style the link in the text color
			// to ensure accessible contrast
			$toggle = $element.find( '.mw-collapsible-toggle' );
			if ( $toggle.length ) {
				// Make the toggle inherit text color (Updated for T333357 2023-04-29)
				if ( $toggle.parent()[ 0 ].style.color ) {
					$toggle.css( 'color', 'inherit' );
					$toggle.find( '.mw-collapsible-text' ).css( 'color', 'inherit' );
				}
			}
		} );
	}

	mw.hook( 'wikipage.collapsibleContent' ).add( mwCollapsibleSetup );

	/* End of mw.loader.using callback */
} );

// CapSach — Sticky TOC overlay (UNRESTRICTED: Works on iPad/Desktop/Mobile)
(function () {
  
  // 1. REMOVED the "min-width: 768px" check. Now runs everywhere.

  // Only run on pages where it makes sense (Articles/MainPage)
  if (window.mw && mw.config && mw.config.get) {
    var isAllowed = mw.config.get('wgIsArticle') || mw.config.get('wgIsMainPage');
    if (!isAllowed) return;
  }

  // Find the content root; MobileFrontend restructures DOM, so be flexible
  var root =
    document.querySelector('#mw-content-text .mw-parser-output') ||
    document.querySelector('.mw-parser-output') ||
    document.getElementById('mw-content-text') ||
    document.querySelector('#content') ||
    document.body;

  // Collect headings (H2–H6). Prefer spans with .mw-headline (stable anchor ids)
  var items = [];
  var headings = root.querySelectorAll('h2, h3, h4, h5, h6');
  headings.forEach(function (h) {
    var level = parseInt(h.tagName.slice(1), 10);
    if (level < 2 || level > 6) return;
    var headline = h.querySelector('.mw-headline') || h;
    var id = headline.id || h.id;
    var text = (headline.textContent || h.textContent || '').trim();
    if (!id || !text) return;
    items.push({ id: id, text: text, level: level });
  });

  // Show only if there are enough headings to be useful
  // CHANGED: Lowered requirement to 1 heading so it always shows if there is any structure
  if (items.length < 1) return;

  // Create trigger button (bottom-left; avoids “Back to top” on bottom-right)
  var btn = document.createElement('button');
  btn.id = 'cps-open-toc';
  btn.type = 'button';
  btn.setAttribute('aria-label', 'Open table of contents');
  btn.innerHTML = '<span class="icon" aria-hidden="true">≡</span><span class="label">TOC</span>';
  document.body.appendChild(btn);

  // Overlay + panel
  var overlay = document.createElement('div');
  overlay.id = 'cps-toc-overlay';
  overlay.setAttribute('aria-hidden', 'true');

  var panel = document.createElement('div');
  panel.id = 'cps-toc-panel';
  panel.setAttribute('role', 'dialog');
  panel.setAttribute('aria-modal', 'true');
  panel.setAttribute('aria-label', 'Table of contents');

  var header = document.createElement('div');
  header.id = 'cps-toc-header';
  header.innerHTML =
    '<h2 id="cps-toc-title">Contents</h2>' +
    '<button id="cps-toc-close" type="button" aria-label="Close">×</button>';

  var list = document.createElement('ul');
  list.id = 'cps-toc-list';

  items.forEach(function (it) {
    var li = document.createElement('li');
    li.setAttribute('data-level', String(it.level));
    var a = document.createElement('a');
    a.href = '#' + it.id;
    a.textContent = it.text;
    li.appendChild(a);
    list.appendChild(li);
  });

  panel.appendChild(header);
  panel.appendChild(list);
  overlay.appendChild(panel);
  document.body.appendChild(overlay);

  // Focus handling
  var lastFocus = null;
  function openOverlay() {
    lastFocus = document.activeElement;
    overlay.classList.add('is-open');
    overlay.setAttribute('aria-hidden', 'false');
    document.body.style.overflow = 'hidden';
    // Focus first link for accessibility
    var firstLink = list.querySelector('a');
    if (firstLink) firstLink.focus({ preventScroll: true });
  }
  function closeOverlay() {
    overlay.classList.remove('is-open');
    overlay.setAttribute('aria-hidden', 'true');
    document.body.style.overflow = '';
    if (lastFocus && lastFocus.focus) lastFocus.focus({ preventScroll: true });
  }

  // Force button display immediately
  btn.style.display = 'flex'; 
  btn.addEventListener('click', openOverlay);

  overlay.addEventListener('click', function (e) {
    // Click outside the bottom sheet closes
    if (e.target === overlay) closeOverlay();
  });
  overlay.querySelector('#cps-toc-close').addEventListener('click', closeOverlay);

  overlay.addEventListener('keydown', function (e) {
    if (e.key === 'Escape') closeOverlay();
  });

  // Navigate and try to ensure mobile-collapsed sections are visible
// Navigate and try to ensure mobile-collapsed sections are visible
  list.addEventListener('click', function (e) {
    var a = e.target.closest('a');
    if (!a) return;
    e.preventDefault();

    var targetId = a.getAttribute('href').slice(1);

    // === NEW LOGIC START: Scroll to Top for "Contents" ===
    // If the user clicks the "Contents" header (id="mw-toc-heading"), scroll to top (0,0)
// === FIXED CODE ===
if (targetId === 'mw-toc-heading') {
   closeOverlay();
   // Delay scroll to let iOS Safari process the overflow change
   setTimeout(function() {
       try {
           window.scrollTo({ top: 0, behavior: 'smooth' });
       } catch (e) {
           window.scrollTo(0, 0);
       }
       // Fallback for older iOS Safari
       document.documentElement.scrollTop = 0;
       document.body.scrollTop = 0;
   }, 100);
   if (history.replaceState) {
       history.replaceState(null, '', window.location.pathname + window.location.search);
   }
   return;
}
    // === NEW LOGIC END ===

    var target = document.getElementById(targetId);
    closeOverlay();

    if (target) {
      try {
        target.scrollIntoView({ behavior: 'smooth', block: 'start' });
      } catch (_) {
        target.scrollIntoView(true);
      }

      // Update URL hash after a tick (so browser back works)
      setTimeout(function () {
        if (history && history.replaceState) {
          history.replaceState(null, '', '#' + targetId);
        } else {
          location.hash = targetId;
        }
      }, 200);

      // MobileFrontend: headings may be inside collapsed sections.
      // Heuristic: click the nearest toggle if present.
      var maybeToggle = target.closest('.collapsible-block, .mf-section') ||
                        target.closest('section');
      if (maybeToggle && maybeToggle.classList.contains('collapsed')) {
        // Try to open; fallback by clicking the first heading inside
        var headingToggle = maybeToggle.querySelector('.section-heading, h2, h3, h4, h5, h6');
        if (headingToggle) headingToggle.click();
      }
    }
  });

  // 2. REMOVED the "resize" event listener that was hiding the button.
  // The button now persists on all screen sizes.
})();

/* Script for Inline Expandable Template */
$(function() {
    $('.inline-expand-trigger').on('click', function() {
        // 1. Toggle the content visibility
        $(this).next('.inline-expand-content').toggle();

        // 2. Toggle the arrow icon
        const currentText = $(this).text();
        $(this).text(
            currentText.includes('▸') ? currentText.replace('▸', '◂') : currentText.replace('◂', '▸')
        );
    });
});

$(document).ready(function() {
    // Check if the button already exists to prevent duplicates
    if ($('#custom-email-btn').length === 0) {
        
        // Create the email button element
        var emailBtn = $('<a>', {
            id: 'custom-email-btn',
            href: 'mailto:services@axabrain.com',
            // Simple accessible title
            title: 'Contact AXA BRAIN Services'
        });

        // Add it to the body of the page
        $('body').append(emailBtn);
    }
});

/* Open AXA BRAIN AI Assistant when clicking the logo */
$(document).ready(function() {
    $('.fullscreen-logo').css('cursor', 'pointer').click(function(e) {
        e.preventDefault();
        
        // Method 1: Click the AI Assistant floating icon
        var $aiButton = $('img[src*="ai-icon.png"]').closest('div, button, a');
        if ($aiButton.length > 0) {
            $aiButton.trigger('click');
            return;
        }
        
        // Method 2: Try the extension's trigger class
        var $trigger = $('.ext-aiassistant-trigger, .ext-aiassistant');
        if ($trigger.length > 0) {
            $trigger.first().trigger('click');
            return;
        }

        console.log("AXA BRAIN Assistant button not found on this page.");
    });
});