User:Bernanke's Crossbow/ModifiedIME.js

/* Coordinatizing an HTML image map can be hard for you. What vision is made from numbers? This script lets you see as you do. Version as of eight/thirteen/twenty-two. Original script by Peter Schlömer modified by	he:user:קיפודנחש and also Bernanke's Crossbow. The original script displays a copyright warning. It says: (https://tools.wmflabs.org/imagemapedit/ime.js) Copyright (c) 2007-2013 Peter Schlömer

Released under the following licenses (to make reuse in other Wikis		easier):

GNU General Public License (GPL), version 2 GNU Free Documentatin Licence (GFDL), version 1.2 or later Creative Commons Attribution ShareAlike (CC-by-sa), version 2 or later

// /*jshint esversion: 6 */ if (mw.config.get('wgNamespaceNumber') == 6 &&	mw.config.get('wgAction') == 'view') $(function {		"use strict";		//Ensure idempotence, in case (eg)		//m:Special:MyPage/global.js and main wiki both activate the script		if (window.mw_ime_running) return;		window.mw_ime_running = true;

//General utilities const util = { //Localize error messages to your favorite language translations: { error_imagenotfound: 'ImageMapEdit: Could not find image in page structure.' },			//Translate texts translate { for (const trans of [					this.translations,					window.ime_translations				]) for (const key in trans || {}) $('.ime_t_' + key).text(trans[key]); },			std_handler(fct) { return e => { e.preventDefault; fct(e); };			},			displayErr(message) { //First try on screen const jqFile = $('#file'); if (jqFile.length !== 0) { const errBox = $(' ')							.css({								'color': 'darkred',								'background': 'white',								'border': '1px solid darkred',								'padding': '1ex'							}) .text(message); const jqIme = $('#ime'); if (jqIme !== 0) jqIme.before(errBox); else errBox.appendTo(jqFile); }				//Can't find a good location else window.alert(message); },			makeDiv(id) { const div = document.createElement('div'); div.id = id || ""; return div; },			//Try to find an  tag within the specified HTML document node. findHyperlink(node) { //Look at the first child until it is none or  let a = node; while (a && a.nodeName.toUpperCase != 'A') a = a.firstChild; return a;			}, all(arr) { return arr.reduce((a, b) => a && b); } };		//Keep track of recent clicks const mouse = { listening: false, currentClicks: {}, relax { this.listening = false; this.currentClicks = {}; ime.jcanvas.css({ cursor: '' }); },			listen { this.listening = true; ime.jcanvas.css({ cursor: 'crosshair' }); },			fired(event) { if (!this.listening) return; event.preventDefault; event.stopPropagation; const offset = $('#imeImg').offset; const x = event.pageX - offset.left, y = event.pageY - offset.top; const position = { x: parseInt(x / ime.scale), y: parseInt(y / ime.scale) };				const dblClick = event.detail > 1; this.currentClicks[dblClick ? 'left' : 'right'] = position; ime.store(dblClick); }		};		//Highlighted areas const areas = Array; areas.current = function { return this[this.currentlyEditing]; }; areas.make = function (shape) { mouse.listen; this.push({ shape: shape, coords: [], link: '' }); this.currentlyEditing = this.length - 1; this.update; return areas.current; };		areas.update = function { this.updateSelection; this.edit(this.currentlyEditing); updateResult; };		areas.updateSelection = function { this.selection.children.remove; for (const [i, area] of this.entries) { const title = (area.title || area.link || '') + ' [' + area.shape + ']'; $(' ', { value: i }) .text(title) .prop({ selected: i == this.currentlyEditing }) .appendTo(this.selection); }			this.selection.prop('selectedIndex', this.currentlyEditing); };		areas.edit = function (index) { $('#imeProps').toggle(false);

const area = this[index]; mouse.currentClicks = (area || {}).clicks || {}; if (area) { this.currentlyEditing = index;

$('#imeProps').toggle(true); $('.ime-prop').toggle(false); $('.ime-prop-' + area.shape).toggle(true);

mouse.listen; updateInputs; }			this.draw; };		areas.remove = function { mouse.relax; // Remove element from this array this.splice(this.currentlyEditing, 1); this.currentlyEditing = Math.min(this.currentlyEditing, this.length - 1); this.update; if (this.currentlyEditing >= 0) this.edit(this.currentlyEditing); };		areas.draw = function { // this is where the magic is done. function markPoint(point, color) { if (point) { const arm = 8 / ime.scale; ime.context.beginPath; ime.context.moveTo(point.x + arm, point.y); ime.context.lineTo(point.x - arm, point.y); ime.context.moveTo(point.x, point.y + arm); ime.context.lineTo(point.x, point.y - arm); ime.context.strokeStyle = color; ime.context.stroke; ime.context.closePath; }			}

function drawPoly(coords) { coords = coords.slice; ime.context.moveTo(coords.shift, coords.shift); while (coords.length) ime.context.lineTo(coords.shift, coords.shift); }

// prepare for a new day. ime.context.clearRect(0, 0,				ime.context.canvas.width / ime.scale,				ime.context.canvas.height / ime.scale); for (const [ind, area] of this.entries) { const current = (ind == this.currentlyEditing); ime.context.fillStyle = 'rgba(255,' + (current ? '255' : '0') + ',0,0.4)';				const coords = area.coords; ime.context.beginPath; switch (area.shape) { case 'rect': //Ignore spurious clicks outside if (coords.length && util.all(coords)) drawPoly([								coords[0], coords[1],								coords[0], coords[3],								coords[2], coords[3],								coords[2], coords[1]							]); break; case 'circle': if (coords.length && util.all(coords)) //x,y,r,startAngle,endAngle ime.context.arc(coords[0], coords[1], coords[2],								0, Math.PI * 2); break; case 'poly': drawPoly(coords); break; }				ime.context.closePath; ime.context.fill; if (current) { ime.context.strokeStyle = 'red'; ime.context.stroke; }			}			markPoint(mouse.currentClicks.left, 'red'); if (this.current && this.current.shape != 'poly') markPoint(mouse.currentClicks.right, 'yellow'); };

const ime = { //Remove UI elements that might interfere //(Wikimedia Commons 'annotations' feature) trimUI { $('#ImageAnnotationAddButton').remove; }, store(leftClick) { const area = areas.current; area.link = document.ime.areaLink.value; area.title = document.ime.areaTitle.value; const d = area.clicks = $.extend({}, mouse.currentClicks);

const full = d.left && d.right; switch (area.shape) { case 'rect': if (d.left) { area.coords[0] = d.left.x;							area.coords[1] = d.left.y;						} if (d.right) { area.coords[2] = d.right.x;							area.coords[3] = d.right.y;						} break; case 'circle': if (leftClick) { area.coords[0] = d.left.x;							area.coords[1] = d.left.y;						} if (full) { const dx = d.left.x - d.right.x,								dy = d.left.y - d.right.y;							area.coords[2] = parseInt(Math.hypot(dx, dy)); }						break; case 'poly': if (leftClick && d.left) area.coords.push(d.left.x, d.left.y); break; }				updateInputs(full || area.shape == 'poly'); areas.update; },			imports: { make { for (const line of						document.ime.importText.value.split("\n")) { const detect = { rect: /^rect +(\d+) +(\d+) +(\d+) +(\d+) +\[\[([^|]*)(|(.*))?\]\]/i, circ: /^circle +(\d+) +(\d+) +(\d+) +\[\[([^|]*)(|(.*))?\]\]/i, poly: /^poly +(.*?) +\[\[([^|]*)(|(.*))?\]\]/i };

let results; if (results = detect.rect.exec(line)) { const area = areas.make("rect"); area.coords = results.slice(1, 5); area.link = results[5]; if (results[6]) area.title = results[6].substring(1); }						else if (results = detect.circ.exec(line)) { const area = areas.make("circle"); area.coords = results.slice(1, 4); area.link = results[4]; if (results[5]) area.title = results[5].substring(1); }						else if (results = detect.poly.exec(line)) { const area = areas.make("poly"); area.coords = results[1].split(/\s+/); area.link = results[2]; if (results[3]) area.title = results[3].substring(1); }					}					areas.update; this.hide; },				show { $('#imeImport').show; $('#imeImportShow').hide; $('#imeImportHide').show; },				hide { $('#imeImport').hide; $('#imeImportShow').show; $('#imeImportHide').hide; }			}		};

/*			Initialization, part 1: -Find image and -Download info async via XMLHttpRequest. When complete... -...show a link to load the rest of ImageMapEdit (continuance) */		{			const divFile = document.getElementById('file'); if (divFile) try { const a = util.findHyperlink(divFile); if (!a) throw 'a'; const img = a.firstChild; if (!img) throw 'img'; const url = mw.config.get('wgScriptPath') + '/api.php?format=json&action=query&prop=imageinfo&' + 'iiprop=size&titles=' + mw.config.get('wgPageName'); $.get(url, data => {					if (typeof data.query.pages != "undefined") {						const imageProperties =							data.query.pages[Object.keys(data.query.pages)[0]];						const imageInfo = imageProperties.imageinfo;						if (imageInfo) {							ime.width = imageInfo[0].width;							ime.height = imageInfo[0].height;							ime.scale = img.width / ime.width;							// Show 'show ImageMapEdit' button now							$('', { id: 'imeLink' })								.css({ display: 'block' })								.text('⟨Start ImageMapEdit⟩')								.click(util.std_handler(continuance))								.appendTo('#file');						}					}				}); } catch (e) { util.displayErr(					'ImageMapEdit initialization failed: "' +					e + '" was missing'); }		}

/*			Initialization, part 2 (triggered by link): -Move the image in the logical structure of the page, then -Hides the link and finally -Adds the ImageMapEdit HTML. */		function continuance { ime.trimUI;

const divFile = document.getElementById('file'), tempNode = divFile.firstChild, a = util.findHyperlink(tempNode), img = a.firstChild, $img = $(img), divImeContainer = util.makeDiv('imeContainer');

divImeContainer.style.position = 'relative';

// Move image from within link to outside a.removeChild(img);

divFile.insertBefore(divImeContainer, tempNode); divFile.removeChild(tempNode); // Fix for rtl wikis, thanks to hewiki user "קיפודנחש" divFile.style.direction = 'ltr';

img.id = 'imeImg'; img.style.border = 'none'; const handler = util.std_handler(e => e.stopPropagation); img.oncontextmenu = handler;

// Internet Explorer needs this differently divImeContainer.style.overflow = (typeof (navigator.userAgent) != 'undefined' &&					navigator.userAgent.match('/MSIE/')) ? 'none' : 'auto'; divImeContainer.appendChild(img);

ime.jcanvas = $(' ') .css({					position: 'absolute',					width: $img.width + 'px',					height: $img.height + 'px',					border: 0,					top: 0,					left: 0				}) .attr({ width: $img.width, height: $img.height }) .appendTo(divImeContainer); ime.context = ime.jcanvas[0].getContext("2d"); $.extend(ime.context, {				fillStyle: 'rgba(255,255,0,0.4)',				strokeStyle: 'red',				lineJoin: 'round',				lineWidth: 1.5 / ime.scale			}			); ime.context.scale(ime.scale, ime.scale); ime.jcanvas.click(mouse.fired.bind(mouse)); ime.jcanvas.click(handler); ime.jcanvas[0].oncontextmenu = img.oncontextmenu = handler;

const divIme = util.makeDiv('ime'); divFile.appendChild(divIme);

// Hide the link now document.getElementById('imeLink').style.display = 'none'; // Disable image context menu so right click can be used for events img.oncontextmenu = e => ((e.cancelBubble = true), false);

$(divIme).html(ime.templateHtml); attachTemplate; util.translate; }

function updateResult { const arr = Array.from(document.ime.imageDescriptionPos), imageDescriptionPos = (arr.find(elt => elt.checked) || arr[0]).value;

const results = Array.concat(				[' ']			);

const preResult = document.getElementById('imeResult'); while(preResult.lastChild) preResult.removeChild(preResult.lastChild);

for (const result of results) { preResult.appendChild(document.createTextNode(result)); preResult.appendChild(document.createElement('br')); }			areas.updateSelection; }

function updateInputs(fromAreas) { function updateShape(selectors, coords) { for (const [index, selector] of selectors.entries) $(selector).val(coords && coords[index] || ''); }

function updatePolyInputs(coords) { $('#imePropsPolyCoords').text(coords.join(', ')); }

const area = areas.current, coords = area.coords; switch (area.shape) { case 'rect': updateShape([						'#ime_areaRectLeft',						'#ime_areaRectTop',						'#ime_areaRectRight',						'#ime_areaRectBottom'					], coords); break; case 'circle': updateShape([						'#ime_areaCircleX',						'#ime_areaCircleY',						'#ime_areaCircleRadius'					], coords); break; case 'poly': updatePolyInputs(coords); break; }			$('#ime_areaLink').val(area.link || ''); $('#ime_areaTitle').val(area.title || ''); }

function attachTemplate { {const clicks = { '.ime_t_rect': => areas.make('rect'), '.ime_t_circle': => areas.make('circle'), '.ime_t_poly': => areas.make('poly'), '.ime_t_deletearea': areas.remove.bind(areas), '#imeImportShow': ime.imports.show.bind(ime.imports), '#imeImportHide': ime.imports.hide.bind(ime.imports), '.ime_t_import': ime.imports.make.bind(ime.imports), '.ime_t_deletecoordinates': => { areas.current.coords = []; mouse.currentClicks = {}; ime.store; }				}, changes = { '#ime_areaselector': function (e) { areas.edit($(this).prop('selectedIndex')); },					'.ime_saveonchange': ime.store.bind(ime), '.ime-updateresultsonchange': updateResult, '#ime_areaLink': function { areas.current.link = $(this).val; updateResult; },					'#ime_areaTitle': function { areas.current.title = $(this).val; updateResult; }				};			for (const [id, click] of Object.entries(clicks)) $(id).click(util.std_handler(click)); for (const [id, change] of Object.entries(changes)) $(id).change(change); }			areas.selection = $('#ime_areaselector'); $('.ime-saveonchange').focusout(function {				const					input = $(this),					ind = input.data('coord'),					val = parseInt(input.val),					area = areas.current,					coords = area && area.coords;				if (coords && typeof (ind) == 'number' && typeof (val) == 'number' && !isNaN(val)) {					coords[ind] = val;					mouse.currentClicks = {};					areas.draw;				}			}); mw.loader.using(['mediawiki.api', 'jquery.ui']).done( => {				$('#ime_areaLink').autocomplete({ source(request, response) { new mw.Api.get({							action: 'opensearch',							search: request.term,							namespace: 0,							limit: 10						}).done(data => {							if (data && data.length > 1)								response(data[1]);						}); // done } // source }); // autocomplete			}); // using }

// this is the one-(very long)-line version of the template //mw linter does not allow use multiline delimiter (ascii 96), not even //in the comment...formatted version in comment below. ime.templateHtml = ' ImageMapEdit Create new area rect(angle) circle poly(gon) Edit area  <label for="ime_areaLink" class="ime_t_linktarget">Link target <input id="ime_areaLink" name="areaLink" style="width:10em" class="ime-saveonchange" /> <label for="ime_areaTitle"><span class="ime_t_linktitle">Link title (<span class="ime_t_optional">optional ) <input id="ime_areaTitle" name="areaTitle" style="width:10em" class="ime-saveonchange" /> <div id="imePropsRect" class="ime-prop ime-prop-rect" style="float:left;margin:0.5ex;padding:0 1ex 1ex;display:none"><label for="ime_areaRectLeft" class="ime_t_rectleft">First corner <input id="ime_areaRectLeft" name="areaRectLeft" data-coord=0 class="ime-saveonchange" style="width:4em" /><input id="ime_areaRectTop" name="areaRectTop" data-coord=1 style="width:4em" class="ime-saveonchange" /><span class="ime_t_rectchoose1">Select with double-click <label for="ime_areaRectRight" class="ime_t_rectright">Second corner <input id="ime_areaRectRight" name="areaRectRight" data-coord=2 style="width:4em" class="ime-saveonchange" /><input id="ime_areaRectBottom" name="areaRectBottom" data-coord=3 style="width:4em" class="ime-saveonchange" /><span class="ime_t_rectchoose2">Select with left mouse button <div id="imePropsCircle" class="ime-prop ime-prop-circle" style="float:left;margin:0.5ex;padding:0 1ex 1ex;display:none"><label class="ime_t_position">Position <input name="areaCircleX" id="ime_areaCircleX" class="ime-saveonchange" data-coord=0 style="width:4em" /><input name="areaCircleY" id="ime_areaCircleY" style="width:4em" data-coord=1 class="ime-saveonchange" /><span class="ime_t_circlechoose1">Select with double-click <label for="ime_areaCircleRadius" class="ime_t_radius">Radius <input id="ime_areaCircleRadius" name="areaCircleRadius" data-coord=2 style="width:4em" class="ime-saveonchange" /><span class="ime_t_circlechoose2">Select with left mouse button <div id="imePropsPoly" class="ime-prop ime-prop-poly" style="float:left;margin:0.5ex;padding:0 1ex 1ex;display:none"><label class="ime_t_coordinates">Coordinates <p id="imePropsPolyCoords" style="font-size:0.8em;max-width:20em;"> <a style="padding:1px;background:white;color:darkblue" class="ime_t_deletecoordinates">Delete all coordinates</a> <span class="ime_t_polychoose">Add new corner with double-click  <a style="padding:1px;background:white;color:darkblue" class="ime_t_deletearea">Delete selected area</a>  <fieldset style="margin:0 0.5ex 0.5ex;padding:0 1ex 1ex"><legend class="ime_t_preferences">General preferences <label for="ime_imageDescription" class="ime_t_imagedescription">Image description <input id="ime_imageDescription" name="imageDescription" class="ime-updateresultsonchange" /> <label style="display:block" class="ime_t_infolinkposition">Position of information link <input type="radio" name="imageDescriptionPos" value="bottom-left" class="ime-updateresultsonchange" checked="checked" id="ime_imageDescriptionPos_bottomleft" /><label for="ime_imageDescriptionPos_bottomleft"><span class="ime_t_bottomleft">Bottom left (<span class="ime_t_default">default ) <input type="radio" name="imageDescriptionPos" value="bottom-right" class="ime-updateresultsonchange" id="ime_imageDescriptionPos_bottomright" /><label for="ime_imageDescriptionPos_bottomright" class="ime_t_bottomright">Bottom right <input type="radio" name="imageDescriptionPos" value="top-left" class="ime-updateresultsonchange" id="ime_imageDescriptionPos_topleft" /><label for="ime_imageDescriptionPos_topleft" class="ime_t_topleft">Top left <input type="radio" name="imageDescriptionPos" value="top-right" class="ime-updateresultsonchange" id="ime_imageDescriptionPos_topright" /><label for="ime_imageDescriptionPos_topright" class="ime_t_topright">Top right <input type="radio" name="imageDescriptionPos" value="none" class="ime-updateresultsonchange" id="ime_imageDescriptionPos_none" /><label for="ime_imageDescriptionPos_none" class="ime_t_nolink">No link  <fieldset style="margin:0.5ex;padding:0 1ex 1ex"><legend class="ime_t_importareas">Import areas from wikicode <a style="padding:1px;background:white;color:darkblue" id="imeImportShow"><span class="ime_t_showtextbox">Show text box &gt;</a><a style="padding:1px;background:white;color:darkblue;display:none" id="imeImportHide"><span class="ime_t_hidetextbox">Hide text box &lt;</a> <textarea name="importText" style="width:100%;margin:0;height:10em;display:block"> <a style="padding:1px;background:white;color:darkblue" class="ime_t_import">Import</a> <fieldset style="margin:0.5ex;padding:0 1ex 1ex"><legend class="ime_t_generatedwikicode">Generated wikicode

`;

}); //