MediaWiki:Gadget-tooltipRef.js

De wikimerdja
Sauter à la navigation Sauter à la recherche

Note : après avoir enregistré vos modifications, il se peut que vous deviez forcer le rechargement complet du cache de votre navigateur pour voir les changements.

  • Firefox / Safari : maintenez la touche Maj (Shift) en cliquant sur le bouton Actualiser ou pressez Ctrl-F5 ou Ctrl-R (⌘-R sur un Mac)
  • Google Chrome : appuyez sur Ctrl-Maj-R (⌘-Shift-R sur un Mac)
  • Internet Explorer : maintenez la touche Ctrl en cliquant sur le bouton Actualiser ou pressez Ctrl-F5
  • Opera : allez dans Menu → Settings (Opera → Préférences sur un Mac) et ensuite à Confidentialité & sécurité → Effacer les données d’exploration → Images et fichiers en cache.
// 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:not(.cite_virgule), a[href^="#CITEREF"]',
            REF_JUMP_UP_SELECTOR = '.mw-cite-backlink';

        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 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,
            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 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( REF_LINK_SELECTOR ).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(
                                            // Don't remove the "lang" and "skin" parameters, they may be mandatory in the future: https://github.com/wikimedia/mediawiki/commit/fa05976
                                            $( '<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;
        
                function filterNoteContents( i, elm ) {
                    var $elm = $( elm );
                    if ( elm.nodeType === Node.TEXT_NODE ) {
                        return true;
                    } else if ( $elm.is( REF_JUMP_UP_SELECTOR ) ) {
                        return false;
                    } else if (
                        // Template:Note_label
                        i === 0 &&
                        $elm.is( 'a' ) &&
                        $elm.attr( 'href' ).indexOf( '#ref' ) === 0
                    ) {
                        return false;
                    }
                    return true;
                }
        
                switch ( this.te.type ) {
                    case 'supRef':
                        this.id = 'rt-' + this.te.$originalElement.attr( 'id' );
                        this.$content = this.te.$ref
                            .contents()
                            .filter( filterNoteContents )
                            .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;
            }
        
            $content.find( REF_LINK_SELECTOR ).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 mobile browser checks are error-prone, adding an 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 );
        
    }() );
}