User:Þjarkur/IPA popups.js
Appearance
Code that you insert on this page could contain malicious content capable of compromising your account. If you import a script from another page with "importScript", "mw.loader.load", "iusc", or "lusc", take note that this causes you to dynamically load a remote script, which could be changed by others. Editors are responsible for all edits and actions they perform, including by scripts. User scripts are not centrally supported and may malfunction or become inoperable due to software changes. A guide to help you find broken scripts is available. If you are unsure whether code you are adding to this page is safe, you can ask at the appropriate village pump. This code will be executed when previewing this page. |
This user script seems to have a documentation page at User:Þjarkur/IPA popups. |
/*
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'))
}())