User:Monster Domosed/common.js: Difference between revisionsGive feedback
Blanked the page Tag: Blanking |
testing |
||
| 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|default=true|' + itemName + '}}', | |||
title: itemName, | |||
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(); | |||
} ); | |||
} ); | |||
} ); | |||
} ); | |||
} )(); | |||