User:Þjarkur/IPA popups.js

/*

Loads IPA data from the help page and displays as a tooltip.

Popup functionality from MediaWiki:Gadget-ReferenceTooltips.js, requires that the gadget be turned on in settings for the CSS to work.

function GetIPATable(element, callback) { /* Will save data on the form { "IPA letter": "HTML table row"} */ var IPA_keys = {} var IPA var help_page_url var help_page_title var header = null

function find_rows(html) { $(html).find('.wikitable tr').each(function {      $(this).find('sup').remove      if ($(this).children('th').length > 0 && $(this).children('th[colspan]').length === 0) {        if(header) return;        header = $(this).html      } else if ($(this).children('td').length > 0) {      	$(this).find('[rowspan], [colspan]').attr('rowspan',).attr('colspan',)        var IPA_key = $(this).children('td').first.text.replace(/[◌]/g, '').trim        IPA_keys[IPA_key] = $(this).html      }    }) create_table console.log(Object.keys(IPA_keys)) }

function create_table { var array_of_IPA_keys = Object.keys(IPA_keys).sort((a, b) => b.length - a.length) var r = new RegExp('( |' + array_of_IPA_keys.map(escapeRegExp).join('|') + ')') var split = IPA.split(r).filter(Boolean) var table = ` Information from ${help_page_title} `   callback(table) }

// Taken from developer.mozilla.org function escapeRegExp(string) { return string.replace(/[.*+?^${}|[\]\\]/g, '\\$&'); // $& means the whole matched string }

/*		Run */ IPA = $(element).text.replace(/[\[\]/]/g, '').trim help_page_url = $(element).attr('href') help_page_title = help_page_url.replace('/wiki/', '') if (!help_page_title.startsWith('Help:IPA/')) return; $.ajax({   url: help_page_url,    type: 'get',    success: function (html) {      find_rows(html)    }  }) }

// Derived from MediaWiki:Gadget-ReferenceTooltips.js (function {  var REF_LINK_SELECTOR = '.IPA a'

mw.messages.set({   'rt-settings': 'Reference Tooltips settings',    'rt-enable-footer': 'Enable Reference Tooltips',    'rt-settings-title': 'Reference Tooltips',    'rt-save': 'Save',    'rt-cancel': 'Cancel',    'rt-enable': 'Enable',    'rt-disable': 'Disable',    'rt-activationMethod': 'Tooltip appears when',    'rt-hovering': 'hovering',    'rt-clicking': 'clicking',    'rt-delay': 'Delay before the tooltip appears (in milliseconds)',    'rt-disabledNote': 'You can re-enable Reference Tooltips using a link in the footer of the page.',    'rt-done': 'Done',    'rt-enabled': 'Reference Tooltips are enabled'  });

// "Global" variables var SECONDS_IN_A_DAY = 60 * 60 * 24, CLASSES = { FADE_IN_DOWN: 'rt-fade-in-down', FADE_IN_UP: 'rt-fade-in-up', FADE_OUT_DOWN: 'rt-fade-out-down', FADE_OUT_UP: 'rt-fade-out-up' },   IS_TOUCHSCREEN = 'ontouchstart' in document.documentElement, // Quite a rough check for mobile browsers, a mix of what is advised at   // https://stackoverflow.com/a/24600597 (sends to    // https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent) // and https://stackoverflow.com/a/14301832 IS_MOBILE = /Mobi|Android/i.test(navigator.userAgent) || typeof window.orientation !== 'undefined', CLIENT_NAME = $.client.profile.name, settingsString, settings, enabled, delay, activatedByClick, tooltipsForComments, cursorWaitCss, windowManager, $body = $(document.body), $window = $(window);

function rt($content) { // Popups gadget if (window.pg) { return; }

var teSelector

function TooltippedElement($element) { var tooltip, events, thisElement = this;

function onStartEvent(e) {

var showRefArgs;

if (activatedByClick) { e.preventDefault; }       if (!thisElement.noRef) { showRefArgs = [$(this)]; // showRefArgs.push(e.pageX, e.pageY); thisElement.showRef.apply( thisElement, showRefArgs ); }     }

function onEndEvent { if (!thisElement.noRef) { thisElement.hideRef; }     }

if (!$element) { return; }

// TooltippedElement.$element and TooltippedElement.$originalElement will be different when // the first is changed after its cloned version is hovered in a tooltip this.$element = $element; this.$originalElement = $element;

if (activatedByClick) { events = { 'click.rt': onStartEvent };       // Adds an ability to see tooltips for links if (this.type === 'commentedText' &&         (this.$element.closest('a').length || this.$element.has('a').length )       ) {          events['contextmenu.rt'] = onStartEvent; }     } else { events = { 'mouseenter.rt': onStartEvent, 'mouseleave.rt': onEndEvent };     }

this.$element.on(events);

this.hideRef = function (immediately) { clearTimeout(thisElement.showTimer);

if (this.type === 'commentedText') { this.$element.attr('title', this.comment); }

if (this.tooltip && this.tooltip.isPresent) { if (activatedByClick || immediately) { this.tooltip.hide; } else { this.hideTimer = setTimeout(function {              thisElement.tooltip.hide;            }, 200); }       } else if (this.$ref && this.$ref.hasClass('rt-target')) { this.$ref.removeClass('rt-target'); if (activatedByClick) { $body.off('click.rt touchstart.rt', this.onBodyClick); }       }      };

this.showRef = function ($element, ePageX, ePageY) { // Popups gadget if (window.pg) { disableRt; return; }

if (this.tooltip && !this.tooltip.$content.length) { return; }

var tooltipInitiallyPresent = this.tooltip && this.tooltip.isPresent;

function reallyShow(html) { var viewportTop, refOffsetTop, teHref;

if (!thisElement.tooltip) { thisElement.tooltip = new Tooltip(thisElement, html); if (!thisElement.tooltip.$content.length) { return; }         }

// If this tooltip is called from inside another tooltip. We can't define it         // in the constructor since a ref can be cloned but have the same Tooltip object; // so, Tooltip.parent is a floating value. thisElement.tooltip.parent = thisElement.$element.closest('.rt-tooltip').data('tooltip'); if (thisElement.tooltip.parent && thisElement.tooltip.parent.disappearing) { return; }

thisElement.tooltip.show;

if (tooltipInitiallyPresent) { if (thisElement.tooltip.$element.hasClass('rt-tooltip-above')) { thisElement.tooltip.$element.addClass(CLASSES.FADE_IN_DOWN); } else { thisElement.tooltip.$element.addClass(CLASSES.FADE_IN_UP); }           return; }

thisElement.tooltip.calculatePosition(ePageX, ePageY);

$window.on('resize.rt', thisElement.onWindowResize); }

// We redefine this.$element here because e.target can be a reference link inside // a reference tooltip, not a link that was initially assigned to this.$element this.$element = $element;

if (this.type === 'commentedText') { this.$element.attr('title', ''); }

if (activatedByClick) { if (tooltipInitiallyPresent ||           (this.$ref && this.$ref.hasClass('rt-target'))          ) { return; } else { setTimeout(function {              $body.on('click.rt touchstart.rt', thisElement.onBodyClick);            }, 0); }       }

GetIPATable($element, function(html) {	       if (activatedByClick || tooltipInitiallyPresent) {	          reallyShow(html);	        } else {	          this.showTimer = setTimeout(function{reallyShow(html)}, delay);	        }				}) };

this.onBodyClick = function (e) { if (!thisElement.tooltip && !thisElement.$ref.hasClass('rt-target')) { return; }

var $current = $(e.target);

function contextMatchesParameter(parameter) { return this === parameter; }

// The last condition is used to determine cases when a clicked tooltip is the current // element's tooltip or one of its descendants while ($current.length &&         (!$current.hasClass('rt-tooltip') || !$current.data('tooltip') || !$current.data('tooltip').upToTopParent(             contextMatchesParameter, [thisElement.tooltip],              true            ) )       ) {          $current = $current.parent; }       if (!$current.length) { thisElement.hideRef; }     };

this.onWindowResize = function { thisElement.tooltip.calculatePosition; };   }

function Tooltip(thisElement, html) { var tooltip = this;

// This variable can change: one tooltip can be called from a harvard-style reference link // that is put into different tooltips this.thisElement = thisElement;

// this.id = 'rt-' + this.thisElement.$originalElement.attr('id'); this.$content = $($.parseHTML(html))

if (!this.$content.length) { return; }

this.insideWindow = Boolean(this.thisElement.$element.closest('.oo-ui-window').length);

this.$element = $(' ') .addClass('rt-tooltip') .attr('id', this.id) .attr('role', 'tooltip') .data('tooltip', this); if (this.insideWindow) { this.$element.addClass('rt-tooltip-insideWindow'); }

// We need the $content interlayer here in order for the settings icon to have correct // margins this.$content = this.$content .wrapAll(' ') .parent .addClass('rt-tooltipContent') .addClass('mw-parser-output') .appendTo(this.$element);

if (!activatedByClick) { this.$element .mouseenter(function {            if (!tooltip.disappearing) {              tooltip.upToTopParent(function  { this.show; });           }          })          .mouseleave(function (e) {            // https://stackoverflow.com/q/47649442 workaround. Relying on relatedTarget            // alone has pitfalls: when alt-tabbing, relatedTarget is empty too            if (CLIENT_NAME !== 'chrome' || (!e.originalEvent ||               e.originalEvent.relatedTarget !== null ||                !tooltip.clickedTime ||                $.now - tooltip.clickedTime > 50              ) ) {             tooltip.upToTopParent(function  { this.thisElement.hideRef; });           }          })          .click(function  {            tooltip.clickedTime = $.now;          }); }

// Tooltip tail element is inside tooltip content element in order for the tooltip // not to disappear when the mouse is above the tail this.$tail = $(' ') .addClass('rt-tooltipTail') .prependTo(this.$element);

this.disappearing = false;

this.show = function { console.log('show') this.disappearing = false; clearTimeout(this.thisElement.hideTimer); clearTimeout(this.thisElement.removeTimer);

this.$element .removeClass(CLASSES.FADE_OUT_DOWN) .removeClass(CLASSES.FADE_OUT_UP);

if (!this.isPresent) { $body.append(this.$element); }

this.isPresent = true; };

this.hide = function { var tooltip = this;

tooltip.disappearing = true;

if (tooltip.$element.hasClass('rt-tooltip-above')) { tooltip.$element .removeClass(CLASSES.FADE_IN_DOWN) .addClass(CLASSES.FADE_OUT_UP); } else { tooltip.$element .removeClass(CLASSES.FADE_IN_UP) .addClass(CLASSES.FADE_OUT_DOWN); }

tooltip.thisElement.removeTimer = setTimeout(function {          if (tooltip.isPresent) {            tooltip.$element.detach;

tooltip.$tail.css('left', '');

if (activatedByClick) { $body.off('click.rt touchstart.rt', tooltip.thisElement.onBodyClick); }           $window.off('resize.rt', tooltip.thisElement.onWindowResize);

tooltip.isPresent = false; }       }, 200);      };

this.calculatePosition = function (ePageX, ePageY) { var teElement, teOffsets, teOffset, tooltipTailOffsetX, tooltipTailLeft, offsetYCorrection = 0;

this.$tail.css('left', '');

teElement = this.thisElement.$element.get(0); if (ePageX !== undefined) { tooltipTailOffsetX = ePageX; teOffsets = teElement.getClientRects && teElement.getClientRects || teElement.getBoundingClientRect; if (teOffsets.length > 1) { for (var i = teOffsets.length - 1; i >= 0; i--) { if (ePageY >= Math.round($window.scrollTop + teOffsets[i].top) &&               ePageY <= Math.round( $window.scrollTop + teOffsets[i].top + teOffsets[i].height )             ) {                teOffset = teOffsets[i]; }           }          }        }

if (!teOffset) { teOffset = teElement.getClientRects && teElement.getClientRects[0] || teElement.getBoundingClientRect; }       teOffset = { top: $window.scrollTop + teOffset.top, left: $window.scrollLeft + teOffset.left, width: teOffset.width, height: teOffset.height };       if (!tooltipTailOffsetX) { tooltipTailOffsetX = (teOffset.left * 2 + teOffset.width) / 2; }       if (CLIENT_NAME === 'msie' && this.thisElement.type === 'supRef') { offsetYCorrection = -Number(           this.thisElement.$element.parent.css('font-size').replace('px', '')          ) / 2; }       this.$element.css({          top: teOffset.top - this.$element.outerHeight - 7 + offsetYCorrection,          left: tooltipTailOffsetX - 20,          right: ''        });

// Is it squished against the right side of the page? if (this.$element.offset.left + this.$element.outerWidth > $window.width - 1) { this.$element.css({           left: '',            right: 0          }); tooltipTailLeft = tooltipTailOffsetX - this.$element.offset.left - 5; }

// Is a part of it above the top of the screen? if (teOffset.top < this.$element.outerHeight + $window.scrollTop + 6) { this.$element .removeClass('rt-tooltip-above') .addClass('rt-tooltip-below') .addClass(CLASSES.FADE_IN_UP) .css({             top: teOffset.top + teOffset.height + 9 + offsetYCorrection            }); if (tooltipTailLeft) { this.$tail.css('left', (tooltipTailLeft + 12) + 'px'); }       } else { this.$element .removeClass('rt-tooltip-below') .addClass('rt-tooltip-above') .addClass(CLASSES.FADE_IN_DOWN) // A fix for cases when a tooltip shown once is then wrongly positioned when it           // is shown again after a window resize. We just repeat what is above. .css({             top: teOffset.top - this.$element.outerHeight - 7 + offsetYCorrection            }); if (tooltipTailLeft) { // 12 is the tail element width/height this.$tail.css('left', tooltipTailLeft + 'px'); }       }      };

// Run some function for all the tooltips up to the top one in a tree. Its context will be     // the tooltip, while its parameters may be passed to Tooltip.upToTopParent as an array // in the second parameter. If the third parameter passed to ToolTip.upToTopParent is true, // the execution stops when the function in question returns true for the first time, // and ToolTip.upToTopParent returns true as well. this.upToTopParent = function (func, parameters, stopAtTrue) { var returnValue, currentTooltip = this;

do { returnValue = func.apply(currentTooltip, parameters); if (stopAtTrue && returnValue) { break; }       } while (currentTooltip = currentTooltip.parent);

if (stopAtTrue) { return returnValue; }     };    }

if (!enabled) { addEnableLink; return; }

teSelector = REF_LINK_SELECTOR; $content.find(teSelector).each(function {      new TooltippedElement($(this));    }); }

settingsString = mw.cookie.get('RTsettings', ''); if (settingsString) { settings = settingsString.split('|'); enabled = Boolean(Number(settings[0])); delay = Number(settings[1]); activatedByClick = Boolean(Number(settings[2])); // The forth value was added later, so we provide for a default value. See comments below // for why we use "IS_TOUCHSCREEN && IS_MOBILE". tooltipsForComments = settings[3] === undefined ? IS_TOUCHSCREEN && IS_MOBILE : Boolean(Number(settings[3])); } else { enabled = true; delay = 200; // Since the mobile browser check is error-prone, adding IS_MOBILE condition here would probably // leave cases where a user interacting with the browser using touches doesn't know how to call // a tooltip in order to switch to activation by click. Some touch-supporting laptop users // interacting by touch (though probably not the most popular use case) would not be happy too. activatedByClick = IS_TOUCHSCREEN; // Arguably we shouldn't convert native tooltips into gadget tooltips for devices that have // mouse support, even if they have touchscreens (there are laptops with touchscreens). // IS_TOUCHSCREEN check here is for reliability, since the mobile check is prone to false // positives. tooltipsForComments = IS_TOUCHSCREEN && IS_MOBILE; }

rt($('.mw-body'))

})