MediaWiki:Common.js
From No Way Out Wiki
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.
// ImageMap Highlighter - only load on pages that have the highlighter div
if ($('.imageMapHighlighter').length > 0) {
/* ImageMap Highlighter — table legend edition (JS only; no CSS here)
- Renders a numbered table (No., Label) under each imagemap
- Hovering a table row highlights the mapped region (and vice versa)
- Robust map lookup via <img usemap> / siblings
- Safe to run multiple times (per-image guard)
Public domain; based on the original by user:קיפודנחש (hewiki)
*/
(function ($, mw) {
'use strict';
// ---------- Config ----------
var myClassName = 'imageMapHighlighterArtefacts',
liHighlightClass = 'liHighlighting',
specialAreaMark = 'area_mark',
specialLiClassesMark = 'list_classes',
areaHighLighting = { fillStyle: 'rgba(0,0,0,0.35)', strokeStyle: 'yellow', lineJoin: 'round', lineWidth: 2 },
hilightDivMarker = '.imageMapHighlighter',
he = mw && mw.config && mw.config.get('wgUserLanguage') === 'he',
expandLegend = he ? 'הצגת מקרא' : 'Show legend',
collapseLegend = he ? 'הסתרת המקרא' : 'Hide legend',
liEvents = 'mouseover mouseout focus blur',
mouseEvents = 'mouseover mouseout',
highlightEvents = ['mouseover', 'focus'];
// ---------- Drawing ----------
function drawMarker(context, areas) {
function drawPoly(coords) {
context.moveTo(coords.shift(), coords.shift());
while (coords.length) context.lineTo(coords.shift(), coords.shift());
}
for (var i = 0; i < areas.length; i++) {
var a = areas[i], coords = a.coords.split(',').map(Number);
context.beginPath();
switch (a.shape) {
case 'rect':
case null:
case '':
// MW 1.45+ sometimes omits shape="rect"
drawPoly([coords[0], coords[1], coords[0], coords[3], coords[2], coords[3], coords[2], coords[1]]);
break;
case 'circle':
context.arc(coords[0], coords[1], coords[2], 0, Math.PI * 2);
break;
case 'poly':
drawPoly(coords);
break;
}
context.closePath();
context.stroke();
context.fill();
}
}
// ---------- Legend hover handler (rows or legacy <li>) ----------
function mouseAction(e) {
var $this = $(this),
activate = highlightEvents.indexOf(e.type) >= 0,
caption = ($this.find('.im-label a').text() || $this.find('.im-label').text() || $this.text()).trim(),
$legend = $this.closest('.im-legend, ol'),
context = $legend.data('context'),
special = $legend.data(specialAreaMark);
$this.toggleClass(liHighlightClass, activate);
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
var $items = $legend.is('ol') ? $legend.children('li') : $legend.find('tbody tr.im-row');
$items.each(function () {
var $item = $(this);
var licap = ($item.find('.im-label a').text() || $item.find('.im-label').text() || $item.text()).trim();
var param;
if (activate && licap === caption) {
param = (special && special.hover) || areaHighLighting;
} else {
param = special && special.nover && (special.nover[licap] || special.nover.default);
}
if (param) {
$.extend(context, param);
drawMarker(context, $item.data('areas') || []);
}
});
}
// ---------- Build one imagemap ----------
function handleOneMap() {
var img = $(this);
// guard: process each image once
if (img.data('imhProcessed')) return;
// wait until the image has real layout size
if (!this.complete || !this.naturalWidth) {
img.one('load', handleOneMap);
return;
}
var w = this.width, h = this.height;
// Find the <map> reliably
var map = (function () {
var m = img.parent().siblings('map:first');
if (m.length) return m;
var usemap = img.attr('usemap'); // e.g. "#imagemap-123"
if (usemap) {
var name = usemap.replace(/^#/, '');
m = $('map[name="' + name + '"]');
if (m.length) return m.first();
}
return img.closest(hilightDivMarker).find('map:first');
})();
if (!map.length || !$('area', map).length) return; // nothing to do
// Layer sandwich: bg image (absolute), canvas (absolute), original img (absolute, transparent)
var dims = { position: 'absolute', width: w + 'px', height: h + 'px', border: 0, top: 0, left: 0 };
var jcanvas = $('<canvas>', { 'class': myClassName }).css(dims).attr({ width: w, height: h });
var bgimg = $('<img>', { 'class': myClassName, src: img.attr('src') }).css(dims);
var context = $.extend(jcanvas[0].getContext('2d'), areaHighLighting);
var wrapper = $('<div>').css({ position: 'relative', width: w + 'px', height: h + 'px' });
img.before(wrapper);
wrapper.append(bgimg).append(jcanvas).append(img);
img.fadeTo(1, 0); // mouse events still come from the original image
img.data('imhProcessed', true);
// Legend wrapper + table
var $legendWrap = $('<div>', {
'class': myClassName + ' im-legend mw-collapsible mw-collapsed'
}).attr({
'data-expandtext': expandLegend,
'data-collapsetext': collapseLegend
});
var $table = $(
'<table class="' + myClassName + ' im-table" aria-label="Image map legend">' +
'<thead><tr>' +
'<th class="im-num">#</th>' +
'<th class="im-label">Location</th>' +
'</tr></thead>' +
'<tbody></tbody>' +
'</table>'
);
$legendWrap.append($table);
wrapper.after($('<hr>', { 'class': myClassName }).css('clear', 'both')).after($legendWrap);
// Collapse duplicate titles into one row
var rowsByTitle = Object.create(null);
var idx = 0, $someRow;
var specialHighlight = img.closest(hilightDivMarker).data(specialAreaMark);
var specialLiClasses = img.closest(hilightDivMarker).data(specialLiClassesMark);
$('area', map).each(function () {
var title = this.title || '';
var href = this.href || '#';
var key = title; // use plain text for matching
var $row = rowsByTitle[key];
if (!$row) {
idx += 1;
$row = $('<tr>', { 'class': myClassName + ' im-row' })
.append($('<td class="im-num">').text(idx))
.append($('<td class="im-label">').append($('<a>', { href: href }).text(title)))
.on(liEvents, mouseAction)
.data('areas', [])
.appendTo($table.find('tbody'));
var addClass = specialLiClasses && (specialLiClasses[title] || specialLiClasses['default']);
if (addClass) $row.addClass(addClass);
rowsByTitle[key] = $row;
}
// bind area ↔ row hover
$row.data('areas').push(this);
$someRow = $row;
$(this).on(mouseEvents, function (e) { $row.trigger(e.type); });
});
if ($someRow) $someRow.trigger('mouseout');
// store drawing context on the wrapper for mouseAction
$legendWrap.data(specialAreaMark, specialHighlight).data('context', context);
mw.loader.using('jquery.makeCollapsible').then(function () {
$legendWrap.makeCollapsible();
});
}
// ---------- Init ----------
function init($scope) {
var $roots = ($scope && $scope.length) ? $scope : $(document);
$roots.find(hilightDivMarker + ' img').each(handleOneMap);
}
// Run on ready and when content changes (VE previews, Ajax loads, etc.)
$(function () { init(); });
mw.hook('wikipage.content').add(function ($c) { init($c); });
})(jQuery, mw);
