|
Tags: Blanking Manual revert |
| Line 1: |
Line 1: |
| /**
| |
| * Custom Item Tooltips — replaces Extension:Popups for .item-link-wrapper links
| |
| */
| |
| ( function () {
| |
| 'use strict';
| |
|
| |
|
| var tooltipCache = {};
| |
| var $tooltip = null;
| |
| var currentTarget = null;
| |
| var showTimer = null;
| |
| var hideTimer = null;
| |
|
| |
| var SHOW_DELAY = 250;
| |
| var HIDE_DELAY = 300;
| |
| var EDGE_MARGIN = 10; // margin from viewport edges
| |
|
| |
| /* ─── Create tooltip DOM element ─── */
| |
| function getTooltip() {
| |
| if ( !$tooltip ) {
| |
| $tooltip = $( '<div>' )
| |
| .addClass( 'custom-item-tooltip' )
| |
| .hide()
| |
| .appendTo( document.body )
| |
| .on( 'mouseenter', function () {
| |
| clearTimeout( hideTimer );
| |
| } )
| |
| .on( 'mouseleave', function () {
| |
| scheduleHide();
| |
| } );
| |
| }
| |
| return $tooltip;
| |
| }
| |
|
| |
| /* ─── Positioning ─── */
| |
| function positionTooltip( anchor ) {
| |
| var tt = getTooltip();
| |
| var rect = anchor.getBoundingClientRect();
| |
| var scrollTop = window.scrollY || document.documentElement.scrollTop;
| |
| var scrollLeft = window.scrollX || document.documentElement.scrollLeft;
| |
| var vpW = window.innerWidth;
| |
| var vpH = window.innerHeight;
| |
|
| |
| // Render offscreen to measure actual dimensions
| |
| tt.css( { top: -9999, left: -9999, visibility: 'hidden' } ).show();
| |
| var ttW = tt.outerWidth();
| |
| var ttH = tt.outerHeight();
| |
| tt.css( 'visibility', '' );
| |
|
| |
| // ─── Vertical positioning ───
| |
| var top;
| |
| var spaceBelow = vpH - rect.bottom - 8;
| |
| var spaceAbove = rect.top - 8;
| |
|
| |
| if ( spaceBelow >= ttH + EDGE_MARGIN ) {
| |
| // Fits below the anchor
| |
| top = rect.bottom + scrollTop + 8;
| |
| } else if ( spaceAbove >= ttH + EDGE_MARGIN ) {
| |
| // Fits above the anchor
| |
| top = rect.top + scrollTop - ttH - 8;
| |
| } else {
| |
| // Doesn't fit above or below — pin to the edge with more space
| |
| if ( spaceBelow >= spaceAbove ) {
| |
| top = scrollTop + vpH - ttH - EDGE_MARGIN;
| |
| } else {
| |
| top = scrollTop + EDGE_MARGIN;
| |
| }
| |
| }
| |
|
| |
| // ─── Horizontal positioning ───
| |
| var left = rect.left + scrollLeft;
| |
|
| |
| // Overflows right edge
| |
| if ( left + ttW > scrollLeft + vpW - EDGE_MARGIN ) {
| |
| left = scrollLeft + vpW - ttW - EDGE_MARGIN;
| |
| }
| |
|
| |
| // Overflows left edge
| |
| if ( left < scrollLeft + EDGE_MARGIN ) {
| |
| left = scrollLeft + EDGE_MARGIN;
| |
| }
| |
|
| |
| tt.css( { top: top, left: left } );
| |
| }
| |
|
| |
| /* ─── Show tooltip ─── */
| |
| function showTooltip( anchor, itemName ) {
| |
| clearTimeout( hideTimer );
| |
| clearTimeout( showTimer );
| |
|
| |
| showTimer = setTimeout( function () {
| |
| var tt = getTooltip();
| |
| currentTarget = anchor;
| |
|
| |
| // Already cached — show immediately
| |
| if ( tooltipCache[ itemName ] ) {
| |
| tt.html( tooltipCache[ itemName ] ).show();
| |
| positionTooltip( anchor );
| |
| return;
| |
| }
| |
|
| |
| // Loading indicator
| |
| tt.html( '<div class="custom-item-tooltip__loading">Loading…</div>' ).show();
| |
| positionTooltip( anchor );
| |
|
| |
| // Fetch rendered template
| |
| new mw.Api().post( {
| |
| action: 'parse',
| |
| text: '{{Infobox item/auto|' + itemName + '}}',
| |
| title: mw.config.get( 'wgPageName' ),
| |
| prop: 'text',
| |
| disablelimitreport: true,
| |
| disableeditsection: true,
| |
| wrapoutputclass: '',
| |
| format: 'json'
| |
| } ).then( function ( data ) {
| |
| var html = data.parse.text[ '*' ];
| |
| tooltipCache[ itemName ] = html;
| |
|
| |
| // Only show if the mouse is still on this element
| |
| if ( currentTarget === anchor ) {
| |
| tt.html( html ).show();
| |
| positionTooltip( anchor );
| |
| }
| |
| } ).catch( function () {
| |
| if ( currentTarget === anchor ) {
| |
| tt.hide();
| |
| }
| |
| } );
| |
|
| |
| }, SHOW_DELAY );
| |
| }
| |
|
| |
| /* ─── Hide tooltip ─── */
| |
| function scheduleHide() {
| |
| clearTimeout( showTimer );
| |
| hideTimer = setTimeout( function () {
| |
| if ( $tooltip ) {
| |
| $tooltip.hide();
| |
| }
| |
| currentTarget = null;
| |
| }, HIDE_DELAY );
| |
| }
| |
|
| |
| /* ─── Initialization ─── */
| |
| mw.hook( 'wikipage.content' ).add( function ( $content ) {
| |
| $content.find( '.item-link-wrapper' ).each( function () {
| |
| var $wrapper = $( this );
| |
| var itemName = $wrapper.attr( 'data-item-name' );
| |
| if ( !itemName ) return;
| |
|
| |
| // All links inside the wrapper, not just the first one
| |
| var $links = $wrapper.find( 'a[href]' );
| |
| if ( !$links.length ) return;
| |
|
| |
| $links.each( function () {
| |
| var $link = $( this );
| |
|
| |
| // Remove native title from each link
| |
| $link.removeAttr( 'title' );
| |
|
| |
| $link.on( 'mouseenter.itemTooltip', function () {
| |
| showTooltip( this, itemName );
| |
| } ).on( 'mouseleave.itemTooltip', function () {
| |
| scheduleHide();
| |
| } );
| |
| } );
| |
| } );
| |
| } );
| |
|
| |
| } )();
| |