/*
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 <a href="${help_page_url}">${help_page_title}</a>
<table class="wikitable">
<tr>${header}</tr>
${split.map(key => {
if(key ==' ') {
return '<tr><td colspan=10></td></tr>'
}
return `<tr>${IPA_keys[key] || `<td colspan=10>Missing data for "<b>${key}</b>"</td>`}</tr>`
}).join('')}
</table>
`
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 = $('<div>')
.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('<div>')
.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 = $('<div>')
.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'))
}())