×
Create a new article
Write your page title here:
We currently have 1,396 articles on World Trigger Wiki. Type your article name above or create one of the articles listed here!



    World Trigger Wiki

    MediaWiki:Cosmos.js: Difference between revisions

    Content added Content deleted
    mNo edit summary
    mNo edit summary
    Tag: Reverted
    Line 71: Line 71:
    }
    }
    });//end document ready
    });//end document ready

    ( function ( $, mw ) {
    'use strict';

    /**
    * @class mw.popups.render.article
    * @singleton
    */
    var article = {};

    /**
    * Size constants for popup images
    * @property SIZES
    */
    article.SIZES = {
    portraitImage: {
    h: 250, // Exact height
    w: 203 // Max width
    },
    landscapeImage: {
    h: 200, // Max height
    w: 300 // Exact Width
    },
    landscapePopupWidth: 450, // Exact width of a landscape popup
    portraitPopupWidth: 300, // Exact width of a portrait popup
    pokeySize: 8 // Height of the triangle used to point at the link
    };


    /**
    * Survey link, if any, for this renderer
    * @property surveyLink
    */
    article.surveyLink = mw.config.get( 'wgPopupsSurveyLink' );

    /**
    * Send an API request and cache the jQuery element
    *
    * @param {jQuery} link
    * @return {jQuery.Promise}
    */
    article.init = function ( link ) {
    var href = link.attr( 'href' ),
    title = mw.popups.getTitle( href ),
    deferred = $.Deferred();

    if ( !title ) {
    return deferred.reject().promise();
    }

    mw.popups.render.currentRequest = mw.popups.api.get( {
    action: 'query',
    prop: 'extracts|pageimages|revisions|info',
    redirects: 'true',
    exintro: 'true',
    exsentences: 2,
    // there is an added geometric limit on .mwe-popups-extract
    // so that text does not overflow from the card
    explaintext: 'true',
    piprop: 'thumbnail',
    pithumbsize: 300,
    rvprop: 'timestamp',
    inprop: 'watched',
    indexpageids: true,
    titles: title
    } );

    mw.popups.render.currentRequest.fail( deferred.reject );
    mw.popups.render.currentRequest.done( function ( re ) {
    mw.popups.render.currentRequest = undefined;

    /*if (
    !re.query ||
    !re.query.pages ||
    !re.query.pages[ re.query.pageids[ 0 ] ].extract ||
    re.query.pages[ re.query.pageids[ 0 ] ].extract === ''
    ) {
    deferred.reject();
    return;
    }*/

    mw.popups.render.cache[ href ] = {};
    mw.popups.render.cache[ href ].popup = article.createPopup( re, href );
    mw.popups.render.cache[ href ].getOffset = article.getOffset;
    mw.popups.render.cache[ href ].getClasses = article.getClasses;
    mw.popups.render.cache[ href ].process = article.processPopup;
    deferred.resolve();
    } );

    return deferred.promise();
    };

    /**
    * Returns a thumbnail object based on the ratio of the image
    * Uses an SVG image where available to add the triangle/pokey
    * mask on the image. Crops and resizes the SVG image so that
    * is fits inside a rectangle of a particular size.
    *
    * @method createPopup
    * @param {Object} re
    * @param {String} href
    * @return {jQuery}
    */
    article.createPopup = function ( re, href ) {
    var $div,
    page = re.query.pages[ re.query.pageids[ 0 ] ],
    $contentbox = $( '<a>' )
    .attr( 'href', href )
    .addClass( 'mwe-popups-extract' )
    .append(
    article.getProcessedElements( page.extract, page.title )
    ),
    thumbnail = page.thumbnail,
    tall = thumbnail && thumbnail.height > thumbnail.width,
    $thumbnail = article.createThumbnail( thumbnail, tall ),
    timestamp = new Date( page.revisions[ 0 ].timestamp ),
    timediff = new Date() - timestamp,
    oneDay = 1000 * 60 * 60 * 24,
    timestampclass = ( timediff < oneDay ) ?
    'mwe-popups-timestamp-recent' :
    'mwe-popups-timestamp-older',
    $settingsImage = $( '<a>' ).addClass( 'mwe-popups-icon mwe-popups-settings-icon' ),
    $surveyImage,
    $timestamp = $( '<div>' )
    .addClass( timestampclass )
    .append(
    $( '<span>' ).text( mw.message( 'popups-last-edited',
    moment( timestamp ).fromNow() ).text() ),
    $settingsImage
    );

    if ( article.surveyLink ) {
    $surveyImage = $( '<a>' )
    .attr( 'href', article.surveyLink )
    .attr( 'target', '_blank' )
    .attr( 'title', mw.message( 'popups-send-feedback' ) )
    .addClass( 'mwe-popups-icon mwe-popups-survey-icon' );
    $timestamp.append( $surveyImage );
    }


    if ( $thumbnail.prop( 'tagName' ) !== 'SPAN' ) {
    $thumbnail = $( '<a>' )
    .addClass( 'mwe-popups-discreet' )
    .attr( 'href', href )
    .append( $thumbnail );
    } else {
    tall = thumbnail = undefined;
    }

    $div = $( '<div>' ).append( $thumbnail, $contentbox, $timestamp );

    mw.popups.render.cache[ href ].settings = {
    'title': page.title,
    'tall': ( tall === undefined ) ? false : tall,
    'thumbnail': ( thumbnail === undefined ) ? false : thumbnail
    };

    return $div;
    };

    /**
    * Returns an array of elements to be appended after removing parentheses
    * and making the title in the extract bold.
    *
    * @method getProcessedElements
    * @param {String} extract Should be unescaped
    * @param {String} title Should be unescaped
    * @return {Array} of elements to appended
    */
    article.getProcessedElements = function ( extract, title ) {
    title = title.replace( /([.?*+^$[\]\\(){}|-])/g, '\\$1' ); // Escape RegExp elements

    var elements = [],
    regExp = new RegExp( '(^|\\s)(' + title + ')(|$)', 'ig' ),
    boldIdentifier = '<bi-' + Math.random() + '>',
    snip = '<snip-' + Math.random() + '>';

    // Remove text in parentheses along with the parentheses
    extract = article.removeParensFromText( extract );
    extract = extract.replace(/\s+/g, ' '); // Remove extra white spaces

    // Make title bold in the extract text
    // As the extract is html escaped there can be no such string in it
    // Also, the title is escaped of RegExp elements thus can't have "*"
    extract = extract.replace( regExp, '$1' + snip + boldIdentifier + '$2' + snip + '$3' );
    extract = extract.split( snip );

    $.each( extract, function ( index, part ) {
    if ( part.indexOf( boldIdentifier ) === 0 ) {
    elements.push( $( '<b>' ).text( part.substring( boldIdentifier.length ) ) );
    } else {
    elements.push( document.createTextNode( part ) );
    }
    } );

    return elements;
    };

    /**
    * Removes content in parentheses from a string. Returns the original
    * string as is if the parentheses are unbalanced or out or order. Does not
    * remove extra spaces.
    *
    * @method removeParensFromText
    * @param {String} string
    * @return {String}
    */
    article.removeParensFromText = function ( string ) {
    var
    ch,
    newString = '',
    level = 0,
    i = 0;

    for( i; i < string.length; i++ ) {
    ch = string.charAt( i );

    if ( ch === ')' && level === 0 ) {
    return string;
    }
    if ( ch === '(' ) {
    level++;
    continue;
    } else if ( ch === ')' ) {
    level--;
    continue;
    }
    if ( level === 0 ) {
    // Remove leading spaces before brackets
    if ( ch === ' ' && string.charAt( i + 1 ) === '(' ) {
    continue;
    }
    newString += ch;
    }
    }

    return ( level === 0 ) ? newString : string;
    };

    /**
    * Use createElementNS to create the svg:image tag as jQuery
    * uses createElement instead. Some browsers map the `image` tag
    * to `img` tag, thus an `svg:image` is required.
    *
    * @method createSVGTag
    * @param {String} tag
    * @return {Object}
    */
    article.createSVGTag = function ( tag ) {
    return document.createElementNS( 'http://www.w3.org/2000/svg', tag );
    };

    /**
    * Returns a thumbnail object based on the ratio of the image
    * Uses an SVG image where available to add the triangle/pokey
    * mask on the image. Crops and resizes the SVG image so that
    * is fits inside a rectangle of a particular size.
    *
    * @method createThumbnail
    * @param {Object} thumbnail
    * @param {boolean} tall
    * @return {Object} jQuery DOM element of the thumbnail
    */
    article.createThumbnail = function ( thumbnail, tall ) {
    var svg = mw.popups.supportsSVG;

    if (
    // No thumbnail
    !thumbnail ||
    // Image too small for landscape display
    ( !tall && thumbnail.width < article.SIZES.landscapeImage.w ) ||
    // Image too small for protrait display
    ( tall && thumbnail.height < article.SIZES.portraitImage.h ) ||
    // These characters in URL that could inject CSS and thus JS
    (
    thumbnail.source.indexOf( '\\' ) > -1 ||
    thumbnail.source.indexOf( '\'' ) > -1 ||
    thumbnail.source.indexOf( '\"' ) > -1
    )
    ) {
    return $( '<span>' );
    }

    if ( tall && svg ) {
    return article.createSvgImageThumbnail(
    'mwe-popups-is-not-tall',
    thumbnail.source,
    ( thumbnail.width > article.SIZES.portraitImage.w ) ?
    ( ( thumbnail.width - article.SIZES.portraitImage.w ) / -2 ) :
    ( article.SIZES.portraitImage.w - thumbnail.width ),
    ( thumbnail.height > article.SIZES.portraitImage.h ) ?
    ( ( thumbnail.height - article.SIZES.portraitImage.h ) / -2 ) :
    0,
    thumbnail.width,
    thumbnail.height,
    article.SIZES.portraitImage.w,
    article.SIZES.portraitImage.h
    );
    }

    if ( tall && !svg ) {
    return article.createImgThumbnail( 'mwe-popups-is-tall', thumbnail.source );
    }

    if ( !tall && svg ) {
    return article.createSvgImageThumbnail(
    'mwe-popups-is-not-tall',
    thumbnail.source,
    0,
    ( thumbnail.height > article.SIZES.landscapeImage.h ) ?
    ( ( thumbnail.height - article.SIZES.landscapeImage.h ) / -2 ) :
    0,
    thumbnail.width,
    thumbnail.height,
    article.SIZES.landscapeImage.w + 3,
    ( thumbnail.height > article.SIZES.landscapeImage.h ) ?
    article.SIZES.landscapeImage.h :
    thumbnail.height,
    'mwe-popups-mask'
    );
    }

    if ( !tall && !svg ) {
    return article.createImgThumbnail( 'mwe-popups-is-not-tall', thumbnail.source );
    }
    };

    /**
    * Returns the `svg:image` object for thumbnail
    *
    * @method createSvgImageThumbnail
    * @param {String} className
    * @param {String} url
    * @param {Number} x
    * @param {Number} y
    * @param {Number} thumbnailWidth
    * @param {Number} thumbnailHeight
    * @param {Number} width
    * @param {Number} height
    * @param {String} clipPath
    * @return {jQuery}
    */
    article.createSvgImageThumbnail = function (
    className, url, x, y, thumbnailWidth, thumbnailHeight, width, height, clipPath
    ) {
    var $thumbnailSVGImage, $thumbnail;

    $thumbnailSVGImage = $( article.createSVGTag( 'image' ) );
    $thumbnailSVGImage
    .addClass( className )
    .attr( {
    'xlink:href': url,
    x: x,
    y: y,
    width: thumbnailWidth,
    height: thumbnailHeight,
    'clip-path': 'url(#' + clipPath + ')'
    } );

    $thumbnail = $( '<svg>' )
    .attr( {
    xmlns: 'http://www.w3.org/2000/svg',
    width: width,
    height: height
    } )
    .append( $thumbnailSVGImage );

    return $thumbnail;
    };

    /**
    * Returns the `img` object for thumbnail
    *
    * @method createImgThumbnail
    * @param {String} className
    * @param {String} url
    * @return {jQuery}
    */
    article.createImgThumbnail = function ( className, url ) {
    return $( '<div>' )
    .addClass( className )
    .css( 'background-image', 'url(' + url + ')' );
    };

    /**
    * Positions the popup based on the mouse position and popup size
    * Default popup positioning is below and to the right of the mouse or link,
    * unless flippedX or flippedY is true. flippedX and flippedY are cached.
    *
    * @method getOffset
    * @param {jQuery} link
    * @param {Object} event
    * @return {Object} This can be passed to `.css()` to position the element
    */
    article.getOffset = function ( link, event ) {
    var
    href = link.attr( 'href' ),
    flippedX = false,
    flippedY = false,
    settings = mw.popups.render.cache[ href ].settings,
    offsetTop = ( event.pageY ) ? // If it was a mouse event
    // Position according to mouse
    event.pageY + 20 :
    // Position according to link position or size
    link.offset().top + link.height() + 9,
    clientTop = ( event.clientY ) ?
    event.clientY :
    offsetTop,
    offsetLeft = ( event.pageX ) ?
    event.pageX :
    link.offset().left;

    // X Flip
    if ( offsetLeft > ( $( window ).width() / 2 ) ) {
    offsetLeft += ( !event.pageX ) ? link.width() : 0;
    offsetLeft -= ( !settings.tall ) ?
    article.SIZES.portraitPopupWidth :
    article.SIZES.landscapePopupWidth;
    flippedX = true;
    }

    if ( event.pageX ) {
    offsetLeft += ( flippedX ) ? 20 : -20;
    }

    mw.popups.render.cache[ href ].settings.flippedX = flippedX;

    // Y Flip
    if ( clientTop > ( $( window ).height() / 2 ) ) {
    flippedY = true;
    }

    if ( event.pageY && flippedY ) {
    offsetTop += 30;
    }

    mw.popups.render.cache[ href ].settings.flippedY = flippedY;

    return {
    top: offsetTop + 'px',
    left: offsetLeft + 'px'
    };
    };

    /**
    * Returns an array of classes based on the size and setting of the popup
    *
    * @method getClassses
    * @param {jQuery} link
    * @return {Array} List of classes to applied to the parent `div`
    */
    article.getClasses = function ( link ) {
    var
    classes = [],
    cache = mw.popups.render.cache [ link.attr( 'href' ) ],
    tall = cache.settings.tall,
    thumbnail = cache.settings.thumbnail,
    flippedY = cache.settings.flippedY,
    flippedX = cache.settings.flippedX;

    if ( flippedY ) {
    classes.push( 'mwe-popups-fade-in-down' );
    } else {
    classes.push( 'mwe-popups-fade-in-up' );
    }

    if ( flippedY && flippedX ) {
    classes.push( 'flipped_x_y' );
    }

    if ( flippedY && !flippedX ) {
    classes.push( 'flipped_y' );
    }

    if ( flippedX && !flippedY ) {
    classes.push( 'flipped_x' );
    }

    if ( ( !thumbnail || tall ) && !flippedY ) {
    classes.push( 'mwe-popups-no-image-tri' );
    }

    if ( ( thumbnail && !tall ) && !flippedY ) {
    classes.push( 'mwe-popups-image-tri' );
    }

    if ( tall ) {
    classes.push( 'mwe-popups-is-tall' );
    } else {
    classes.push( 'mwe-popups-is-not-tall' );
    }

    return classes;
    };

    /**
    * Processed the popup div after it has been displayed
    * to correctly render the triangle/pokeys
    *
    * @method processPopups
    * @param {jQuery} link
    */
    article.processPopup = function ( link ) {
    var
    svg = mw.popups.supportsSVG,
    cache = mw.popups.render.cache [ link.attr( 'href' ) ],
    popup = mw.popups.$popup,
    tall = cache.settings.tall,
    thumbnail = cache.settings.thumbnail,
    flippedY = cache.settings.flippedY,
    flippedX = cache.settings.flippedX;

    popup.find( '.mwe-popups-settings-icon' ).click( function () {
    mw.popups.settings.open();
    } );

    if ( !flippedY && !tall && cache.settings.thumbnail.height < article.SIZES.landscapeImage.h ) {
    $( '.mwe-popups-extract').css(
    'margin-top',
    cache.settings.thumbnail.height - article.SIZES.pokeySize
    );
    }

    if ( !svg && flippedY && !tall ) {
    $( '.mwe-popups-extract' ).css( 'margin-top', '206px' );
    }

    if ( flippedY ) {
    popup.css( {
    top: popup.offset().top - ( popup.outerHeight() + 50 )
    } );
    }

    if ( flippedY && thumbnail && svg ) {
    mw.popups.$popup
    .find( 'image' )[ 0 ]
    .setAttribute( 'clip-path', '' );
    }

    if ( flippedY && flippedX && thumbnail && tall && svg ) {
    mw.popups.$popup
    .find( 'image' )[ 0 ]
    .setAttribute( 'clip-path', 'url(#mwe-popups-landscape-mask-flip)' );
    }

    if ( flippedX && !flippedY && thumbnail && !tall && svg ) {
    mw.popups.$popup
    .find( 'image' )[ 0 ]
    .setAttribute( 'clip-path', 'url(#mwe-popups-mask-flip)' );
    }

    if ( flippedX && !flippedY && thumbnail && tall && svg ) {
    mw.popups.$popup
    .removeClass( 'mwe-popups-no-image-tri' )
    .find( 'image' )[ 0 ]
    .setAttribute( 'clip-path', 'url(#mwe-popups-landscape-mask)' );
    }
    };

    mw.popups.render.article = article;

    } ) ( jQuery, mediaWiki );

    Revision as of 16:51, March 22, 2023

    /* All JavaScript here will be loaded for users of the Cosmos skin */
    //check localStorage for darkmode
    var html = document.getElementsByTagName('html')[0];
    if (localStorage.getItem('dusty-darkmode') === 'dark') {
        html.classList.add('client-darkmode');
    } else {
        html.classList.remove('client-darkmode');
    }
    
    $(document).ready(function () {
        /*/Site notice noexcerpt /*
        if ($('#siteNotice').length > 0) {
            var siteNotice = document.getElementById('siteNotice');
            siteNotice.classList.add('noexcerpt');
        }
        //empty paragraphs noexcerpt
        if ($('.mw-empty-elt').length > 0) {
            var emptyP = document.getElementsByClassName('mw-empty-elt');
            for (var i = 0; i < emptyP.length; i++) {
                emptyP[i].classList.add('noexcerpt');
            }
        }*/
        //end noexcerpt class-adds
        //ADD ... TO SEARCH BAR
        document.getElementsByName('search')[0].placeholder = "Search WT Wiki...";
    
        //ACTIVATING DARK MODE
        //target the icon area
        var dropdown = document.getElementById('p-more');
        var buttonGroup = document.getElementsByClassName('cosmos-header__wiki-buttons')[0];
        // Create & Place darkmode button
        var darkModeButton = document.createElement('a');
        darkModeButton.classList.add('wds-button', 'wds-is-secondary', 'dusty-darkmode-button');
        darkModeButton.title = 'Switch to Dark Mode';
        var moonSVG = '<svg style="color: white" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-moon wds-icon-small wds-icon-darkmode" id="wds-icons-darkmode-small" viewBox="0 0 16 16"> <path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278zM4.858 1.311A7.269 7.269 0 0 0 1.025 7.71c0 4.02 3.279 7.276 7.319 7.276a7.316 7.316 0 0 0 5.205-2.162c-.337.042-.68.063-1.029.063-4.61 0-8.343-3.714-8.343-8.29 0-1.167.242-2.278.681-3.286z"></path> </svg>';//little moon icon
        var sunSVG = '<svg style="color: white" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-sun wds-icon-small wds-icon-lightmode" id="wds-icons-lightmode-small" viewBox="0 0 16 16"> <path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z" fill="white"></path> </svg>';//little sun icon
        //sneak it intp the button group
        buttonGroup.insertBefore(darkModeButton, dropdown);
        console.log('this works?');
    
        //changing the button
        if (html.classList.contains('client-darkmode')) {
            darkModeButton.innerHTML = sunSVG;//if dark mode, make button sun
        } else {
            darkModeButton.innerHTML = moonSVG;//if light mode, make button moon
        }
        //when u click on dark mode button
        darkModeButton.onclick = function () {
            //add darkmode class to html element
            html.classList.toggle('client-darkmode');
            //make browser remember & change buttons again
            if (darkModeButton.innerHTML === sunSVG) {
                darkModeButton.innerHTML = moonSVG;
                darkModeButton.title = 'Switch to Light Mode';
                console.log('switching to light mode!');
                //saving light mode setting
                localStorage.setItem('dusty-darkmode', 'light');
            } else {
                darkModeButton.innerHTML = sunSVG;
                darkModeButton.title = 'Switch to Dark Mode';
                console.log('switching to dark mode!');
                //saving dark mode setting
                localStorage.setItem('dusty-darkmode', 'dark');
            }
        };//end darkmode function
    
        //check if discord widget exists (presence of Cosmos Rail)
        if ($('#discordwidget').length > 0) {
            //place discord widget
            document.getElementById("discordwidget").innerHTML = '<iframe src="https://discord.com/widget?id=461483989463597057&theme=dark" class="railModule module interface-module" width="100%" height="400px" allowtransparency="true" frameborder="0" sandbox="allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts"></iframe>';
        }
    });//end document ready
    
    ( function ( $, mw ) {
    	'use strict';
    
    	/**
    	 * @class mw.popups.render.article
    	 * @singleton
    	 */
    	var article = {};
    
    	/**
    	 * Size constants for popup images
    	 * @property SIZES
    	 */
    	article.SIZES = {
    		portraitImage: {
    			h: 250, // Exact height
    			w: 203 // Max width
    		},
    		landscapeImage: {
    			h: 200, // Max height
    			w: 300 // Exact Width
    		},
    		landscapePopupWidth: 450, // Exact width of a landscape popup
    		portraitPopupWidth: 300, // Exact width of a portrait popup
    		pokeySize: 8 // Height of the triangle used to point at the link
    	};
    
    
    	/**
    	 * Survey link, if any, for this renderer
    	 * @property surveyLink
    	 */
    	article.surveyLink = mw.config.get( 'wgPopupsSurveyLink' );
    
    	/**
    	 * Send an API request and cache the jQuery element
    	 *
    	 * @param {jQuery} link
    	 * @return {jQuery.Promise}
    	 */
    	article.init = function ( link ) {
    		var href = link.attr( 'href' ),
    			title = mw.popups.getTitle( href ),
    			deferred = $.Deferred();
    
    		if ( !title ) {
    			return deferred.reject().promise();
    		}
    
    		mw.popups.render.currentRequest = mw.popups.api.get( {
    			action: 'query',
    			prop: 'extracts|pageimages|revisions|info',
    			redirects: 'true',
    			exintro: 'true',
    			exsentences: 2,
    			// there is an added geometric limit on .mwe-popups-extract
    			// so that text does not overflow from the card
    			explaintext: 'true',
    			piprop: 'thumbnail',
    			pithumbsize: 300,
    			rvprop: 'timestamp',
    			inprop: 'watched',
    			indexpageids: true,
    			titles: title
    		} );
    
    		mw.popups.render.currentRequest.fail( deferred.reject );
    		mw.popups.render.currentRequest.done( function ( re ) {
    			mw.popups.render.currentRequest = undefined;
    
    			/*if (
    				!re.query ||
    				!re.query.pages ||
    				!re.query.pages[ re.query.pageids[ 0 ] ].extract ||
    				re.query.pages[ re.query.pageids[ 0 ] ].extract === ''
    			) {
    				deferred.reject();
    				return;
    			}*/
    
    			mw.popups.render.cache[ href ] = {};
    			mw.popups.render.cache[ href ].popup = article.createPopup( re, href );
    			mw.popups.render.cache[ href ].getOffset = article.getOffset;
    			mw.popups.render.cache[ href ].getClasses = article.getClasses;
    			mw.popups.render.cache[ href ].process = article.processPopup;
    			deferred.resolve();
    		} );
    
    		return deferred.promise();
    	};
    
    	/**
    	 * Returns a thumbnail object based on the ratio of the image
    	 * Uses an SVG image where available to add the triangle/pokey
    	 * mask on the image. Crops and resizes the SVG image so that
    	 * is fits inside a rectangle of a particular size.
    	 *
    	 * @method createPopup
    	 * @param {Object} re
    	 * @param {String} href
    	 * @return {jQuery}
    	 */
    	article.createPopup = function ( re, href ) {
    		var $div,
    			page = re.query.pages[ re.query.pageids[ 0 ] ],
    			$contentbox = $( '<a>' )
    				.attr( 'href', href )
    				.addClass( 'mwe-popups-extract' )
    				.append(
    					article.getProcessedElements( page.extract, page.title )
    				),
    			thumbnail = page.thumbnail,
    			tall = thumbnail && thumbnail.height > thumbnail.width,
    			$thumbnail = article.createThumbnail( thumbnail, tall ),
    			timestamp = new Date( page.revisions[ 0 ].timestamp ),
    			timediff = new Date() - timestamp,
    			oneDay = 1000 * 60 * 60 * 24,
    			timestampclass = ( timediff < oneDay ) ?
    				'mwe-popups-timestamp-recent' :
    				'mwe-popups-timestamp-older',
    			$settingsImage = $( '<a>' ).addClass( 'mwe-popups-icon mwe-popups-settings-icon' ),
    			$surveyImage,
    			$timestamp = $( '<div>' )
    				.addClass( timestampclass )
    				.append(
    					$( '<span>' ).text( mw.message( 'popups-last-edited',
    						moment( timestamp ).fromNow() ).text() ),
    					$settingsImage
    				);
    
    		if ( article.surveyLink ) {
    			$surveyImage = $( '<a>' )
    				.attr( 'href', article.surveyLink )
    				.attr( 'target', '_blank' )
    				.attr( 'title', mw.message( 'popups-send-feedback' ) )
    				.addClass( 'mwe-popups-icon mwe-popups-survey-icon' );
    			$timestamp.append( $surveyImage );
    		}
    
    
    		if ( $thumbnail.prop( 'tagName' ) !== 'SPAN' ) {
    			$thumbnail = $( '<a>' )
    				.addClass( 'mwe-popups-discreet' )
    				.attr( 'href', href )
    				.append( $thumbnail );
    		} else {
    			tall = thumbnail = undefined;
    		}
    
    		$div = $( '<div>' ).append( $thumbnail, $contentbox, $timestamp );
    
    		mw.popups.render.cache[ href ].settings = {
    			'title': page.title,
    			'tall': ( tall === undefined ) ? false : tall,
    			'thumbnail': ( thumbnail === undefined ) ? false : thumbnail
    		};
    
    		return $div;
    	};
    
    	/**
    	 * Returns an array of elements to be appended after removing parentheses
    	 * and making the title in the extract bold.
    	 *
    	 * @method getProcessedElements
    	 * @param {String} extract Should be unescaped
    	 * @param {String} title Should be unescaped
    	 * @return {Array} of elements to appended
    	 */
    	article.getProcessedElements = function ( extract, title ) {
    		title = title.replace( /([.?*+^$[\]\\(){}|-])/g, '\\$1' ); // Escape RegExp elements
    
    		var elements = [],
    			regExp = new RegExp( '(^|\\s)(' + title + ')(|$)', 'ig' ),
    			boldIdentifier = '<bi-' + Math.random() + '>',
    			snip = '<snip-' + Math.random() + '>';
    
    		// Remove text in parentheses along with the parentheses
    		extract = article.removeParensFromText( extract );
    		extract = extract.replace(/\s+/g, ' '); // Remove extra white spaces
    
    		// Make title bold in the extract text
    		// As the extract is html escaped there can be no such string in it
    		// Also, the title is escaped of RegExp elements thus can't have "*"
    		extract = extract.replace( regExp, '$1' + snip + boldIdentifier + '$2' + snip + '$3' );
    		extract = extract.split( snip );
    
    		$.each( extract, function ( index, part ) {
    			if ( part.indexOf( boldIdentifier ) === 0 ) {
    				elements.push( $( '<b>' ).text( part.substring( boldIdentifier.length ) ) );
    			} else {
    				elements.push( document.createTextNode( part ) );
    			}
    		} );
    
    		return elements;
    	};
    
    	/**
    	 * Removes content in parentheses from a string.  Returns the original
    	 * string as is if the parentheses are unbalanced or out or order. Does not
    	 * remove extra spaces.
    	 *
    	 * @method removeParensFromText
    	 * @param {String} string
    	 * @return {String}
    	 */
    	article.removeParensFromText = function ( string ) {
    		var
    			ch,
    			newString = '',
    			level = 0,
    			i = 0;
    
    		for( i; i < string.length; i++ ) {
    			ch = string.charAt( i );
    
    			if ( ch === ')' && level === 0  ) {
    				return string;
    			}
    			if ( ch === '(' ) {
    				level++;
    				continue;
    			} else if ( ch === ')' ) {
    				level--;
    				continue;
    			}
    			if ( level === 0 ) {
    				// Remove leading spaces before brackets
    				if ( ch === ' ' && string.charAt( i + 1 ) === '(' ) {
    					continue;
    				}
    				newString += ch;
    			}
    		}
    
    		return ( level === 0 ) ? newString : string;
    	};
    
    	/**
    	 * Use createElementNS to create the svg:image tag as jQuery
    	 * uses createElement instead. Some browsers map the `image` tag
    	 * to `img` tag, thus an `svg:image` is required.
    	 *
    	 * @method createSVGTag
    	 * @param {String} tag
    	 * @return {Object}
    	 */
    	article.createSVGTag = function ( tag ) {
    		return document.createElementNS( 'http://www.w3.org/2000/svg', tag );
    	};
    
    	/**
    	 * Returns a thumbnail object based on the ratio of the image
    	 * Uses an SVG image where available to add the triangle/pokey
    	 * mask on the image. Crops and resizes the SVG image so that
    	 * is fits inside a rectangle of a particular size.
    	 *
    	 * @method createThumbnail
    	 * @param {Object} thumbnail
    	 * @param {boolean} tall
    	 * @return {Object} jQuery DOM element of the thumbnail
    	 */
    	article.createThumbnail = function ( thumbnail, tall ) {
    		var svg = mw.popups.supportsSVG;
    
    		if (
    			// No thumbnail
    			!thumbnail ||
    			// Image too small for landscape display
    			( !tall && thumbnail.width < article.SIZES.landscapeImage.w ) ||
    			// Image too small for protrait display
    			( tall && thumbnail.height < article.SIZES.portraitImage.h ) ||
    			// These characters in URL that could inject CSS and thus JS
    			(
    				thumbnail.source.indexOf( '\\' ) > -1 ||
    				thumbnail.source.indexOf( '\'' ) > -1 ||
    				thumbnail.source.indexOf( '\"' ) > -1
    			)
    		) {
    			return $( '<span>' );
    		}
    
    		if ( tall && svg ) {
    			return article.createSvgImageThumbnail(
    				'mwe-popups-is-not-tall',
    				thumbnail.source,
    				( thumbnail.width > article.SIZES.portraitImage.w ) ?
    						( ( thumbnail.width - article.SIZES.portraitImage.w ) / -2 ) :
    						( article.SIZES.portraitImage.w - thumbnail.width ),
    				( thumbnail.height > article.SIZES.portraitImage.h ) ?
    						( ( thumbnail.height - article.SIZES.portraitImage.h ) / -2 ) :
    						0,
    				thumbnail.width,
    				thumbnail.height,
    				article.SIZES.portraitImage.w,
    				article.SIZES.portraitImage.h
    			);
    		}
    
    		if ( tall && !svg ) {
    			return article.createImgThumbnail( 'mwe-popups-is-tall', thumbnail.source );
    		}
    
    		if ( !tall && svg ) {
    			return article.createSvgImageThumbnail(
    				'mwe-popups-is-not-tall',
    				thumbnail.source,
    				0,
    				( thumbnail.height > article.SIZES.landscapeImage.h ) ?
    						( ( thumbnail.height - article.SIZES.landscapeImage.h ) / -2 ) :
    						0,
    				thumbnail.width,
    				thumbnail.height,
    				article.SIZES.landscapeImage.w + 3,
    				( thumbnail.height > article.SIZES.landscapeImage.h ) ?
    						article.SIZES.landscapeImage.h :
    						thumbnail.height,
    				'mwe-popups-mask'
    			);
    		}
    
    		if ( !tall && !svg ) {
    			return article.createImgThumbnail( 'mwe-popups-is-not-tall', thumbnail.source );
    		}
    	};
    
    	/**
    	 * Returns the `svg:image` object for thumbnail
    	 *
    	 * @method createSvgImageThumbnail
    	 * @param {String} className
    	 * @param {String} url
    	 * @param {Number} x
    	 * @param {Number} y
    	 * @param {Number} thumbnailWidth
    	 * @param {Number} thumbnailHeight
    	 * @param {Number} width
    	 * @param {Number} height
    	 * @param {String} clipPath
    	 * @return {jQuery}
    	 */
    	article.createSvgImageThumbnail = function (
    		className, url, x, y, thumbnailWidth, thumbnailHeight, width, height, clipPath
    	) {
    		var $thumbnailSVGImage, $thumbnail;
    
    		$thumbnailSVGImage = $( article.createSVGTag( 'image' ) );
    		$thumbnailSVGImage
    			.addClass( className )
    			.attr( {
    				'xlink:href': url,
    				x: x,
    				y: y,
    				width: thumbnailWidth,
    				height: thumbnailHeight,
    				'clip-path': 'url(#' + clipPath + ')'
    			} );
    
    		$thumbnail = $( '<svg>' )
    			.attr( {
    				xmlns: 'http://www.w3.org/2000/svg',
    				width: width,
    				height: height
    			} )
    			.append( $thumbnailSVGImage );
    
    		return $thumbnail;
    	};
    
    	/**
    	 * Returns the `img` object for thumbnail
    	 *
    	 * @method createImgThumbnail
    	 * @param {String} className
    	 * @param {String} url
    	 * @return {jQuery}
    	 */
    	article.createImgThumbnail = function ( className, url ) {
    		return $( '<div>' )
    			.addClass( className )
    			.css( 'background-image', 'url(' + url + ')' );
    	};
    
    	/**
    	 * Positions the popup based on the mouse position and popup size
    	 * Default popup positioning is below and to the right of the mouse or link,
    	 * unless flippedX or flippedY is true. flippedX and flippedY are cached.
    	 *
    	 * @method getOffset
    	 * @param {jQuery} link
    	 * @param {Object} event
    	 * @return {Object} This can be passed to `.css()` to position the element
    	 */
    	article.getOffset = function ( link, event ) {
    		var
    			href = link.attr( 'href' ),
    			flippedX = false,
    			flippedY = false,
    			settings = mw.popups.render.cache[ href ].settings,
    			offsetTop = ( event.pageY ) ? // If it was a mouse event
    				// Position according to mouse
    				event.pageY + 20 :
    				// Position according to link position or size
    				link.offset().top + link.height() + 9,
    			clientTop = ( event.clientY ) ?
    				event.clientY :
    				offsetTop,
    			offsetLeft = ( event.pageX ) ?
    				event.pageX :
    				link.offset().left;
    
    		// X Flip
    		if ( offsetLeft > ( $( window ).width() / 2 ) ) {
    			offsetLeft += ( !event.pageX ) ? link.width() : 0;
    			offsetLeft -= ( !settings.tall ) ?
    				article.SIZES.portraitPopupWidth :
    				article.SIZES.landscapePopupWidth;
    			flippedX = true;
    		}
    
    		if ( event.pageX ) {
    			offsetLeft += ( flippedX ) ? 20 : -20;
    		}
    
    		mw.popups.render.cache[ href ].settings.flippedX = flippedX;
    
    		// Y Flip
    		if ( clientTop > ( $( window ).height() / 2 ) ) {
    			flippedY = true;
    		}
    
    		if ( event.pageY && flippedY ) {
    			offsetTop += 30;
    		}
    
    		mw.popups.render.cache[ href ].settings.flippedY = flippedY;
    
    		return {
    			top: offsetTop + 'px',
    			left: offsetLeft + 'px'
    		};
    	};
    
    	/**
    	 * Returns an array of classes based on the size and setting of the popup
    	 *
    	 * @method getClassses
    	 * @param {jQuery} link
    	 * @return {Array} List of classes to applied to the parent `div`
    	 */
    	article.getClasses = function ( link ) {
    		var
    			classes = [],
    			cache = mw.popups.render.cache [ link.attr( 'href' ) ],
    			tall = cache.settings.tall,
    			thumbnail = cache.settings.thumbnail,
    			flippedY = cache.settings.flippedY,
    			flippedX = cache.settings.flippedX;
    
    		if ( flippedY ) {
    			classes.push( 'mwe-popups-fade-in-down' );
    		} else {
    			classes.push( 'mwe-popups-fade-in-up' );
    		}
    
    		if ( flippedY && flippedX ) {
    			classes.push( 'flipped_x_y' );
    		}
    
    		if ( flippedY && !flippedX ) {
    			classes.push( 'flipped_y' );
    		}
    
    		if ( flippedX && !flippedY ) {
    			classes.push( 'flipped_x' );
    		}
    
    		if ( ( !thumbnail || tall ) && !flippedY ) {
    			classes.push( 'mwe-popups-no-image-tri' );
    		}
    
    		if ( ( thumbnail && !tall ) && !flippedY ) {
    			classes.push( 'mwe-popups-image-tri' );
    		}
    
    		if ( tall ) {
    			classes.push( 'mwe-popups-is-tall' );
    		} else {
    			classes.push( 'mwe-popups-is-not-tall' );
    		}
    
    		return classes;
    	};
    
    	/**
    	 * Processed the popup div after it has been displayed
    	 * to correctly render the triangle/pokeys
    	 *
    	 * @method processPopups
    	 * @param {jQuery} link
    	 */
    	article.processPopup = function ( link ) {
    		var
    			svg = mw.popups.supportsSVG,
    			cache = mw.popups.render.cache [ link.attr( 'href' ) ],
    			popup = mw.popups.$popup,
    			tall = cache.settings.tall,
    			thumbnail = cache.settings.thumbnail,
    			flippedY = cache.settings.flippedY,
    			flippedX = cache.settings.flippedX;
    
    		popup.find( '.mwe-popups-settings-icon' ).click( function () {
    			mw.popups.settings.open();
    		} );
    
    		if ( !flippedY && !tall && cache.settings.thumbnail.height < article.SIZES.landscapeImage.h ) {
    			$( '.mwe-popups-extract').css(
    				'margin-top',
    				cache.settings.thumbnail.height - article.SIZES.pokeySize
    			);
    		}
    
    		if ( !svg && flippedY && !tall ) {
    			$( '.mwe-popups-extract' ).css( 'margin-top', '206px' );
    		}
    
    		if ( flippedY ) {
    			popup.css( {
    				top: popup.offset().top - ( popup.outerHeight() + 50 )
    			} );
    		}
    
    		if ( flippedY && thumbnail && svg ) {
    			mw.popups.$popup
    				.find( 'image' )[ 0 ]
    				.setAttribute( 'clip-path', '' );
    		}
    
    		if ( flippedY && flippedX && thumbnail && tall && svg ) {
    			mw.popups.$popup
    				.find( 'image' )[ 0 ]
    				.setAttribute( 'clip-path', 'url(#mwe-popups-landscape-mask-flip)' );
    		}
    
    		if ( flippedX && !flippedY && thumbnail && !tall && svg ) {
    			mw.popups.$popup
    				.find( 'image' )[ 0 ]
    				.setAttribute( 'clip-path', 'url(#mwe-popups-mask-flip)' );
    		}
    
    		if ( flippedX && !flippedY && thumbnail && tall && svg ) {
    			mw.popups.$popup
    				.removeClass( 'mwe-popups-no-image-tri' )
    				.find( 'image' )[ 0 ]
    				.setAttribute( 'clip-path', 'url(#mwe-popups-landscape-mask)' );
    		}
    	};
    
    	mw.popups.render.article = article;
    
    } ) ( jQuery, mediaWiki );
    
    Cookies help us deliver our services. By using our services, you agree to our use of cookies.
    Cookies help us deliver our services. By using our services, you agree to our use of cookies.