MediaWiki:Common.js

From JoJo's Bizarre Encyclopedia - JoJo Wiki
Revision as of 18:18, 29 July 2020 by Vish (talk | contribs)
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/* Any JavaScript here will be loaded for all users on every page load. */
//<tabber> extension req
//v2.0, 2017, user:fngplg.
//set active tab: https://jojowiki.com/page#activeTab
(function ($){
    var nstarget = window.location.hash.replace('#', '');
    if (nstarget === '') return;
    //convert wiki-utf 2 ansi
    nstarget = nstarget.replace(/\./g, '%');
    nstarget = decodeURIComponent(nstarget).replace(/_/g, ' ');
    //console.log('trgt:'+nstarget);
    $(function(){
        setTimeout(function() {
            var $nt2a = $('.tabberlive>.tabbernav>Li>a[title="' + nstarget + '"]');
            $nt2a.click();
            $nt2a.get(0).scrollIntoView({inline: "nearest"});
        }, 100);//settimeout
    });//doc.rdy    
})(jQuery);

/* [[QuickDiff]] - quickly view any diff link */
 
/*jslint browser, long */
/*global jQuery, mediaWiki, dev */
 
(function ($, mw) {
    "use strict";
 
    // double-run protection
    if (window.quickDiffLoaded) {
        return;
    }
    window.quickDiffLoaded = true;
 
 
    var diffStylesModule = "mediawiki.action.history.diff";
    var i18n;
    var modal;
    var special = {};
 
    // "Special:Diff/12345" and "Special:ComparePages" link detection
    function initSpecialPageStrings() {
        special.diffDefault = mw.util.getUrl("Special:Diff/");
        special.compareDefault = mw.util.getUrl("Special:ComparePages");
 
        var wiki = mw.config.get("wgDBname");
        var storageKeyDiff = "QuickDiff-specialdiff_" + wiki;
        var storageKeyCompare = "QuickDiff-specialcompare_" + wiki;
 
        try {
            special.diff = localStorage.getItem(storageKeyDiff);
            special.compare = localStorage.getItem(storageKeyCompare);
        } catch (ignore) {}
 
        if (special.diff && special.compare) {
            // using stored values - no need for api request
            return;
        }
 
        $.getJSON(mw.util.wikiScript("api"), {
            action: "parse",
            format: "json",
            prop: "text",
            text: "<span class='diff'>[[Special:Diff/]]</span><span class='compare'>[[Special:ComparePages]]</span>",
            disablepp: "" // note: deprecated in MW 1.26, but needed for older versions
        }).done(function (data) {
            var $parsed = $(data.parse.text["*"]);
 
            special.diff = $parsed.find(".diff > a").attr("href");
            special.compare = $parsed.find(".compare > a").attr("href");
 
            try {
                localStorage.setItem(storageKeyDiff, special.diff);
                localStorage.setItem(storageKeyCompare, special.compare);
            } catch (ignore) {}
        });
    }
 
    function getDiffTitle($diff) {
        var prevTitle = $diff.find("#mw-diff-otitle1 a").attr("title");
        var currTitle = $diff.find("#mw-diff-ntitle1 a").attr("title");
 
        if (prevTitle && prevTitle !== currTitle) {
            return i18n("differences-multipage", prevTitle, currTitle).plain();
        }
 
        return i18n("differences", currTitle).plain();
    }
 
    function loadDiff(url) {
        modal.show({
            loading: true,
            title: !modal.visible && i18n("loading").plain()
        });
 
        // add 'action=render' and 'diffonly' params to save some bytes on each request
        url.extend({
            action: "render",
            diffonly: "1"
        });
 
        // pass through 'bot' param for rollback links if it's in use on the current page
        if (mw.util.getParamValue("bot")) {
            url.extend({bot: "1"});
        }
 
        $.when(
            $.get(url.getRelativePath()),
            mw.loader.using(diffStylesModule)
        ).always(function (response) {
            delete url.query.action;
            delete url.query.diffonly;
            delete url.query.bot;
 
            var data = {
                buttons: [{
                    text: i18n("link").plain(),
                    href: url.toString(),
                    attr: {"data-disable-quickdiff": ""}
                }]
            };
            var $diff;
 
            if (typeof response[0] === "string") {
                var $content = $(response[0]);
                $diff = $content.filter("table.diff, #mw-rev-deleted-no-diff");
 
                if (!$diff.length) {
                    // $content is a complete page - see if a diff can be found
                    // needed for diffs from special pages as they ignore action=render URL parameter
                    $diff = $content.find("table.diff");
                }
            }
 
            if ($diff && $diff.length) {
                data.content = $diff;
                data.hook = "quickdiff.ready";
                data.title = getDiffTitle($diff);
            } else {
                data.content = i18n("error", url.toString()).escape();
            }
 
            modal.show(data);
        });
    }
 
    function linkClickHandler(event) {
        // ignore clicks with modifier keys to avoid overriding browser features
        if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
            return;
        }
 
        // ignore click if link has "data-disable-quickdiff" attribute set
        if (event.currentTarget.dataset.disableQuickdiff !== undefined) {
            return;
        }
 
        var url = event.currentTarget.href;
 
        try {
            url = new mw.Uri(url);
        } catch (ignore) {
            // quit if url couldn't be parsed
            // it wouldn't be a link QuickDiff could handle anyway
            return;
        }
 
        // cross-domain requests not supported
        if (url.host !== location.hostname) {
            return;
        }
 
        // no fragment check is to ensure section links/collapsible trigger links on diff pages are ignored
        var hasDiffParam = url.query.diff !== undefined
                && url.fragment === undefined;
        var isSpecialDiffLink = url.path.indexOf(special.diff) === 0
                || url.path.indexOf(special.diffDefault) === 0;
        var isSpecialCompareLink = url.path.indexOf(special.compare) === 0
                || url.path.indexOf(special.compareDefault) === 0;
 
        if (hasDiffParam || isSpecialDiffLink || isSpecialCompareLink) {
            event.preventDefault();
            loadDiff(url);
        }
    }
 
    function init() {
        modal = new mw.libs.QDmodal("quickdiff-modal");
 
        // full screen modal
        mw.util.addCSS("#quickdiff-modal { height: 100%; width: 100% }");
 
        // diff styles module was renamed in MW 1.28
        if (mw.loader.getState("mediawiki.diff.styles")) {
            diffStylesModule = "mediawiki.diff.styles";
        }
 
        // attach to body for compatibility with ajax-loaded content
        // also, one attached event handler is better than hundreds!
        $(document.body).on("click.quickdiff", "a[href]", linkClickHandler);
 
        initSpecialPageStrings();
    }
 
    function initDependencies() {
    	var loadURL = "/index.php?title=";
        var i18nMsgs = new $.Deferred();
        var waitFor = [
            i18nMsgs,
            mw.loader.using(["mediawiki.Uri", "mediawiki.util"])
        ];
 
        if (!(mw.libs.QDmodal && mw.libs.QDmodal.version >= 20180212)) {
            waitFor.push($.ajax({
                cache: true,
                dataType: "script",
                url: loadURL + "MediaWiki:QDmodal.js&action=raw&ctype=text/javascript"
            }));
        }
 
        if (!(window.dev && dev.i18n && dev.i18n.loadMessages)) {
            mw.loader.load(loadURL + "MediaWiki:I18n-js/code.js&action=raw&ctype=text/javascript");
        }
 
        mw.hook("dev.i18n").add(function (i18njs) {
            i18njs.loadMessages("QuickDiff").done(function (i18nData) {
                i18n = i18nData.msg;
                i18nMsgs.resolve();
            });
        });
 
        $.when.apply($, waitFor).done(init);
    }
 
    initDependencies();
 
 
    // collect action links (edit, undo, rollback, patrol) and add them to footer
    mw.hook("quickdiff.ready").add(function (modal) {
        // edit/undo links use "mw-rev-head-action" class on Wikia,
        // and "mw-diff-edit" or "-undo" class on MW 1.24+
        var $buttons = modal.$content.find(".diff-ntitle").find(
            ".mw-rev-head-action, .mw-diff-edit, .mw-diff-undo, .mw-rollback-link, .patrollink"
        ).clone();
 
        // remove text nodes (the brackets around each link)
        $buttons.contents().filter(function (ignore, element) {
            return element.nodeType === 3;
        }).remove();
 
        $buttons.find("a")
            .addClass("qdmodal-button")
            .attr("target", "_blank");
 
        modal.$footer.append($buttons);
    });
}(jQuery, mediaWiki));

window.hotcat_use_category_links = false;

/**
 * @source https://www.mediawiki.org/wiki/Snippets/Open_specific_links_in_new_window
 * @version 2018-09-15
 */
$( function () {
	$( '#mw-content-text' ).on( 'click', '.newwin > a', function () {
		var otherWindow = window.open();
		otherWindow.opener = null;
		otherWindow.location = this;
		return false;
	} );
} );

// Expand all button
var state =1;
$(function() {
   $('#collapse-global').html($('<a>', {
       'class': 'mw-ui-button',
       text: 'Expand/Collapse All'
   })).click(function() {
       if(state ===0){
       $('.mw-collapsible-toggle-expanded').click();
       state = 1;
       }
       else {
           $('.mw-collapsible-toggle-collapsed').click();
           state = 0;
       }
      }
)});

// Refresh Button
$(function() {
   $('.mw-refreshButton').html($('<a>', {
       'class': 'mw-ui-button',
       text: 'Refresh'
   })).click(function() {
		location.reload();
		window.scrollTo(0, element.offsetTop);
    }
)});

// English Cast Button
$(function() {
   $('.mw-englishButton').html($('<a>', {
       'class': 'mw-ui-button',
       text: 'English Cast'
   })).click(function() {
       window.location = (""+window.location).replace(/#[A-Za-z0-9_]*$/,'')+"#ENG";
    }
)});

// Japanese Cast Button
$(function() {
   $('.mw-japaneseButton').html($('<a>', {
       'class': 'mw-ui-button',
       text: 'Japanese Cast'
   })).click(function() {
       window.location = (""+window.location).replace(/#[A-Za-z0-9_]*$/,'')+"#JPN";
    }
)});

// Spanish Cast Button
$(function() {
   $('.mw-spanishButton').html($('<a>', {
       'class': 'mw-ui-button',
       text: 'Spanish Cast'
   })).click(function() {
       window.location = (""+window.location).replace(/#[A-Za-z0-9_]*$/,'')+"#SPA";
    }
)});

// French Cast Button
$(function() {
   $('.mw-frenchButton').html($('<a>', {
       'class': 'mw-ui-button',
       text: 'French Cast'
   })).click(function() {
       window.location = (""+window.location).replace(/#[A-Za-z0-9_]*$/,'')+"#FR";
    }
)});

// Italian Cast Button
$(function() {
   $('.mw-italianButton').html($('<a>', {
       'class': 'mw-ui-button',
       text: 'Italian Cast'
   })).click(function() {
       window.location = (""+window.location).replace(/#[A-Za-z0-9_]*$/,'')+"#ITA";
    }
)});

// See [[mw:Reference Tooltips]]
// Source https://en.wikipedia.org/wiki/MediaWiki:Gadget-ReferenceTooltips.js

( function () {

// enwiki settings
var REF_LINK_SELECTOR = '.reference, a[href^="#CITEREF"]',
	COMMENTED_TEXT_CLASS = 'rt-commentedText',
	COMMENTED_TEXT_SELECTOR = ( COMMENTED_TEXT_CLASS ? '.' + COMMENTED_TEXT_CLASS + ', ' : '') +
		'abbr[title]';

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-tooltipsForComments': 'Show tooltips over <span title="Tooltip example" class="' + ( COMMENTED_TEXT_CLASS || 'rt-commentedText' ) + '" style="border-bottom: 1px dotted; cursor: help;">text with a dotted underline</span> in Reference Tooltips style (allows to see such tooltips on devices with no mouse support)',
	'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,
		settingsDialogOpening = false;

	function setSettingsCookie() {
		mw.cookie.set(
			'RTsettings',
			Number( enabled ) + '|' + delay + '|' + Number( activatedByClick ) + '|' +
				Number( tooltipsForComments ),
			{ path: '/', expires: 90 * SECONDS_IN_A_DAY, prefix: '' }
		);
	}

	function enableRt() {
		enabled = true;
		setSettingsCookie();
		$( '.rt-enableItem' ).remove();
		rt( $content );
		mw.notify( mw.msg( 'rt-enabled' ) );
	}

	function disableRt() {
		$content.find( teSelector ).removeClass( 'rt-commentedText' ).off( '.rt' );
		$body.off( '.rt' );
		$window.off( '.rt' );
	}

	function addEnableLink() {
		// #footer-places – Vector
		// #f-list – Timeless, Monobook, Modern
		// parent of #footer li – Cologne Blue
		var $footer = $( '#footer-places, #f-list' );
		if ( !$footer.length ) {
			$footer = $( '#footer li' ).parent();
		}
		$footer.append(
			$( '<li>' )
				.addClass( 'rt-enableItem' )
				.append(
					$( '<a>' )
						.text( mw.msg( 'rt-enable-footer' ) )
						.attr( 'href', 'javascript:' )
						.click( function ( e ) {
							e.preventDefault();
							enableRt();
						} )
			)
		);
	}

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

		function onStartEvent( e ) {
			var showRefArgs;

			if ( activatedByClick && te.type !== 'commentedText' && e.type !== 'contextmenu' ) {
				e.preventDefault();
			}
			if ( !te.noRef ) {
				showRefArgs = [ $( this ) ];
				if ( te.type !== 'supRef' ) {
					showRefArgs.push( e.pageX, e.pageY );
				}
				te.showRef.apply( te, showRefArgs );
			}
		}

		function onEndEvent() {
			if ( !te.noRef ) {
				te.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 ( this.$element.is( REF_LINK_SELECTOR ) ) {
			if ( this.$element.prop( 'tagName' ) === 'SUP' ) {
				this.type = 'supRef';
			} else {
				this.type = 'harvardRef';
			}
		} else {
			this.type = 'commentedText';
			this.comment = this.$element.attr( 'title' );
			if ( !this.comment ) {
				return;
			}
			this.$element.addClass('rt-commentedText');
		}
		
		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( te.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 () {
						te.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() {
				var viewportTop, refOffsetTop, teHref;

				if ( !te.$ref && !te.comment ) {
					teHref = te.type === 'supRef' ?
						te.$element.find( 'a' ).attr( 'href' ) :
						te.$element.attr( 'href' ); // harvardRef
					te.$ref = teHref &&
						$( '#' + $.escapeSelector( teHref.slice( 1 ) ) );
					if ( !te.$ref || !te.$ref.length || !te.$ref.text() ) {
						te.noRef = true;
						return;
					}
				}

				if ( !tooltipInitiallyPresent && !te.comment ) {
					viewportTop = $window.scrollTop();
					refOffsetTop = te.$ref.offset().top;
					if ( !activatedByClick &&
						viewportTop < refOffsetTop &&
						viewportTop + $window.height() > refOffsetTop + te.$ref.height() &&
						// There can be gadgets/scripts that make references horizontally scrollable.
						$window.width() > te.$ref.offset().left + te.$ref.width()
					) {
						// Highlight the reference itself
						te.$ref.addClass( 'rt-target' );
						return;
					}
				}

				if ( !te.tooltip ) {
					te.tooltip = new Tooltip( te );
					if ( !te.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.
				te.tooltip.parent = te.$element.closest( '.rt-tooltip' ).data( 'tooltip' );
				if ( te.tooltip.parent && te.tooltip.parent.disappearing ) {
					return;
				}

				te.tooltip.show();

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

				te.tooltip.calculatePosition( ePageX, ePageY );

				$window.on( 'resize.rt', te.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', te.onBodyClick );
					}, 0 );
				}
			}

			if ( activatedByClick || tooltipInitiallyPresent ) {
				reallyShow();
			} else {
				this.showTimer = setTimeout( reallyShow, delay );
			}
		};

		this.onBodyClick = function ( e ) {
			if ( !te.tooltip && !te.$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, [ te.tooltip ],
						true
					)
				)
			) {
				$current = $current.parent();
			}
			if ( !$current.length ) {
				te.hideRef();
			}
		};

		this.onWindowResize = function () {
			te.tooltip.calculatePosition();
		};
	}

	function Tooltip( te ) {
		function openSettingsDialog() {
			var settingsDialog, settingsWindow;

			if ( cursorWaitCss ) {
				cursorWaitCss.disabled = true;
			}

			function SettingsDialog() {
				SettingsDialog.parent.call( this );
			}
			OO.inheritClass( SettingsDialog, OO.ui.ProcessDialog );

			SettingsDialog.static.name = 'settingsDialog';
			SettingsDialog.static.title = mw.msg( 'rt-settings-title' );
			SettingsDialog.static.actions = [
				{
					modes: 'basic',
					action: 'save',
					label: mw.msg( 'rt-save' ),
					flags: [ 'primary', 'progressive' ]
				},
				{
					modes: 'basic',
					label: mw.msg( 'rt-cancel' ),
					flags: 'safe'
				},
				{
					modes: 'disabled',
					action: 'deactivated',
					label: mw.msg( 'rt-done' ),
					flags: [ 'primary', 'progressive' ]
				}
			];

			SettingsDialog.prototype.initialize = function () {
				var dialog = this;

				SettingsDialog.parent.prototype.initialize.apply( this, arguments );

				this.enableOption = new OO.ui.RadioOptionWidget( {
					label: mw.msg( 'rt-enable' )
				} );
				this.disableOption = new OO.ui.RadioOptionWidget( {
					label: mw.msg( 'rt-disable' )
				} );
				this.enableSelect = new OO.ui.RadioSelectWidget( {
					items: [ this.enableOption, this.disableOption ],
					classes: [ 'rt-enableSelect' ]
				} );
				this.enableSelect.selectItem( this.enableOption );
				this.enableSelect.on( 'choose', function ( item ) {
					if ( item === dialog.disableOption ) {
						dialog.activationMethodSelect.setDisabled( true );
						dialog.delayInput.setDisabled( true );
						dialog.tooltipsForCommentsCheckbox.setDisabled( true );
					} else {
						dialog.activationMethodSelect.setDisabled( false );
						dialog.delayInput.setDisabled( dialog.clickOption.isSelected() );
						dialog.tooltipsForCommentsCheckbox.setDisabled( false );
					}
				} );

				this.hoverOption = new OO.ui.RadioOptionWidget( {
					label: mw.msg( 'rt-hovering' )
				} );
				this.clickOption = new OO.ui.RadioOptionWidget( {
					label: mw.msg( 'rt-clicking' )
				} );
				this.activationMethodSelect = new OO.ui.RadioSelectWidget( {
					items: [ this.hoverOption, this.clickOption ]
				} );
				this.activationMethodSelect.selectItem( activatedByClick ?
					this.clickOption :
					this.hoverOption
				);
				this.activationMethodSelect.on( 'choose', function ( item ) {
					if ( item === dialog.clickOption ) {
						dialog.delayInput.setDisabled( true );
					} else {
						dialog.delayInput.setDisabled( dialog.clickOption.isSelected() );
					}
				} );
				this.activationMethodField = new OO.ui.FieldLayout( this.activationMethodSelect, {
					label: mw.msg( 'rt-activationMethod' ),
					align: 'top'
				} );

				this.delayInput = new OO.ui.NumberInputWidget( {
					input: { value: delay },
					step: 50,
					min: 0,
					max: 5000,
					disabled: activatedByClick,
					classes: [ 'rt-numberInput' ]
				} );
				this.delayField = new OO.ui.FieldLayout( this.delayInput, {
					label: mw.msg( 'rt-delay' ),
					align: 'top'
				} );

				this.tooltipsForCommentsCheckbox = new OO.ui.CheckboxInputWidget( {
					selected: tooltipsForComments
				} );
				this.tooltipsForCommentsField = new OO.ui.FieldLayout(
					this.tooltipsForCommentsCheckbox,
					{
						label: new OO.ui.HtmlSnippet( mw.msg( 'rt-tooltipsForComments' ) ),
						align: 'inline',
						classes: [ 'rt-tooltipsForCommentsField' ]
					}
				);
				new TooltippedElement(
					this.tooltipsForCommentsField.$element.find(
						'.' + ( COMMENTED_TEXT_CLASS || 'rt-commentedText' )
					)
				);

				this.fieldset = new OO.ui.FieldsetLayout();
				this.fieldset.addItems( [
					this.activationMethodField,
					this.delayField,
					this.tooltipsForCommentsField
				] );

				this.panelSettings = new OO.ui.PanelLayout( {
					padded: true,
					expanded: false
				} );
				this.panelSettings.$element.append(
					this.enableSelect.$element,
					$( '<hr>' ).addClass( 'rt-settingsFormSeparator' ),
					this.fieldset.$element
				);

				this.panelDisabled = new OO.ui.PanelLayout( {
					padded: true,
					expanded: false
				} );
				this.panelDisabled.$element.append(
					$( '<table>' )
						.addClass( 'rt-disabledHelp' )
						.append(
							$( '<tr>' ).append(
								$( '<td>' ).append(
									$( '<img>' ).attr( 'src', 'https://en.wikipedia.org/w/load.php?modules=ext.popups.images&image=footer&format=rasterized&lang=ru&skin=vector&version=0uotisb' )
								),
								$( '<td>' )
									.addClass( 'rt-disabledNote' )
									.text( mw.msg( 'rt-disabledNote' ) )
							)
						)
				);

				this.stackLayout = new OO.ui.StackLayout( {
					items: [ this.panelSettings, this.panelDisabled ]
				} );

				this.$body.append( this.stackLayout.$element );
			};

			SettingsDialog.prototype.getSetupProcess = function ( data ) {
				return SettingsDialog.parent.prototype.getSetupProcess.call( this, data )
					.next( function () {
						this.stackLayout.setItem( this.panelSettings );
						this.actions.setMode( 'basic' );
					}, this );
			};

			SettingsDialog.prototype.getActionProcess = function ( action ) {
				var dialog = this;

				if ( action === 'save' ) {
					return new OO.ui.Process( function () {
						var newDelay = Number( dialog.delayInput.getValue() );

						enabled = dialog.enableOption.isSelected();
						if ( newDelay >= 0 && newDelay <= 5000 ) {
							delay = newDelay;
						}
						activatedByClick = dialog.clickOption.isSelected();
						tooltipsForComments = dialog.tooltipsForCommentsCheckbox.isSelected();

						setSettingsCookie();

						if ( enabled ) {
							dialog.close();
							disableRt();
							rt( $content );
						} else {
							dialog.actions.setMode( 'disabled' );
							dialog.stackLayout.setItem( dialog.panelDisabled );
							disableRt();
							addEnableLink();
						}
					} );
				} else if ( action === 'deactivated' ) {
					dialog.close();
				}
				return SettingsDialog.parent.prototype.getActionProcess.call( this, action );
			};

			SettingsDialog.prototype.getBodyHeight = function () {
				return this.stackLayout.getCurrentItem().$element.outerHeight( true );
			};

			tooltip.upToTopParent( function adjustRightAndHide() {
				if ( this.isPresent ) {
					if ( this.$element[ 0 ].style.right ) {
						this.$element.css(
							'right',
							'+=' + ( window.innerWidth - $window.width() )
						);
					}
					this.te.hideRef( true );
				}
			} );

			if ( !windowManager ) {
				windowManager = new OO.ui.WindowManager();
				$body.append( windowManager.$element );
			}

			settingsDialog = new SettingsDialog();
			windowManager.addWindows( [ settingsDialog ] );
			settingsWindow = windowManager.openWindow( settingsDialog );
			settingsWindow.opened.then( function () {
				settingsDialogOpening = false;
			} );
			settingsWindow.closed.then( function () {
				windowManager.clearWindows();
			} );
		}

		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.te = te;

		switch ( this.te.type ) {
			case 'supRef':
				this.id = 'rt-' + this.te.$originalElement.attr( 'id' );
				this.$content = this.te.$ref
					.contents()
					.filter( function ( i ) {
						var $this = $( this );
						return this.nodeType === Node.TEXT_NODE ||
							!( $this.is( '.mw-cite-backlink' ) ||
								( i === 0 &&
									// Template:Cnote, Template:Note
									( $this.is( 'b' ) ||
										// Template:Note_label
										$this.is( 'a' ) &&
										$this.attr( 'href' ).indexOf( '#ref' ) === 0
									)
								)
							);
					} )
					.clone( true );
				break;
			case 'harvardRef':
				this.id = 'rt-' + this.te.$originalElement.closest( 'li' ).attr( 'id' );
				this.$content = this.te.$ref
					.clone( true )
					.removeAttr( 'id' );
				break;
			case 'commentedText':
				this.id = 'rt-' + String( Math.random() ).slice( 2 );
				this.$content = $( document.createTextNode( this.te.comment ) );
				break;
		}
		if ( !this.$content.length ) {
			return;
		}

		this.insideWindow = Boolean( this.te.$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.te.hideRef();
						} );
					}
				} )
				.click( function () {
					tooltip.clickedTime = $.now();
				} );
		}

		if ( !this.insideWindow ) {
			$( '<div>' )
				.addClass( 'rt-settingsLink' )
				.attr( 'title', mw.msg( 'rt-settings' ) )
				.click( function () {
					if ( settingsDialogOpening ) {
						return;
					}
					settingsDialogOpening = true;

					if ( mw.loader.getState( 'oojs-ui' ) !== 'ready' ) {
						if ( cursorWaitCss ) {
							cursorWaitCss.disabled = false;
						} else {
							cursorWaitCss = mw.util.addCSS( 'body { cursor: wait; }' );
						}
					}
					mw.loader.using( [ 'oojs', 'oojs-ui' ], openSettingsDialog );
				} )
				.prependTo( this.$content );
		}

		// 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 () {
			this.disappearing = false;
			clearTimeout( this.te.hideTimer );
			clearTimeout( this.te.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.te.removeTimer = setTimeout( function () {
				if ( tooltip.isPresent ) {
					tooltip.$element.detach();
					
					tooltip.$tail.css( 'left', '' );

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

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

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

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

			teElement = this.te.$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.te.type === 'supRef' ) {
				offsetYCorrection = -Number(
					this.te.$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;
	if ( tooltipsForComments ) {
		teSelector += ', ' + COMMENTED_TEXT_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;
}

mw.hook( 'wikipage.content' ).add( rt );

}() );