MediaWiki:Gadget-ShowIDs.js
Jump to navigation
Jump to search
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
// <nowiki>
/* jshint maxerr:1048576, strict:true, undef:true, latedef:true, esversion:6 */
/* global $, mw */
/**
* Display sense and etym IDs.
*
* Note: this gadget also loads ShowIDs-pagestyles, which offers a
* CSS-only fallback to this gadget that does not require JavaScript.
* The CSS is nevertheless required for this JavaScript gadget
* to display correctly.
* CSS: [[MediaWiki:Gadget-ShowIDs-pagestyles.css]]
*
* Author(s): Surjection, Fenakhay
* Last updated: 2025-09-22
*/
$(document).ready(() => {
'use strict';
const ID_CLASSES = [
{ name: 'senseid', showTooltip: true },
{ name: 'etymid', showTooltip: true },
{ name: 'etymonid', showTooltip: false }
];
const WIKIDATA_PATTERN = /^Q\d+$/;
/**
* Generate CSS selectors for all ID classes
*/
function generateSelectors(suffix = '') {
return ID_CLASSES.map(cls => `.${cls.name}${suffix}`).join(', ');
}
/**
* Returns the text content of element, excluding children (unlike .text()).
*/
function getTextOnlyRoot($elem) {
return $elem.clone().children().remove().end().text();
}
/**
* Move element (sole child span) into the next visible, non-floated paragraph.
*/
function moveIntoNextParagraph($elem) {
const $parentP = $elem.parent('p');
// Only proceed if parent is a p with single child and no text content
if (!$parentP.length ||
$parentP.children().length !== 1 ||
getTextOnlyRoot($parentP).trim()) {
return;
}
let $next = $parentP.next();
while ($next.length) {
// Skip floated or hidden elements
if ($next.css('float') !== 'none' || !$next.is(':visible')) {
$next = $next.next();
continue;
}
// Move content to next paragraph if found
if ($next.is('p')) {
$next.prepend('\n');
$parentP.children().prependTo($next);
$parentP.remove();
}
break;
}
}
/**
* Create display element for ID (link for Wikidata, span for others).
*/
function createDisplayElement(idStr, tooltip) {
const isWikidata = WIKIDATA_PATTERN.test(idStr);
return isWikidata
? $('<a/>', {
href: `https://www.wikidata.org/wiki/${idStr}`,
text: idStr,
title: tooltip || undefined,
target: '_blank',
rel: 'noopener noreferrer'
})
: $('<span/>', {
text: idStr,
title: tooltip || undefined
});
}
/**
* Generate tooltip text for template.
*/
function generateTooltip(classConfig, lang, idStr) {
return (classConfig.showTooltip && lang && idStr)
? `{{${classConfig.name}|${lang}|${idStr}}}`
: '';
}
// Process each ID element
$(generateSelectors()).each(function () {
const $elem = $(this);
// Find the matching class configuration
const classConfig = ID_CLASSES.find(cls => $elem.hasClass(cls.name));
if (!classConfig) return;
const displayedClass = `${classConfig.name}-displayed`;
// Skip if already processed
if ($elem.hasClass(displayedClass)) return;
// Extract data
const lang = $elem.attr('data-lang');
const rawId = $elem.data('id');
const idStr = String(rawId);
const tooltip = generateTooltip(classConfig, lang, idStr);
const hasText = !!$elem.text().trim();
// Create display element
const $content = createDisplayElement(idStr, tooltip);
if ($elem.is('li') || hasText) {
// Wrap in new span to preserve existing content
const $wrapper = $('<span/>', { class: displayedClass }).append($content);
// Add space if needed
if (!hasText || !$elem.text().match(/^\s/)) {
$elem.prepend(' ');
}
$elem.prepend($wrapper);
} else {
// Replace element content entirely
$elem.addClass(displayedClass).empty().append($content);
moveIntoNextParagraph($elem);
}
});
// Prevent CSS-only pseudo-elements from duplicating the display
mw.util.addCSS(
`${generateSelectors('::before')} { content: none !important; display: none !important; }`
);
});
// Ensure CSS fallback is loaded
mw.loader.load('ext.gadget.ShowIDs-pagestyles');
// </nowiki>