Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
* ArticlePreview
* Version: 2.0
*
* Author: Ultimate Dark Carnage
* Other attribution: HumansCanWinElves
*
* This script allows a user to see a preview of an article by hovering
* a local link and go to a page by clicking on that link.
**/
( function( window, $, mw ) {
"use strict";
// Sets the configuration object to an empty object if it does not exist
window.articlePreview = window.articlePreview || { };
// URL variables for the current page
const urlVars = new URLSearchParams( location.search );
// If the user prefers not to show the article preview, prevent running
if ( urlVars.get( "nopreview" ) || window.articlePreview.disabled ) return;
// Function to escape regular expressions
function escapeRegExp( string ) {
return string.replace( /[-[\]{}()*+!<=:?.\/\\^$|#\s,]/g, '\\$&' );
}
// Decodes the URL and converts underscores to spaces
function decodeURL( string ) {
return decodeURIComponent( string.replace( /_/g, " " ) );
}
// Creates a spinner constructor
function Spinner( size ) {
// Set the size to an object if the size is a number
if ( typeof size !== "object" ) {
if ( !isNaN( size ) && isFinite( size ) ) {
size = { width: Number( size ), height: Number( size ) };
} else {
throw new TypeError( "The value must either be an object or a finite number." );
}
}
// The current spinner instance
const sp = this;
// The spinner element
const spinner = document.createElementNS( "http://www.w3.org/2000/svg", "svg" );
spinner.classList.add( "wds-spinner", "wds-spinner__block" );
const spinnerAttr = Object.freeze( {
width: size.width,
height: size.height,
viewBox: [ 0, 0, 66, 66 ].join( " " ),
xmlns: "http://www.w3.org/2000/svg"
} );
Object.getOwnPropertyNames( spinnerAttr )
.forEach( function( property ) {
const value = spinnerAttr[ property ];
spinner.setAttribute( property, value );
} );
// The grouping element
const g = document.createElementNS( "http://www.w3.org/2000/svg", "g" );
g.setAttribute( "transform", "translate(33, 33)" );
// The circle element
const c = document.createElementNS( "http://www.w3.org/2000/svg", "circle" );
c.classList.add( "wds-spinner__stroke" );
const cAttr = Object.freeze( {
fill: "none",
r: 30,
"stroke-width": 2,
"stroke-dasharray": 188.49555921538757,
"stroke-dashoffset": 188.49555921538757,
"stroke-linecap": "round"
} );
Object.getOwnPropertyNames( cAttr )
.forEach( function( property ) {
const value = cAttr[ property ];
c.setAttribute( property, value );
} );
// Creates the spinner element
g.append( c );
spinner.append( g );
// Returns the actual element
sp.el = function( ) {
return spinner;
};
// Returns the outer html
sp.html = function( ) {
return spinner.outerHTML;
};
}
// Creates a script loader constructor
function Loader( resources, callback, thisArg ) {
// Sets the current instance in a variable
const al = this;
// If there are less than three arguments, set the context to the current instance
if ( arguments.length < 3 ) thisArg = this;
// An array of loaded scripts
al.loadedScripts = [ ];
// An array of MediaWiki modules
al.modules = resources.MODULES || [ ];
// An object of scripts to load
al.scripts = resources.SCRIPTS || { };
// An array of stylesheets to load
al.stylesheets = resources.STYLESHEETS || [ ];
// Initializes the loader
al.init = function( ) {
if ( Array.isArray( al.modules ) && al.modules.length ) {
return mw.loader
.using( al.modules )
.then( al.loadScripts.bind( al ) );
}
al.loadScripts( );
};
// Loads all scripts and stylesheets if they are not loaded
al.loadScripts = function( ) {
if ( Array.isArray( al.stylesheets ) && al.stylesheets.length ) {
importArticles( { type: "style", articles: al.stylesheets } );
}
const promises = Promise.all(
Object
.getOwnPropertyNames( al.scripts )
.map( function( name ) {
const script = al.scripts[ name ];
if ( window.dev && window.dev[ name ] ) {
al.loadedScripts.push( script );
return Promise.resolve( );
}
return new Promise( function( resolve, reject ) {
importArticle( {
type: "script",
article: script
} ).then( function( ) {
al.loadedScripts.push( script );
} ).then( resolve )[ "catch" ]( reject );
} );
} )
);
return promises.then( function( ) {
al.loaded = true;
return callback.apply( thisArg, al );
} );
};
return al;
}
// Creates a new vector
function Vector( x, y ) {
x = ( isNaN( x ) || !isFinite( x ) ) ? 0 : x;
y = ( isNaN( y ) || !isFinite( y ) ) ? 0 : y;
this.x = x;
this.y = y;
this.copy = function( ) {
return new Vector( x, y );
};
this.add = function( v ) {
return Vector.add( this, v );
};
this.subtract = function( v ) {
return Vector.add( this, v );
};
this.multiply = function( scalar ) {
return Vector.multiply( this, scalar );
};
this.divide = function( scalar ) {
return Vector.divide( this, scalar );
};
return this;
}
// Static methods for the vector constructor
Vector.add = function( vx, vy ) {
const ax = vx.x + vy.x, ay = vx.y + vy.y;
return new Vector( ax, ay );
};
Vector.subtract = function( vx, vy ) {
const ax = vx.x - vy.x, ay = vx.y - vy.y;
return new Vector( ax, ay );
};
Vector.multiply = function( v, scalar ) {
const ax = v.x * scalar, ay = v.y * scalar;
return new Vector( ax, ay );
};
Vector.divide = function( v, scalar ) {
const ax = v.x / scalar, ay = v.y / scalar;
return new Vector( ax, ay );
};
function ArticlePreview( options ) {
// If there are no arguments, set the options to an empty object
options = arguments.length === 0 ? { } : options;
// Setting the current instance to a variable
const ap = this;
// The current script version
ap.VERSION = "2.0";
// The current script name
ap.NAME = "ArticlePreview";
// MediaWiki configuration variables
ap.MW = mw.config.get( [
"wgServer",
"wgPageName",
"wgNamespaceNumber",
"wgArticlePath",
"wgFormattedNamespaces"
] );
// The current script target
ap.TARGET = document.querySelector( "#WikiaMainContent" ) ||
document.querySelector( "#mw-content-text" );
// The current mouse vector
ap.VECTOR = new Vector( );
// Checks if the mouse is hovering over a local link
ap.HOVER = false;
// Determines whether the article preview allows caching
ap.ALLOW_CACHE = false;
// The loading class name
ap.LOADING_CLASS = "article-preview__loading";
// Determines whether the preview is loading
ap.LOADING = false;
// The cache object
ap.CACHE = { };
// Determines whether the preview is in the DOM
ap.IN_DOM = false;
// The image used when no image is found
ap.DEFAULT_IMAGE = "";
// Determines whether to show infoboxes
ap.INFOBOX = false;
// Determines whether to show the table of contents
ap.TOC = false;
// Determines whether to generate the whole page
ap.WHOLE_PAGE = false;
// Determines whether the preview allows an image
ap.NO_IMAGE = false;
// Determines whether red links should be allowed
ap.ALLOW_NEW = false;
// The maximum text length
ap.MAXIMUM_TEXT_LENGTH = 1024;
// Default spinner height
ap.SPINNER_SIZE = 64;
// Default configuration options
ap.DEFAULTS = Object.freeze( {
ignoreActions: [ ],
ignoreImages: [ ],
ignorePages: [ mw.config.get( "wgPageName" ).split( "_" ).join( " " ) ],
ignoreURLs: [ ],
ignoreParents: [ ],
ignoreSelectors: [ ],
ignoreNamespaces: [ ],
infobox: ap.INFOBOX,
toc: ap.TOC,
wholePage: ap.WHOLE_PAGE,
cache: ap.ALLOW_CACHE,
allowNew: ap.ALLOW_NEW,
noImage: ap.NO_IMAGE,
defaultImage: ap.DEFAULT_IMAGE,
target: ap.TARGET,
maxTextLength: ap.MAXIMUM_TEXT_LENGTH
} );
// An array of scripts to load
ap.SCRIPTS = Object.freeze( {
"i18n": "u:dev:MediaWiki:I18n-js/code.js",
"wds": "u:dev:MediaWiki:WDSIcons/code.js",
"dorui": "u:dev:MediaWiki:Dorui.js",
"colors": "u:dev:MediaWiki:Colors/code.js"
} );
// An array of stylesheets to load
ap.STYLESHEETS = Object.freeze( [
"u:dev:MediaWiki:ArticlePreview.css"
] );
// An array of MediaWiki modules to load
ap.MODULES = Object.freeze( [
"mediawiki.api",
"mediawiki.Title",
"mediawiki.Uri"
] );
// The setters object
ap.SETTERS = Object.freeze( {
ignoreActions: function( value ) {
if ( Array.isArray( value ) ) {
return value.forEach( function( v ) {
if ( v === undefined || v === null ) return;
ap.PATTERNS.IGNORE_ACTIONS.push( v );
} );
}
ap.PATTERNS.IGNORE_ACTIONS.push( value );
},
ignoreImages: function( value ) {
if ( Array.isArray( value ) ) {
return value.forEach( function( v ) {
if ( v === undefined || v === null ) return;
ap.PATTERNS.IGNORE_IMAGES.push( v );
} );
}
ap.PATTERNS.IGNORE_IMAGES.push( value );
},
ignorePages: function( value ) {
if ( Array.isArray( value ) ) {
return value.forEach( function( v ) {
if ( v === undefined || v === null ) return;
ap.PATTERNS.IGNORE_PAGES.push( v );
} );
}
ap.PATTERNS.IGNORE_PAGES.push( value );
},
ignoreSelectors: function( value ) {
if ( Array.isArray( value ) ) {
return value.forEach( function( v ) {
if ( v === undefined || v === null ) return;
ap.PATTERNS.IGNORE_SELECTORS.push( v );
} );
}
ap.PATTERNS.IGNORE_SELECTORS.push( value );
},
ignoreNamespaces: function( value ) {
if ( Array.isArray( value ) ) {
return value.forEach( function( v ) {
if ( v === undefined || v === null ) return;
ap.PATTERNS.IGNORE_NAMESPACES.push( v );
} );
}
ap.PATTERNS.IGNORE_NAMESPACES.push( value );
},
ignoreParents: function( value ) {
if ( Array.isArray( value ) ) {
return value.forEach( function( v ) {
if ( v === undefined || v === null ) return;
ap.PATTERNS.IGNORE_PARENTS.push( v );
} );
}
ap.PATTERNS.IGNORE_PARENTS.push( value );
},
ignoreURLs: function( value ) {
if ( Array.isArray( value ) ) {
return value.forEach( function( v ) {
if ( v === undefined || v === null ) return;
ap.PATTERNS.IGNORE_URLS.push( v );
} );
}
ap.PATTERNS.IGNORE_URLS.push( value );
},
maxTextLength: function( value ) {
if ( isNaN( value ) || !isFinite( value ) ) {
ap.MAXIMUM_TEXT_LENGTH = 1024;
return;
}
ap.MAXIMUM_TEXT_LENGTH = Math.max( 250, Math.min( value, 1200 ) );
},
onlyinclude: function( value ) {
if ( Array.isArray( value ) ) {
return value.forEach( function( v ) {
if ( v === undefined || v === null ) return;
ap.PATTERNS.ONLYINCLUDE.push( v );
} );
}
ap.PATTERNS.ONLYINCLUDE.push( value );
},
prep: function( value ) {
if ( Array.isArray( value ) ) {
return value.forEach( function( v ) {
if ( v === undefined || v === null ) return;
ap.PATTERNS.PREP.push( v );
} );
}
ap.PATTERNS.PREP.push( value );
},
infobox: "INFOBOX",
toc: "TOC",
allowNew: "ALLOW_NEW",
wholePage: "WHOLE_PAGE",
cache: "ALLOW_CACHE",
noImage: "NO_IMAGE",
defaultImage: "DEFAULT_IMAGE",
target: "TARGET"
} );
// The configuration object
ap.CONFIG = { };
// An object of regular expression patterns
ap.PATTERNS = {
IGNORE_SELECTORS: [
".free",
".toc a",
".wikia-button",
".wds-button",
".wikia-button a",
".wds-button a",
".wikia-menu-button a",
".new",
".wds-list a"
],
IGNORE_ACTIONS: [ "edit" ],
IGNORE_PARENTS: [ ".nopreview" ],
IGNORE_PAGES: [ /Special\:(?:User)(?:Logout)/ ],
IGNORE_IMAGES: [ ],
IGNORE_URLS: [ window.location.href ],
IGNORE_NAMESPACES: [ ],
ONLYINCLUDE: [ ],
PREP: [ /<script>[\s\S]*?<\/script>/igm, /<ref>[\s\S]*?<\/ref>/igm, /<p.*\>\n*(?:<br.*\s*\/?>|)\n*<\/p>/gim ],
IMAGE_URI: /\.(?:(?:wikia|fandom)\.com|wikia\.(?:org|nocookie\.net))$/,
FANDOM_URI: /\.(?:(?:wikia|fandom)\.com|wikia\.org)$/,
WIKI_TITLE: /^\/wiki\/([^?&]+).*$/,
INDEX_TITLE: /^\/index\.php\?(?:.+&|)title=([^?&]+).*$/,
};
// Sets all configuration object
ap.setOptions = function( opts ) {
Object
.getOwnPropertyNames( ap.DEFAULTS )
.forEach( function( key ) {
const value = ap.DEFAULTS[ key ];
ap.CONFIG[ key ] = typeof opts[ key ] !== "undefined" ?
opts[ key ] : value;
} );
ap.initOptions( );
};
// Initializes all configurations
ap.initOptions = function( ) {
Object
.getOwnPropertyNames( ap.CONFIG )
.forEach( function( key ) {
const setter = ap.SETTERS[ key ];
if ( Function.prototype.isPrototypeOf( Object( setter ) ) ) {
return setter.call( ap, ap.CONFIG[ key ] );
}
ap[ setter ] = ap.CONFIG[ key ];
} );
};
// The function to initialize the script
ap.init = function( ) {
// Dorui (UI) object
ap.ui = window.dev.dorui;
// Colors object
ap.colors = window.dev.colors;
// WDSIcons object
ap.wds = window.dev.wds;
// Creates the user interface
ap.createUI( );
};
ap.createUI = function( ) {
// The preview actions
ap.ACTIONS = ap.ui.div( {
"class": "article-preview__actions"
} );
// The preview content wrapper
ap.CONTENT = ap.ui.div( {
"class": "article-preview__content"
} );
// The main preview wrapper
ap.WRAPPER = ap.ui.div( {
"class": "article-preview__body",
"children": [ ap.CONTENT, ap.ACTIONS ]
} );
// The main preview container
ap.CONTAINER = ap.ui.section( {
"class": "article-preview",
"child": ap.WRAPPER
} );
// The spinner element
ap.SPINNER = ( new Spinner( ap.SPINNER_SIZE ) ).el( );
ap.SPINNER.classList.add( "article-preview__spinner" );
// When the i18n messages are loaded, continue
ap.loadMessages( ap.load );
};
// Loads all messages for the script
ap.loadMessages = function( callback ) {
mw
.hook( "dev.i18n" )
.add( function( i18no ) {
// I18n object
ap.i18no = i18no;
i18no
.loadMessages( ap.NAME )
.then( callback );
} );
};
// Loads the script
ap.load = function( i18n ) {
// Sets the current message instance
ap.i18n = i18n;
// The main api object
ap.API = new mw.Api( );
// An object of conditions for showing the preview
ap.CONDITIONS = Object.freeze( {
ELEMENT: {
type: HTMLElement,
dispatch: function( element ) {
const whitelistedTagNames = Object.freeze( [
"a",
"area"
] );
if ( !whitelistedTagNames.includes( element.tagName.toLowerCase( ) ) ) {
return false;
}
const ignoreSelectors = ap.PATTERNS.IGNORE_SELECTORS.every( function( selector ) {
const elementsFromSelector = ap.TARGET.querySelectorAll( selector );
const inSelector = Array.from( elementsFromSelector ).includes( element );
return inSelector === false;
} );
if ( !ignoreSelectors ) return false;
return ap.PATTERNS.IGNORE_PARENTS.every( function( selector ) {
return element.closest( selector ) === null;
} );
}
},
PAGE: {
type: String,
dispatch: function( page ) {
if ( page === "" ) return false;
const title = new mw.Title( page );
const ignoreNamespaces = ap.PATTERNS.IGNORE_NAMESPACES.every( function( value ) {
if ( !isNaN( value ) && isFinite( value ) ) {
return title.namespace === value;
}
if ( RegExp.prototype.isPrototypeOf( Object( value ) ) ) {
const namespacePrefix = title.getNamespacePrefix( );
const namespace = namespacePrefix.split( ":" )[ 0 ];
const trueNamespace = namespace === "" ? "Main" : namespace;
const namespaceKey = "namespace-" + trueNamespace.toLowerCase( );
const parsedNamespace = ap.msg( namespaceKey ).escape( );
if ( !ap.msg( namespaceKey ).exists ) return true;
return !value.test( parsedNamespace );
}
if ( typeof namespace === "number" ) {
return title.namespace !== value;
}
return String( title.getNamespacePrefix( ).split( ":" )[ 0 ] ) !== value;
} );
if ( !ignoreNamespaces ) return false;
return ap.PATTERNS.IGNORE_PAGES.every( function( value ) {
if ( RegExp.prototype.isPrototypeOf( Object( value ) ) ) {
return !value.test( page );
}
return String( value ) !== page;
} );
}
},
URL: {
type: mw.Uri,
dispatch: function( url ) {
if ( !ap.PATTERNS.FANDOM_URI.test( url.host ) ) return false;
return ap.PATTERNS.IGNORE_URLS.every( function( value ) {
if ( RegExp.prototype.isPrototypeOf( Object( value ) ) ) {
return !value.test( String( url ) );
}
return String( value ) !== String( url );
} );
}
}
} );
// Fetches all links
ap.LINKS = ap.TARGET.querySelectorAll( "a, area" );
// Create a shorthand message function
ap.msg = function( name ) {
function getInstance( args ) {
args.unshift( name );
return ap.i18n.msg.apply( ap.i18n, args );
}
return Object.freeze( {
parse: function( ) {
const msg = getInstance( Array.from( arguments ) );
return msg.parse( );
},
escape: function( ) {
const msg = getInstance( Array.from( arguments ) );
return msg.escape( );
},
plain: function( ) {
const msg = getInstance( Array.from( arguments ) );
return msg.plain( );
},
encode: function( ) {
const msg = getInstance( Array.from( arguments ) );
const plain = msg.plain( );
return encodeURIComponent( plain );
},
decode: function( ) {
const msg = getInstance( Array.from( arguments ) );
const plain = msg.plain( );
return decodeURIComponent( plain );
},
exists: ap.i18n.msg( name ).exists
} );
};
// Checks the image source
ap.checkImageSrc = function( src ) {
if ( !src ) return false;
try {
const url = new mw.Uri( src );
return ap.PATTERNS.IMAGE_URI.test( url.host );
} catch( e ) {
return false;
}
};
// Sets the current theme
ap.setTheme = function( ) {
function createCSSVarProp( name, value ) {
const key = "--article-preview__" + name;
return [ key, value ].join( ": " );
}
const rootCSS = ":root { $css }";
const css = Object
.getOwnPropertyNames( ap.THEME )
.map( function( property ) {
const value = ap.THEME[ property ];
if ( Function.prototype.isPrototypeOf( value ) ) {
return createCSSVarProp( property, value.call( ap, property ) );
}
return createCSSVarProp( property, value );
} )
.join( ";" );
const result = rootCSS.replace( /\$css/i, css );
};
// Matches the value type
ap.matchesType = function( type, value ) {
const isString = typeof type === "string";
return isString ? typeof value === type :
type.prototype.isPrototypeOf( Object( value ) );
};
// Creates a checker for an object
ap.check = function( checker, value ) {
return ap.matchesType( checker.type, value ) ?
checker.dispatch( value ) : false;
};
// Checks the URL for an element
ap.checkURL = function( url ) {
return ap.check( ap.CONDITIONS.URL, url );
};
// Checks the <a> element
ap.checkElement = function( element ) {
return ap.check( ap.CONDITIONS.ELEMENT, element );
};
// Checks if a page can show the preview
ap.checkPage = function( page ) {
return ap.check( ap.CONDITIONS.PAGE, page );
};
ap.LINKS.forEach( ap.checkLink );
};
// Checks the current link
ap.checkLink = function( element ) {
// Step 1. Checks if an element can show the preview
const isAllowed = ap.checkElement( element );
// If the element does not pass the test, skip;
if ( !isAllowed ) return;
// Step 2. Creates the URL object for the link
const url = new mw.Uri( element.href );
// If the link does not pass the test, skip;
if ( !ap.checkURL( url ) ) return;
// Step 3. Fetches the page name
const page = decodeURL( ap.PATTERNS.INDEX_TITLE.test( url.path ) ?
ap.PATTERNS.INDEX_TITLE.exec( url.path )[ 1 ] :
( ap.PATTERNS.WIKI_TITLE.test( url.path ) ?
ap.PATTERNS.WIKI_TITLE.exec( url.path )[ 1 ] :
"" ) );
// If the page name is blank, undefined, or does not
// pass the test, stop here.
if ( page === "" || !ap.checkPage( page ) ) return;
// The parsed page name
const parsedPage = decodeURIComponent( page )
.split( "_" )
.join( " " );
// Sets the page name to a data attribute
element.dataset.pageName = parsedPage;
// Creates an event listener when the mouse is hovering over the link
element.addEventListener( "mouseover", ap.showPreview.bind( ap, {
element: element,
page: parsedPage,
url: url
} ) );
// Creates an event listener when the mouse leaves the link
element.addEventListener( "mouseout", ap.hidePreview.bind( ap, {
// element: element,
page: parsedPage
} ) );
};
ap.showPreview = function( opts ) {
// Sets the hover state to true
ap.HOVER = true;
// Sets the loading state to true
ap.LOADING = true;
// Adds the loading class to the container
ap.CONTAINER.classList.add( ap.LOADING_CLASS );
// Adds the spinner to the wrapper
ap.CONTENT.append( ap.SPINNER );
// The current element
const element = opts.element;
// The current element's client rect object
const rect = element.getBoundingClientRect( );
// Sets the actual coordinates to a vector
ap.VECTOR.x = ( rect.left + window.pageXOffset );
ap.VECTOR.y = ( rect.top + window.pageYOffset ) + ( rect.height + 2 );
// The current page name
const currentPage = opts.page;
// The current URL
const url = opts.url;
// Generates information for the article preview
ap.generateInfo( currentPage )
.then( ap.generateElement.bind( ap, url ) );
};
ap.generateInfo = function( page ) {
if ( ap.ALLOW_CACHE && ap.CACHE.hasOwnProperty( page ) ) {
return new Promise( function( resolve ) {
resolve( ap.CACHE[ page ] );
} );
}
const params = {
action: "parse",
page: page,
prop: "images|text",
format: "json",
disablepp: true,
redirects: true
};
if ( !ap.WHOLE_PAGE ) params.section = 0;
return ap.API.get( params );
};
ap.process = function( text ) {
const temp = ap.ui.div( {
html: text
} );
if ( !Array.isArray( ap.PATTERNS.PREP ) ) return "";
if ( Array.isArray( ap.PATTERNS.ONLYINCLUDE ) ) {
text = ap.PATTERNS
.ONLYINCLUDE
.map( function( value ) {
const elements = temp.querySelectorAll( value );
if ( !elements ) return false;
return Array
.from( elements )
.map( function( element ) {
temp.removeChild( element );
return element.outerHTML;
} ).join( "" );
} )
.filter( Boolean )
.join( "" ) || text;
}
text = ap.PATTERNS
.PREP
.reduce( function( value, pattern ) {
return value.replace( pattern, "" );
}, text );
return text;
};
ap.generateElement = function( url, data ) {
if ( !data.parse ) {
ap.PATTERNS.IGNORE_URLS.push( url.path );
return;
}
if ( ap.ALLOW_CACHE && !ap.CACHE.hasOwnProperty( data.parse.title ) ) {
ap.CACHE[ data.parse.title ] = data;
}
const image = data.parse.images
.map( function( value ) {
return ap.checkImageSrc( value ) ? false : value;
} )
.filter( Boolean )[ 0 ];
const text = data.parse.text[ "*" ];
if ( !image && !text ) {
ap.PATTERNS.IGNORE_URLS.push( url.path );
return;
}
const temp = ap.ui.div( {
html: ap.process( text )
} );
const selectorsToRemove = [ ".notice", "table" ];
if ( !ap.INFOBOX ) selectorsToRemove.push( ".infobox", "aside.portable-infobox" );
if ( !ap.TOC ) selectorsToRemove.push( ".toc" );
const elementsToRemove = temp.querySelectorAll( selectorsToRemove.join( ", " ) );
elementsToRemove.forEach( function( target ) {
target.remove( );
} );
const result = temp.innerHTML;
// Set loading state to false
ap.LOADING = false;
// Removes the spinner from the preview's content area
ap.CONTENT.removeChild( ap.SPINNER );
// The preview text area
const previewText = ap.ui.div( {
"class": "article-preview__text",
html: result
} );
// Inserts the preview text to its content
ap.CONTENT.insertAdjacentElement( "beforeend", previewText );
if ( ap.NO_IMAGE ) return ap.processPreview( url );
// The preview's imagee
const previewImage = ap.ui.img( {
"class": "article-preview__image"
} );
// The preview's image container
const imageContainer = ap.ui.div( {
"class": "article-preview__image-container",
child: previewImage
} );
// Appends the image to its content area
ap.CONTENT.insertAdjacentElement( "afterbegin", imageContainer );
if ( !image ) return ap.processImage( previewImage, url, {
useDefaultImage: true
} );
return ap.generateImage( image )
.then( ap.processImage.bind( ap, previewImage, url ) );
};
ap.generateImage = function( image ) {
const params = Object.freeze( {
action: "query",
redirects: true,
titles: "File:" + image.trim( ),
iiprop: "url",
prop: "imageinfo",
format: "json",
indexpageids: true
} );
return ap.API.get( params );
};
ap.getImageOrientation = function( image ) {
const height = image.naturalHeight || image.height;
const width = image.naturalWidth || image.width;
const aspectRatio = width / height;
console.log( aspectRatio, width, height );
if ( aspectRatio < 1 ) return "portrait";
if ( aspectRatio > 1 ) return "landscape";
return "square";
};
ap.processImage = function( previewImage, url, data ) {
if ( data.useDefaultImage ) {
previewImage.src = ap.DEFAULT_IMAGE;
return ap.processPreview( url );
}
const query = data.query, pageid = query.pageids[ 0 ], page = query.pages[ pageid ];
if ( query.redirects ) {
const redirect = query.redirects[ 0 ].to;
const redirectTitle = redirect.replace( /^File:/i, "" );
return ap.generateImage( redirectTitle )
.then( ap.processImage.bind( ap, previewImage, url ) );
}
const imageinfo = page.imageinfo;
if ( imageinfo.length ) {
previewImage.src = imageinfo[ 0 ].url;
} else {
previewImage.src = ap.DEFAULT_IMAGE;
}
previewImage.addEventListener( "load", function( ) {
const imageOrientation = ap.getImageOrientation( previewImage );
ap.CONTAINER.dataset.orientation = imageOrientation;
if ( imageOrientation === "portrait" ) {
ap.CONTAINER.style.height = Math.max( previewImage.naturalHeight || 0, previewImage.height || 0 ) + "px";
}
return ap.processPreview( url );
} );
};
ap.processPreview = function( url ) {
// Changes the positioning of the article preview
ap.CONTAINER.style.top = ap.VECTOR.y + "px";
ap.CONTAINER.style.left = ap.VECTOR.x + "px";
// Creates the element if it is not on the DOM
if ( !ap.IN_DOM ) {
// Append the container to its target
ap.TARGET.append( ap.CONTAINER );
// The preview is in the DOM. Set the state to true.
ap.IN_DOM = true;
}
// Adds a class name to show the preview
ap.CONTAINER.classList.add( "show" );
// Create preview actions
ap.createActions( url );
};
ap.createActions = function( url ) {
};
ap.hidePreview = function( ) {
if ( !ap.HOVER ) return;
// Sets the hover state to false
ap.HOVER = false;
// Empties the content area
ap.CONTENT.innerHTML = "";
// Hides the article preview
ap.CONTAINER.classList.remove( "show" );
};
// Sets all configuration options
ap.setOptions( options );
// Create a loader to initialize the script
ap.LOADER = new Loader( {
SCRIPTS: ap.SCRIPTS,
MODULES: ap.MODULES,
STYLESHEETS: ap.STYLESHEETS
}, ap.init, ap );
// Initializes the script loader
ap.LOADER.init( );
// Fires the ready hook
mw.hook( "articlePreview.ready" ).fire( this );
}
window.ArticlePreview = ArticlePreview;
window.ap = new ArticlePreview( window.articlePreview );
} )( window, jQuery, mediaWiki );