Aller au contenu

MediaWiki:Gadget-ReferenceTooltips.js

Une page de Wikipédia, l'encyclopédie libre.
Ceci est une version archivée de cette page, en date du 30 août 2019 à 05:40 et modifiée en dernier par Od1n (discuter | contributions) (suite à discussion, réémigration de la fonctionnalité "tooltips aussi sur les abbr" ; admirez tout le code que cela permet de retirer, et il en reste encore un peu). Elle peut contenir des erreurs, des inexactitudes ou des contenus vandalisés non présents dans la version actuelle.
Note : après avoir enregistré la page, vous devrez forcer le rechargement complet du cache de votre navigateur pour voir les changements.

Mozilla / Firefox / Konqueror / Safari : maintenez la touche Majuscule (Shift) en cliquant sur le bouton Actualiser (Reload) ou pressez Maj-Ctrl-R (Cmd-R sur Apple Mac) ;

Firefox (sur GNU/Linux) / Chrome / Internet Explorer / Opera : maintenez la touche Ctrl en cliquant sur le bouton Actualiser ou pressez Ctrl-F5.
// See [[mw:Reference Tooltips]]
// Source https://en.wikipedia.org/wiki/MediaWiki:Gadget-ReferenceTooltips.js

window.referenceTooltipsEnabled = true;

if( typeof tooltipRefEnabled === 'undefined' ) {
    ( function () {
        // Settings
        var REF_LINK_SELECTOR = '.reference, a[href^="#CITEREF"]',
            REF_JUMP_UP_SELECTOR = '.renvois_vers_le_texte';

        mw.messages.set({
            'rt-settings': 'Paramètres des infobulles des références',
            'rt-enable-footer': 'Activer les infobulles des références',
            'rt-settings-title': 'Gadget ReferenceTooltips',
            'rt-save': 'Enregistrer',
            'rt-cancel': 'Annuler',
            'rt-enable': 'Activer',
            'rt-disable': 'Désactiver',
            'rt-activationMethod': 'L’infobulle doit apparaître au :',
            'rt-hovering': 'survol',
            'rt-clicking': 'clic',
            'rt-delay': 'Délai avant que l’infobulle n’apparaisse (en millisecondes) :',
            'rt-disabledNote': 'Vous pouvez réactiver les infobulles en cliquant sur le lien «\u00a0Activer les infobulles des références\u00a0» en bas de la page.',
            'rt-done': 'Terminer',
            'rt-enabled': 'Le gadget ReferenceTooltips est activé.'
        });
        
        // "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, 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 ),
                    { 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 ).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 events,
                    te = this;
        
                function onStartEvent( e ) {
                    var showRefArgs;
        
                    if ( activatedByClick && 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.prop( 'tagName' ) === 'SUP' ) {
                        this.type = 'supRef';
                    } else {
                        this.type = 'harvardRef';
                    }
                
                if ( activatedByClick ) {
                    events = {
                        'click.rt': onStartEvent
                    };
                } else {
                    events = {
                        'mouseenter.rt': onStartEvent,
                        'mouseleave.rt': onEndEvent
                    };
                }
        
                this.$element.on( events );
        
                this.hideRef = function ( immediately ) {
                    clearTimeout( te.showTimer );
        
                    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 ( 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 );
                            } else {
                                dialog.activationMethodSelect.setDisabled( false );
                                dialog.delayInput.setDisabled( dialog.clickOption.isSelected() );
                            }
                        } );
        
                        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.fieldset = new OO.ui.FieldsetLayout();
                        this.fieldset.addItems( [
                            this.activationMethodField,
                            this.delayField
                        ] );
        
                        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', mw.config.get( 'wgServer' ) + mw.config.get( 'wgScriptPath' ) + '/load.php?modules=ext.popups.images&image=footer&lang=fr&skin=fallback' )
                                        ),
                                        $( '<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();
        
                                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( REF_JUMP_UP_SELECTOR ) ||
                                        ( 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;
                }
                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;
            $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 ] ) );
        } 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;
        }
        
        mw.hook( 'wikipage.content' ).add( rt );
        
    }() );
}