User:Phiarc/October2011/mabe-october.js

/** * mabe-october.js * Copyright (c) 2011 Marian Beermann * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */

// JS Hint settings (jshint.com) /*global $:true, jQuery:true, browser:true, mw:true */ /*jshint strict:false */

//TODO: // * mobile version (kein resizing, scaling über scaling server etc.) // * compat IE6-8, opera(?), chrome(?)

// CSS is dynamically added at runtime mw.loader.implement( "slideshow", [ "//www.mediawiki.org/w/index.php?title=User:Phiarc/October2011/jquery.cycle.all.js&action=raw&ctype=text/javascript", "//www.mediawiki.org/w/index.php?title=User:Phiarc/October2011/jquery.scrollTo.js&action=raw&ctype=text/javascript" ], {}, {} );

//-- Script entry point mw.loader.using ( "slideshow", function {	jQuery( document ).ready( function ( $ ) { // Custom effect for jQuery.Cycle $.fn.cycle.transitions.SlideshowTransitionFX = function ( $cont, $slides, opts ) { $cont.width; $slides.not( ":eq(" + opts.currSlide + ")" ).css( "opacity", 0 ); opts.before.push( function( curr, next, opts, fwd ) {				if( opts.rev )				{					fwd = !fwd;				}				$.fn.cycle.commonReset( curr, next, opts );				opts.cssBefore.left = fwd ? ( next.cycleW - 1 ) : ( 1 - next.cycleW );				opts.cssBefore.opacity = 0;				opts.animOut.left = fwd ? -curr.cycleW : curr.cycleW;			} ); opts.cssFirst.left = 0; opts.cssBefore.top = 0; opts.animIn.left = 0; opts.animIn.opacity = 1; opts.animOut.top = 0; opts.animOut.opacity = 0; };		//-- Slideshow object var Slideshow = {}; Slideshow.Initialized = false; Slideshow.Mobile = false; Slideshow.WikiPath = "//" + document.domain; Slideshow.CycleOpts = {}; // Simple spin-lock mechanism Slideshow.lock = false; Slideshow.Access = function( fn ) { if( Slideshow.lock ) { window.setTimeout( function {					Slideshow.Access( fn );				}, 50 ); } else { fn; }		};		//-- Config // The config is a mw.Map extended by loading and storing the contained data to cookies // and "global" default values Slideshow.Config = new mw.Map; Slideshow.Config.load = function { Slideshow.Config.values = Slideshow.Config.defaults; $.each( Slideshow.Config.values, function ( eindex, elem ) {				var a = "Slideshow." + eindex;				var b = $.cookie( a );				// Check if cookie is set				if( b !== null ) {					Slideshow.Config.values[ eindex ] = b;				}			} ); };		Slideshow.Config.store = function { $.each( Slideshow.Config.values, function ( eindex, elem ) {				$.cookie( "Slideshow." + eindex, null );				$.cookie( "Slideshow." + eindex, elem, { expires: 3650 } );			} ); };		// Just to implement central default values Slideshow.Config.getOld = Slideshow.Config.get; Slideshow.Config.get = function ( selection ) { var u = this.getOld( selection, Slideshow.Config.defaults[ selection ] ); var v;			switch( u ) { case "true": v = true; break; case "false": v = false; break; default: v = u;			} return v;		}; Slideshow.Config.defaults = { // Commmons "Commons.Enable": true, "Commons.Limit": 10, "Commons.Symbol12px": "http://upload.wikimedia.org/wikipedia/commons/thumb/4/4a/Commons-logo.svg/12px-Commons-logo.svg.png", // Animation "Animation.Wrapper": 450, "Animation.Slides": 500, // Mobile "Mobile.Force": false, "Mobile.Resizing": false, "Mobile.UI.LeftArrow": "http://upload.wikimedia.org/wikipedia/commons/thumb/5/56/Go-previous-grey.svg/40px-Go-previous-grey.svg.png", "Mobile.UI.RightArrow": "http://upload.wikimedia.org/wikipedia/commons/thumb/1/13/Go-next-grey.svg/40px-Go-next-grey.svg.png", // Markup (also contains many i18n-relevant strings) "Markup.Main": '  Close        ', "Markup.Config": ' Slideshow settings  ', // Internationalization "i18n.StartSlideshow": "Start Slideshow", "i18n.Slideshow": "Slideshow", "i18n.SlideshowSettings": "Slideshow settings" };		//-- Objects' functions Slideshow.ShowConfig = function ( e ) { // This maps the properties of Slideshow.Config to their corresponding input field ids var config_map = { "Commons.Enable": "mw-input-sscommons-enable", "Commons.Limit": "mw-input-sscommons-limit", "Animation.Wrapper": "mw-input-ssanimation-wrapper", "Animation.Slides": "mw-input-ssanimation-slides", "Mobile.Force": "mw-input-ssmobile-force", "Mobile.Resizing": "mw-input-ssmobile-resizing" };			function load_fn ( idx, e ) { var a = $( "#" + config_map[ idx ] ); if( a.attr( "type" ) === "checkbox" ) { if( e === true || e === "true") { a.attr( {							value: 1,							checked: "checked"						} ); } else { a.attr( "value", "0" ); a.removeAttr( "checked" ); }				} else { a.val( e ); }			}			function save_fn ( idx, e ) { var a = $( "#" + config_map[ idx ] ); var b = a.val; var c;				switch( a.attr( "type" ) ) { case "checkbox": if( a.filter(":checked").length !== 0 ) { c = true; } else { c = false; }						break; case "number": if( !isNaN( parseInt( b, 10 ) ) ) { c = parseInt( b, 10 ); }						if( idx === "Animation.Slides" && Slideshow.CycleOpts !== undefined) { Slideshow.CycleOpts.speed = Slideshow.CycleOpts.speedIn = Slideshow.CycleOpts.speedOut = c;						} break; default: c = b;				} Slideshow.Config.values[ idx ] = c;			} function hide_fn { $( "#preferences" ).css( "display", "none" ); $( "#pt-slideshow" ).click( Slideshow.ShowConfig ); }			if( $( "#preferences" ).length === 0 ) { $( "#content" ).prepend( Slideshow.Config.get( "Markup.Config" ) ); }			$( "#preferences" ).css( "display", "block" ); $( this ).unbind( e ).click( hide_fn ); $.each( Slideshow.Config.values, load_fn ); $( "#mw-input-sshide" ).click( hide_fn ); $( "#mw-input-sssave" ).click( function {				$.each( Slideshow.Config.values, save_fn );				Slideshow.Config.store;				hide_fn;			} ); };		Slideshow.ArticleHasImages = function { if( ( $( ".thumb .image img" ).length + $( ".infobox .image" ).length ) === 0 ) { return false; }			return true; };		Slideshow.GetThumbnailURL = function ( BaseURL, ImageId, size ) { return BaseURL + "thumb" + ImageId + "/" + size + "px-" + ImageId.split( "/", 4)[3]; };		Slideshow.Display = function ( showv, animatev ) { var show = showv === undefined ? true : showv; var animate = animatev === undefined ? true : animatev; var wrapper = $( "#slideshow-wrapper" ); var dur = Slideshow.Config.get( "Animation.Wrapper" ); // Do we actually need to do anything? if( ( show && wrapper.css( "display" ) === "block" ) || ( !show && wrapper.css( "display" ) === "none" ) ) { return true; }			if( animate ) { if( show ) { wrapper.fadeIn( dur ); } else { wrapper.fadeOut( dur ); }			} else { if( show ) { wrapper.css( "display", "block" ); } else { wrapper.css( "display", "none" ); }			}			if( Slideshow.ShouldResize ) { Slideshow.OnWindowResize( 0 ); }		};		Slideshow.StartSlideshow = function { if( !Slideshow.Initialized ) { //-- Insert markup $( "body" ).append( Slideshow.Config.get( "Markup.Main" ) ); $( "#slideshow-main .slideshow" ).addClass( "loading" ); Slideshow.Display;

Slideshow.OnWindowResize( true ); $( "#slideshow-main .slideshow" ).css( "height", $( "#slideshow-main" ).height ); // Handles resizing if( Slideshow.ShouldResize ) { $( window ).resize( Slideshow.OnWindowResize ); window.onresize = Slideshow.OnWindowResize; setInterval( function {						Slideshow.OnWindowResize;					}, 150 ); }				$( "#slideshow-top, #slideshow-bottom, .close" ).click( Slideshow.CloseSlideshow ); // Do the rest asynchronously to not disturb UI rendering etc.				setTimeout( function {					//-- START OF HELPER FUNCTIONS					function AddImage ( index, obj, caption ) {						// This Regex explained:						// \w{1}\/\w{2}\/[^\/]+						// Well, we may unescape it:						// \w{1}/\w{2}/[^/]+						// What this does:						//  /     - Find a slash						//   \w{1} - Foloowed by any character						//   /     - Followed by a slash						//   \w{2} - Followed by two characters						//   /     - Followed by jet another slash						//   [^/]+ - Followed by anything of any length, as long as it doesn't include a slash						//						// This basically rips of the filename and basis path of an thumb URL						// For example:						//   http://upload.wikimedia.org/wikipedia/commons/thumb/0/0d/Proteles_cristata-map.png/300px-Proteles_cristata-map.png						// The regex would only match						//   0/0d/Proteles_cristata-map.png //						// This allows us to create thumbs of (effectivly) any size and/or // to download the image :-)						// One may ask: Why don't you just use the Special:Filepath interface?						//  Well, this way I do not need to do additional request. Imagine we have an article with						//    25 images, then using that interface would result in doing 25 de facto unnecessary						//    requests. As this script uses already many many requests, I try to avoid unnecessary ones						var FindImgId = /\/\w{1}\/\w{2}\/[^\/]+/;						var Id = {							ImageId: obj.attr( "src" ).match( FindImgId )[ 0 ],							BaseURL: obj.attr( "src" ).split( "thumb", 1 )[ 0 ],							Caption: caption						};						// If it the image is *not* embedded as a thumb, the above method for						//  finding the base URL won't work						if( Id.BaseURL.indexOf( Id.ImageId ) !== -1 ) {							Id.BaseURL = obj.attr( "src" ).split( Id.ImageId, 1 )[ 0 ] + "/";						}						Id.ImageName = Id.ImageId.split( "/", 4 )[ 3 ]; // Create "non-intrusive" jump ID						if( obj.attr( "id" ) !== undefined ) { Id.jumpID = obj.attr( "id" ); } else { Id.jumpID = "image-" + index; obj.attr( "id", Id.jumpID ); }						return Id; }					function AddToSlideshow ( ImageData, index, indirect ) { var indirectv = false; var HTMLString; if ( ( indirect !== undefined ) && indirect ) { indirectv = true; }						HTMLString = "  " + ImageData[ index ].Caption + "   "; if( indirectv ) { Slideshow.CycleOpts.addSlide( HTMLString ); } else { $( ".slideshow" ).append( HTMLString ); }					}					function GetImageDimensions ( index, finalise_fn ) { var Titles = ""; if( index === undefined ) { $.each( Slideshow.ImageData, function ( eindex, elem ) {								if( eindex > 0 ) {									Titles += "|";								}								Titles += "File:" + elem.ImageName;							} ); } else { Titles = "File:" + Slideshow.ImageData[ index ].ImageName; }						// For documentation: http://www.mediawiki.org/wiki/API:Properties#imageinfo_.2F_ii // tl;dr: gets image size // This gets hold of all image information we need with a single query $.ajax( Slideshow.WikiPath + "/w/api.php?action=query&prop=imageinfo&iiprop=size|url&format=xml&titles=" + Titles, {							async: finalise_fn === undefined ? false : true,							dataType: "xml",							success: function ( data ) {								var pages;								pages = $( data.getElementsByTagName( "page" ) );								pages.each( function( eindex, elem ) { var ii = $( this ).find( "imageinfo ii" ); var ai; if( index === undefined ) { ai = eindex; } else { ai = index; }									Slideshow.ImageData[ ai ].Width = ii.attr( "width" ); Slideshow.ImageData[ ai ].Height = ii.attr( "height" ); if( finalise_fn !== undefined ) { finalise_fn( ai ); }								} );							}						} );								}					//-- END OF HELPER FUNCTIONS //-- Rip images from article var Thumbnails = []; var index = 0; var bc = $( "#bodyContent" ); // context optimization // Handles normal thumbs bc.find( ".thumbinner a.image img" ).each( function( eindex, elem ) {									Thumbnails[ index ] = AddImage( index, $( this ), bc.find( ".thumb .thumbcaption:eq(" + eindex + ")" ).text );					AddToSlideshow( Thumbnails, index );					index += 1;				} ); // Handles infoboxes, taxoboxes and such - sadly there is no standard class for infoboxes bc.find( ".infobox, .wikitable, .taxobox, .toccolours" ).each( function ( ibindex, infobox ) {					var InfoboxTitle, InfoboxTitleElem, InfoboxObj;					InfoboxObj = $( infobox );					// The infobox title is nearly always the first cell in the table					InfoboxTitle = InfoboxObj.find( "th" ).first.text;					if( InfoboxTitle === undefined ) {						InfoboxTitle = InfoboxObj.find( "caption" ).first.text;					}					if( InfoboxTitle === undefined ) {						InfoboxTitle = InfoboxObj.find( "td" ).first.text;					}					if( InfoboxTitle === undefined ) {						mw.config.get( "wgTitle" );						}					$( this ).find( ".image img" ).each( function( eindex, elem ) { Thumbnails[ index ] = AddImage( index, $( this ), InfoboxTitle ); AddToSlideshow( Thumbnails, index ); index += 1; } );				} );				// Handles links to Wikimedia Commons bc.find( ".sisterproject a" ).each( function( eindex, elem ) {					var commons_images = 0;					if( $( this ).attr( "href" ).search( /commons.wikimedia.org/ ) !== -1 && Slideshow.Config.get( "Commons.Enable" ) === true ) {						$.ajax( "http://anyorigin.com/get?url=http:" + $( this ).attr( "href" ), { dataType: "jsonp", success: function ( data ) { $( data.contents ).find( ".thumb a.image img" ).each( function( eindex, elem ) {									commons_images += 1;									if( commons_images > Slideshow.Config.get( "Commons.Limit" ) ) {										return false;									}									var entry = AddImage( index, $( this ), $( ".gallerytext:eq(" + eindex + ") p" ).text + " " );									var already_exists = false;									$.each( Slideshow.ImageData, function( aindex, arrayelem ) { if( arrayelem.ImageId.indexOf( entry.ImageId ) !== -1 ) { already_exists = true; }									} );									if( !already_exists ) {										Slideshow.ImageData[ index ] = entry;										GetImageDimensions( index, function( ii ) { // As GetImageDimensions uses an asynchronous query we need // to delay the execution AddToSlideshow until the query is answered // Luckily I implemented such a feature earlier :-)											AddToSlideshow( Slideshow.ImageData, ii, true );										} ); index += 1; }								} );								Slideshow.OnWindowResize( true );							}						} ); }				} );				Slideshow.ImageData = Thumbnails;				GetImageDimensions;				//-- Click & hover handlers				// Click on the image - open file page in new window/tab				var c = $( "#slideshow-wrapper" );				c.find( ".slide img" ).click( function ( e ) { index = $( this ).parent.parent.attr( "id" ).match( /[0-9]+/ ); window.open( Slideshow.WikiPath + "/wiki/File:" + Slideshow.ImageData[ index ].ImageName ); } );				// Click on the text - jump to the image in the article				c.find( ".slide p" ).click( function ( e ) { Slideshow.Display( false ); index = $( this ).parent.parent.attr( "id" ).match( /[0-9]+/ ); window.location.hash = Slideshow.ImageData[ index ].jumpID; } );				if( Slideshow.Mobile ) {					var arrows;					arrows = c.find( ".left-arrow, .right-arrow, .close" );					arrows.filter( ".left-arrow" ).css( "background-image", "url(" + Slideshow.Config.get( "Mobile.UI.LeftArrow" ) + ")" );					arrows.filter( ".right-arrow" ).css( "background-image", "url(" + Slideshow.Config.get( "Mobile.UI.RightArrow" ) + ")" );					arrows.mouseenter( function ( e ) { $( this ).animate( {							opacity: 0.85						}, 175 ); } );					arrows.mouseleave( function ( e ) { $( this ).animate( {							opacity: 0						}, 175 ); } );				}				//-- Cycle				c.find( ".slideshow" ).cycle( { fx: 'SlideshowTransitionFX', speed: parseInt( Slideshow.Config.get( "Animation.Slides" ), 10 ), timeout: 0, sync: 0, height: '100%', containerResize: false, pager: '.thumbnails', prev: '.left-arrow', next: '.right-arrow', fit: true, before: function ( curr, next, opts ) { Slideshow.CycleOpts = opts; },					pagerAnchorBuilder: function ( id, slide ) { var ThumbWidth, ThumbHeight; var Id = Thumbnails[ id ]; // Calculate width of the thumbnail for a height of exactly 100 pixels ThumbWidth = 100 * ( Id.Width / Id.Height ); // If the original image is too small, use it instead if( ( ThumbWidth >= Id.Width ) && ( Id.Height <= 100 ) ) { return ''; }						if( isNaN( ThumbWidth ) ) { ThumbWidth = 100; }						return ''; },					onPrevNextEvent: function ( isNext, id, slide ) { c.find( ".thumbnails" ).scrollTo( c.find( ".thumbnails a:eq(" + id + ")" ) ); }				} );				$( document ).keypress( function ( e ) { switch( e.keyCode ) { case 27: // Escape Slideshow.Display( false ); break; case 37: // Arrow left case 65: // A							c.find( ".slideshow" ).cycle( "prev" ); break; case 39: // Arrow right case 68: // D							c.find( ".slideshow" ).cycle( "next" ); break; case 40: // Arrow down case 83: // S c.find( ".slide.activeSlide p" ).click; // Does this still work? i see no activeslide class... break; case 38: // Arrow up						case 87: // W							c.find( ".slide.activeSlide img" ).click; break; }				} );				//-- Finish				c.find( "#slideshow-main .slideshow" ).removeClass( "loading" );				Slideshow.Initialized = true;				Slideshow.OnWindowResize( true );				}, Slideshow.Config.get( "Animation.Wrapper" ) ); } else { Slideshow.Display; }		};		Slideshow.CloseSlideshow = function { Slideshow.Display( false ); };		Slideshow.ShouldResize = function { return !Slideshow.Mobile || Slideshow.Config.get( "Mobile.Resizing" ); };		Slideshow.CenterVertically = function ( elem ) { var position; position = $( "#slideshow-main" ).height / 2; position = position - $( elem ).height / 2; $( elem ).css( "top", position ); return true; };		Slideshow.OnWindowResize = function ( e ) { // context optimization, since this function can be called very often and is thus very CPU intensive var c = $( "#slideshow-wrapper" ); var r = Slideshow.ShouldResize; var h = c.find( "#slideshow-main" ).height, w = c.find( "#slideshow-main" ).width; if( e === true ) { r = true; }			c.css( "height", window.innerHeight ); if( !Slideshow.Mobile) { Slideshow.CenterVertically( "#slideshow-main .left-arrow, #slideshow-main .right-arrow" ); c.find( ".slideshow" ).css( "height", h - 150 ); c.find( ".slideshow" ).css( "width", w - 380 ); // minus 380px: // -the arrows are each 150px wide plus 20 pixels margin on the left (respective right) side // -.slideshow has an margin on both sides of 20 pixels } else { c.find( ".slideshow" ).css( {					width: w,					height: h				} ); }			c.find( ".slideshow .slide-wrapper" ).css( "width", c.find( ".slideshow" ).css( "width" ) ); // This function is called once when the slideshow is not fully initialized // So we need to check if it everything is set up... if( Slideshow.Initialized && r ) { var Height, Width, Img, Para, d;				c.find( ".slideshow > .slide" ).each( function( index, element ) {					Img = $( this ).find( "img" ).first;					Para = $( this ).find( "p" );					d = Slideshow.ImageData[ index ];					if ( $( this ).css( "display" ) === "block" ) {						if( Slideshow.Mobile ) {							Height = h;						} else {							Height = ( h - 150 ) - Para.outerHeight;						}						//Width = Height * ( c.Height / c.Width );						Width = Height * ( Img.Width / Img.Height ) ;						if( ( Height > d.Height ) || ( Width > d.Width ) ) {							Height = d.Height;							Width = d.Width;						}						Img.css( { "width": Width, "height": Height } );					}				} );			}		};		//-- End of object // This is the 'real' body of the function // I put everything into this function in order to not pollute the global namespace // (which I personally feel is bad style and the pre-commit checklist also implies it		//  http://www.mediawiki.org/wiki/Manual:Pre-commit_checklist ) Slideshow.Config.load; function MobileScreen { // Checks screen size considering orientation, i.e. so called landsacpe and // portrait orientation function u ( v, w ) { if( screen.availWidth <= w && screen.availHeight <= v ) { return true; } else { if( screen.availWidth <= v && screen.availHeight <= w ) { return true; } else { return false; }				}			}			return u( 960, 640 ); // iPhone 4S, smartphone screen with currently highest resolution }		if( mw.config.get( "wgIsArticle") && ( mw.config.get( "wgNamespaceNumber" ) === 0 ) && Slideshow.ArticleHasImages ) { var css; if( navigator.userAgent.search( /webOS|iPhone|iPod|iPad|Mobile|Android/i ) !== -1 || MobileScreen || Slideshow.Config.get( "Mobile.Force" ) === true ) { Slideshow.Mobile = true; css = "mabe-october-mobile.css"; } else { css = "mabe-october.css"; }			mw.loader.load( "//www.mediawiki.org/w/index.php?action=raw&ctype=text/css&title=User:Phiarc/October2011/" + css, "text/css" ); mw.util.addPortletLink( "p-views",				"#",				Slideshow.Config.get( "i18n.Slideshow" ),				"ca-slideshow",				Slideshow.Config.get( "i18n.StartSlideshow" ),				"",				"#ca-edit" ); // We cannot create links to javascript functions belonging to objects only existing // in the local scope of a (anonymous) function. So we just define // Slideshow.StartSlideshow as the handler for the onclick event... $( "#ca-slideshow" ).click( Slideshow.StartSlideshow ); }		// Adds a portlet link to the personal toolbar for the slideshow config mw.util.addPortletLink( "p-personal",			"#",			Slideshow.Config.get( "i18n.SlideshowSettings" ),			"pt-slideshow",			Slideshow.Config.get( "i18n.SlideshowSettings" ),			"",			"#pt-watchlist" ); $( "#pt-slideshow" ).click( Slideshow.ShowConfig ); } ); } );