MediaWiki:Common.js: Difference between revisions

From No Way Out Wiki
No edit summary
Tag: Reverted
No edit summary
Tag: Reverted
Line 211: Line 211:
});
});


/* Timeless: Move/merge "Page tools" + "More" into the LEFT rail */
/* Timeless: put all page tools/More links into the LEFT rail,
  but DO NOT touch the header actions menu. */
mw.hook('wikipage.content').add(function () {
mw.hook('wikipage.content').add(function () {
   if (mw.config.get('skin') !== 'timeless') return;
   if (mw.config.get('skin') !== 'timeless') return;


   var $left  = $('#mw-site-navigation');
   var $left  = $('#mw-site-navigation');       // left sidebar
   var $right = $('#mw-related-navigation');
   var $right = $('#mw-related-navigation');     // right sidebar
   if (!$left.length) return;
   if (!$left.length || !$right.length) return;


   // Ensure a single "Page tools" portlet in the left rail
   // Ensure a single "Page tools" portlet in the left rail
   function ensurePageTools() {
   var $tools = $left.find('#p-tb, #p-tools').first();
    var $pt = $('#p-tb,#p-tools').first();
  if (!$tools.length) {
    if (!$pt.length) {
    $tools = $(
      $pt = $(
      '<div id="p-tb" class="sidebar-chunk">' +
        '<div id="p-tb" class="sidebar-chunk">' +
        '<h2>Page tools</h2>' +
          '<h2>Page tools</h2>' +
        '<div class="body"><ul></ul></div>' +
          '<div class="body"><ul></ul></div>' +
      '</div>'
        '</div>'
    ).prependTo($left);
      ).prependTo($left);
  } else {
    } else {
    $tools.addClass('sidebar-chunk');
      if (!$pt.closest('#mw-site-navigation').length) $pt.prependTo($left);
    if (!$tools.find('.body').length) $tools.wrapInner('<div class="body"></div>');
      $pt.addClass('sidebar-chunk');
    if (!$tools.find('ul').length) $tools.find('.body').append('<ul></ul>');
      if (!$pt.find('.body').length) $pt.wrapInner('<div class="body"></div>');
      if (!$pt.find('ul').length) $pt.find('.body').append('<ul></ul>');
    }
    return $pt;
   }
   }
   var $tools = ensurePageTools();
   var $list = $tools.find('ul');


   // Move all <li> items from a container into Page tools, then remove the container
   // Drain ONLY items that are INSIDE the RIGHT rail (leave header alone)
   function drain(container) {
   $right.find('#p-tb, #p-tools, #p-cactions, #p-actions, #p-more, .vector-menu').each(function () {
     var $c = $(container);
     var $src = $(this);
    if (!$c.length) return;
     var $ul = $src.find('ul').first();
     var $ul = $c.find('ul').first();
     if ($ul.length) $ul.children('li').appendTo($list);
     if ($ul.length) $ul.children('li').appendTo($tools.find('ul'));
     $src.remove(); // remove the emptied container from the right rail
     $c.remove();
  }
 
  // Known IDs that can hold "More"/actions/tool items
  ['#p-cactions', '#p-actions', '#p-more', '#p-page-tools', '#p-tb-sidebar'].forEach(drain);
 
  // Right-rail chunks by heading text
  $right.find('.sidebar-chunk').each(function () {
    var title = $(this).children('h2,h3,.sidebar-title').first().text().trim().toLowerCase();
    if (title === 'more' || title === 'page tools') drain(this);
   });
   });


   // Some builds use vector menus inside the sidebars
   // De-duplicate by href (in case items were present twice)
   $right.find('.vector-menu').each(function () {
  var seen = {};
     var txt = $(this).find('.vector-menu-heading, .menu .menu-label').first().text().trim().toLowerCase();
   $list.children('li').each(function () {
    if (txt === 'more' || txt === 'page tools') drain(this);
     var href = $(this).find('a').attr('href') || '';
    if (seen[href]) $(this).remove(); else seen[href] = true;
   });
   });
});
});

Revision as of 18:24, 23 September 2025

// ImageMap Highlighter - only load on pages that have the highlighter div
if ($('.imageMapHighlighter').length > 0) {
   /*
written by user:קיפודנחש on hewiki.
released as public domain. you may copy, modify and redistribute any way you want.
no guaranty or waranty of any kind, explicit or implied.
*/
$(document).ready(function() {

    var
//add this class to all elements created by the script. the reason is that we call the script again on
//window resize, and use the class to remove all the "artefacts" we created in the previous run.
		myClassName = 'imageMapHighlighterArtefacts'
		, liHighlightClass = 'liHighlighting'
		, specialAreaMark = 'area_mark'
		, specialLiClassesMark = 'list_classes'
// "2d context" attributes used for highlighting.
		, areaHighLighting = {fillStyle: 'rgba(0,0,0,0.35)', strokeStyle: 'yellow', lineJoin: 'round', lineWidth: 2}
//every imagemap that wants highlighting, should reside in a div of this 'class':
		, hilightDivMarker = '.imageMapHighlighter'
// specifically for wikis - redlinks tooltip adds this message
		, 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']
		;


	function drawMarker(context, areas) { // mthis is where the magic is done.

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

		for (var i in areas) {
			var coords = areas[i].coords.split(',');
			context.beginPath();
			switch (areas[i].shape) {
				case 'rect': 
				case null:
				case '': // imagemap extension stopped setting shape="rect" for rectangles in mw version 1.45.0-wmf.7
					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;//x,y,r,startAngle,endAngle
				case 'poly': drawPoly(coords); break;
			}
			context.closePath();
			context.stroke();
			context.fill();
		}
	}

	function mouseAction(e, fromMap) {
		var $this = $(this),
			activate = $.inArray(e.type, highlightEvents) >= 0,
			caption = $this.text(),
			ol = $this.parent(),
			context = ol.data('context'),
			special = ol.data(specialAreaMark);
		
		$this.toggleClass(liHighlightClass, activate); // mark/unmark the list item. 
		
		context.clearRect(0, 0, context.canvas.width, context.canvas.height); // prepare for a new day.
		
		ol.find('li').each(function() {
			var $li = $(this);
			var licap = $li.text();
			var param;
			if (activate && licap === caption) { // highlight!!!
				param = special && special.hover  || areaHighLighting;
			} else {
				param = special && special.nover && (special.nover[licap] || special.nover.default);
			}
			if (param) {
				$.extend(context, param);
				drawMarker(context, $li.data('areas'));
			}
		});
	}

	function handleOneMap() {
		var img = $(this), 
			w = this.width, 
			h = this.height,
			map = img.parent().siblings('map:first'),
			dims = {position: 'absolute', width: w + 'px', height: h + 'px', border: 0, top:0, left:0},
			specialHighlight = img.closest(hilightDivMarker).data(specialAreaMark),
			specialLiClasses = img.closest(hilightDivMarker).data(specialLiClassesMark);
		
		
		if (!($('area', map).length))
			return;	//not an imagemap, or map with 0 areas.

		var jcanvas = $('<canvas>', {'class': myClassName})
			.css(dims)
			.attr({width: w, height: h});
		var bgimg = $('<img>', {'class': myClassName, src: img.attr('src')})
			.css(dims);//completely inert image. this is what we see.
		var context = $.extend(jcanvas[0].getContext("2d"), areaHighLighting);
		
	// this is where the magic is done: prepare a sandwich of the inert bgimg at the bottom,
	// the canvas above it, and the original image on top,
	// so canvas won't steal the mouse events.
	// pack them all TIGHTLY in a newly minted "relative" div, so when page chnage
	// (other scripts adding elements, window resize etc.), canvas and imagese remain aligned.
		var div = $('<div>').css({position: 'relative', width: w + 'px', height: h + 'px'});
		img.before(div);	// put the div just above the image, and ...
		div.append(bgimg)	// place the background image in the div
			.append(jcanvas)// and the canvas. both are "absolute", so they don't occupy space in the div
			.append(img);	// now yank the original image from the window and place it on the div.
		img.fadeTo(1, 0);	// make the image transparent - we see canvas and bgimg through it. 
							// the original, now transparent image is creating our mouse events
		
		var ol = $('<ol>', {'class': myClassName})
			.css({clear: 'both', margin: 0, listStyle: 'none', maxWidth: w + 'px', float: 'left', position: 'relative'})
			.attr({'data-expandtext' : expandLegend, 'data-collapsetext': collapseLegend})
			.data(specialAreaMark, specialHighlight)
			.data('context', context);

		// ol below image, hr below ol. original caption pushed below hr.
		div.after($('<hr>', {'class': myClassName}).css('clear', 'both'))
			.after(ol);
		var lis = {};	//collapse areas with same caption to one list item
		var someli; // select arbitrary one
		$('area', map).each(function() {
			var html = mw.html.escape(this.title);
			var li = lis[html];	// title already met? use the same li
			if (!li) {			//no? create a new one.
				var href = this.href;
				lis[html] = li = $('<li>', {'class': myClassName})
					.append($('<a>', {href: href}).html(html))  
					.on(liEvents, mouseAction)
					.data('areas', [])
					.addClass(specialLiClasses && (specialLiClasses[html] || specialLiClasses['default']))
					.appendTo(ol);
			}
			li.data('areas').push(this);	//add the area to the li
			someli = li; // whichever - we just want one...
			$(this).on(mouseEvents, function(e) {
					li.trigger(e.type);
			});
		});
		if (someli) someli.trigger('mouseout');
		mw.loader.using('jquery.makeCollapsible').then( function () {
			ol.addClass('mw-collapsed')
				.makeCollapsible();
		});
	}

	function init() {
		mw.util.addCSS('li.' + myClassName + '{white-space:nowrap;border:solid 1px transparent;border-radius:6px;}\n' + //css for li element
					'li.' +  myClassName + '.' + liHighlightClass + '{background-color:yellow;border-color:green;}\n' + //css for highlighted li element.
					'.rtl li.' + myClassName + '{float: right; margin-left: 3em;}\n' +
					'.ltr li.' + myClassName + '{float: left; margin-right: 3em;}');
		$(hilightDivMarker+ ' img').each(handleOneMap);
	}

	//has at least one "imagehighlight" div, and canvas-capable browser:
	if ( $(hilightDivMarker).length && $('<canvas>')[0].getContext )
		mw.loader.using( ['jquery.makeCollapsible', 'mediawiki.util'] ).done( init );
});
}

/* TOC JS */


/* Move #toc into Timeless' right rail and make it behave responsively */
mw.hook('wikipage.content').add(function ($c) {
  var $toc = $c.find('#toc');
  if (!$toc.length) return;

  var $rail = $('#mw-related-navigation');
  if (!$rail.length) return;

  // Create a sidebar section for the TOC if it doesn't exist
  var $chunk = $rail.find('.sidebar-chunk.toc-chunk');
  if (!$chunk.length) {
    $chunk = $('<div class="sidebar-chunk toc-chunk"></div>').prependTo($rail);
  }
  $chunk.prepend($toc);        // move TOC into the rail

  // Keep the "hide" toggle in sync with a compact class
  function syncCollapsed() {
    var collapsed = $toc.children('ul:first').is(':hidden');
    $toc.toggleClass('toc--collapsed', collapsed)
        .toggleClass('toc--trim', collapsed);  // hide sublevels when collapsed
  }
  syncCollapsed();
  $toc.on('click', '.toctoggle a, .toctogglelabel, .toctogglelink', function () {
    setTimeout(syncCollapsed, 0);
  });

  // Optional: on small screens, move the TOC back into the article flow
  function place() {
    if (window.matchMedia('(max-width:980px)').matches) {
      if (!$toc.closest('.mw-parser-output').length) {
        $('.mw-parser-output').first().prepend($toc);
      }
    } else {
      if (!$toc.closest('#mw-related-navigation').length) {
        $chunk.prepend($toc);
      }
    }
  }
  place();
  $(window).on('resize', place);
});

/* Timeless: put all page tools/More links into the LEFT rail,
   but DO NOT touch the header actions menu. */
mw.hook('wikipage.content').add(function () {
  if (mw.config.get('skin') !== 'timeless') return;

  var $left  = $('#mw-site-navigation');        // left sidebar
  var $right = $('#mw-related-navigation');     // right sidebar
  if (!$left.length || !$right.length) return;

  // Ensure a single "Page tools" portlet in the left rail
  var $tools = $left.find('#p-tb, #p-tools').first();
  if (!$tools.length) {
    $tools = $(
      '<div id="p-tb" class="sidebar-chunk">' +
        '<h2>Page tools</h2>' +
        '<div class="body"><ul></ul></div>' +
      '</div>'
    ).prependTo($left);
  } else {
    $tools.addClass('sidebar-chunk');
    if (!$tools.find('.body').length) $tools.wrapInner('<div class="body"></div>');
    if (!$tools.find('ul').length) $tools.find('.body').append('<ul></ul>');
  }
  var $list = $tools.find('ul');

  // Drain ONLY items that are INSIDE the RIGHT rail (leave header alone)
  $right.find('#p-tb, #p-tools, #p-cactions, #p-actions, #p-more, .vector-menu').each(function () {
    var $src = $(this);
    var $ul = $src.find('ul').first();
    if ($ul.length) $ul.children('li').appendTo($list);
    $src.remove();  // remove the emptied container from the right rail
  });

  // De-duplicate by href (in case items were present twice)
  var seen = {};
  $list.children('li').each(function () {
    var href = $(this).find('a').attr('href') || '';
    if (seen[href]) $(this).remove(); else seen[href] = true;
  });
});