From: Timo Tijhof Date: Tue, 29 Sep 2015 03:15:48 +0000 (-0700) Subject: Restructure /resources/src/mediawiki.page/ X-Git-Tag: 1.31.0-rc.0~9687^2 X-Git-Url: http://git.cyclocoop.org//%27http:/code.google.com/p/ie7-js//%27?a=commitdiff_plain;h=46a01c422b1731cf57993f4c7a13a7df43f2befc;p=lhc%2Fweb%2Fwiklou.git Restructure /resources/src/mediawiki.page/ Follows-up 0bfdd927. Change-Id: I65b9343ea002c332323ededf4e328e4463faf698 --- diff --git a/jsduck.json b/jsduck.json index c0641ded40..aac76c57b1 100644 --- a/jsduck.json +++ b/jsduck.json @@ -15,7 +15,6 @@ "resources/src/mediawiki.action", "resources/src/mediawiki.language", "resources/src/mediawiki.messagePoster", - "resources/src/mediawiki.page", "resources/src/mediawiki.special", "resources/src/mediawiki.toolbar", "resources/src/mediawiki.widgets", diff --git a/resources/Resources.php b/resources/Resources.php index 3b3769eeeb..d6e13ed3f4 100644 --- a/resources/Resources.php +++ b/resources/Resources.php @@ -1479,7 +1479,7 @@ return array( /* MediaWiki Page */ 'mediawiki.page.gallery' => array( - 'scripts' => 'resources/src/mediawiki.page/mediawiki.page.gallery.js', + 'scripts' => 'resources/src/mediawiki/page/gallery.js', 'dependencies' => array( 'mediawiki.page.gallery.styles', 'jquery.throttle-debounce', @@ -1487,14 +1487,14 @@ return array( ), 'mediawiki.page.gallery.styles' => array( 'styles' => array( - 'resources/src/mediawiki.page/mediawiki.page.gallery.print.css' => array( 'media' => 'print' ), - 'resources/src/mediawiki.page/mediawiki.page.gallery.css', + 'resources/src/mediawiki/page/gallery-print.css' => array( 'media' => 'print' ), + 'resources/src/mediawiki/page/gallery.css', ), 'position' => 'top', 'targets' => array( 'desktop', 'mobile' ), ), 'mediawiki.page.ready' => array( - 'scripts' => 'resources/src/mediawiki.page/mediawiki.page.ready.js', + 'scripts' => 'resources/src/mediawiki/page/ready.js', 'dependencies' => array( 'jquery.accessKeyLabel', 'jquery.checkboxShiftClick', @@ -1505,13 +1505,13 @@ return array( 'targets' => array( 'desktop', 'mobile' ), ), 'mediawiki.page.startup' => array( - 'scripts' => 'resources/src/mediawiki.page/mediawiki.page.startup.js', + 'scripts' => 'resources/src/mediawiki/page/startup.js', 'dependencies' => 'mediawiki.util', 'position' => 'top', 'targets' => array( 'desktop', 'mobile' ), ), 'mediawiki.page.patrol.ajax' => array( - 'scripts' => 'resources/src/mediawiki.page/mediawiki.page.patrol.ajax.js', + 'scripts' => 'resources/src/mediawiki/page/patrol.js', 'dependencies' => array( 'mediawiki.page.startup', 'mediawiki.api', @@ -1528,7 +1528,7 @@ return array( ), ), 'mediawiki.page.watch.ajax' => array( - 'scripts' => 'resources/src/mediawiki.page/mediawiki.page.watch.ajax.js', + 'scripts' => 'resources/src/mediawiki/page/watch.js', 'dependencies' => array( 'mediawiki.api.watch', 'mediawiki.notify', @@ -1547,7 +1547,7 @@ return array( ), ), 'mediawiki.page.image.pagination' => array( - 'scripts' => 'resources/src/mediawiki.page/mediawiki.page.image.pagination.js', + 'scripts' => 'resources/src/mediawiki/page/image-pagination.js', 'dependencies' => array( 'mediawiki.Uri', 'mediawiki.util', @@ -1772,7 +1772,7 @@ return array( 'position' => 'top', 'styles' => array( // @todo: Remove mediawiki.page.gallery when cache has cleared - 'resources/src/mediawiki.page/mediawiki.page.gallery.print.css' => array( 'media' => 'print' ), + 'resources/src/mediawiki/page/gallery-print.css' => array( 'media' => 'print' ), // @todo: Remove mediawiki.action.view.filepage.print.css when cache has cleared 'resources/src/mediawiki.action/mediawiki.action.view.filepage.print.css' => array( 'media' => 'print' ), @@ -1789,7 +1789,7 @@ return array( 'position' => 'top', 'styles' => array( // @todo: Remove when mediawiki.page.gallery in cached html. - 'resources/src/mediawiki.page/mediawiki.page.gallery.css', + 'resources/src/mediawiki/page/gallery.css', // @todo: Remove mediawiki.action.view.filepage.css // and mediawiki.legacy/images/checker.png when cache has cleared 'resources/src/mediawiki.action/mediawiki.action.view.filepage.css', diff --git a/resources/src/mediawiki.page/mediawiki.page.gallery.css b/resources/src/mediawiki.page/mediawiki.page.gallery.css deleted file mode 100644 index 20deb2142e..0000000000 --- a/resources/src/mediawiki.page/mediawiki.page.gallery.css +++ /dev/null @@ -1,101 +0,0 @@ -/* Galleries */ -/* These display attributes look nonsensical, but are needed to support IE and FF2 */ -/* Don't forget to update mediawiki.page.gallery.print.css */ -li.gallerybox { - vertical-align: top; - display: -moz-inline-box; - display: inline-block; -} - -ul.gallery, -li.gallerybox { - zoom: 1; - *display: inline; -} - -ul.gallery { - margin: 2px; - padding: 2px; - display: block; -} - -li.gallerycaption { - font-weight: bold; - text-align: center; - display: block; - word-wrap: break-word; -} - -li.gallerybox div.thumb { - text-align: center; - border: 1px solid #ccc; - background-color: #f9f9f9; - margin: 2px; -} - -li.gallerybox div.thumb img { - display: block; - margin: 0 auto; -} - -div.gallerytext { - overflow: hidden; - font-size: 94%; - padding: 2px 4px; - word-wrap: break-word; -} - -/* new gallery stuff */ -ul.mw-gallery-nolines li.gallerybox div.thumb { - background-color: transparent; - border: none; -} - -ul.mw-gallery-nolines li.gallerybox div.gallerytext { - text-align: center; -} - -/* height constrained gallery */ - -ul.mw-gallery-packed li.gallerybox div.thumb, -ul.mw-gallery-packed-overlay li.gallerybox div.thumb, -ul.mw-gallery-packed-hover li.gallerybox div.thumb { - background-color: transparent; - border: none; -} - -ul.mw-gallery-packed li.gallerybox div.thumb img, -ul.mw-gallery-packed-overlay li.gallerybox div.thumb img, -ul.mw-gallery-packed-hover li.gallerybox div.thumb img { - margin: 0 auto; -} - -ul.mw-gallery-packed-hover li.gallerybox, -ul.mw-gallery-packed-overlay li.gallerybox { - position: relative; -} - -ul.mw-gallery-packed-hover div.gallerytextwrapper { - overflow: hidden; - height: 0; -} - -ul.mw-gallery-packed-hover li.gallerybox:hover div.gallerytextwrapper, -ul.mw-gallery-packed-overlay li.gallerybox div.gallerytextwrapper, -ul.mw-gallery-packed-hover li.gallerybox.mw-gallery-focused div.gallerytextwrapper { - position: absolute; - background: white; - background: rgba(255, 255, 255, 0.8); - padding: 5px 10px; - bottom: 0; - left: 0; /* Needed for IE */ - height: auto; - font-weight: bold; - margin: 2px; /* correspond to style on div.thumb */ -} - -ul.mw-gallery-packed-hover, -ul.mw-gallery-packed-overlay, -ul.mw-gallery-packed { - text-align: center; -} diff --git a/resources/src/mediawiki.page/mediawiki.page.gallery.js b/resources/src/mediawiki.page/mediawiki.page.gallery.js deleted file mode 100644 index dfccf21516..0000000000 --- a/resources/src/mediawiki.page/mediawiki.page.gallery.js +++ /dev/null @@ -1,268 +0,0 @@ -/*! - * Show gallery captions when focused. Copied directly from jquery.mw-jump.js. - * Also Dynamically resize images to justify them. - */ -( function ( mw, $ ) { - var $galleries, - bound = false, - // Is there a better way to detect a touchscreen? Current check taken from stack overflow. - isTouchScreen = !!( window.ontouchstart !== undefined || - window.DocumentTouch !== undefined && document instanceof window.DocumentTouch - ); - - /** - * Perform the layout justification. - * - * @ignore - * @context {HTMLElement} A `ul.mw-gallery-*` element - */ - function justify() { - var lastTop, - $img, - imgWidth, - imgHeight, - captionWidth, - rows = [], - $gallery = $( this ); - - $gallery.children( 'li' ).each( function () { - // Math.floor to be paranoid if things are off by 0.00000000001 - var top = Math.floor( $( this ).position().top ), - $this = $( this ); - - if ( top !== lastTop ) { - rows[ rows.length ] = []; - lastTop = top; - } - - $img = $this.find( 'div.thumb a.image img' ); - if ( $img.length && $img[ 0 ].height ) { - imgHeight = $img[ 0 ].height; - imgWidth = $img[ 0 ].width; - } else { - // If we don't have a real image, get the containing divs width/height. - // Note that if we do have a real image, using this method will generally - // give the same answer, but can be different in the case of a very - // narrow image where extra padding is added. - imgHeight = $this.children().children( 'div:first' ).height(); - imgWidth = $this.children().children( 'div:first' ).width(); - } - - // Hack to make an edge case work ok - if ( imgHeight < 30 ) { - // Don't try and resize this item. - imgHeight = 0; - } - - captionWidth = $this.children().children( 'div.gallerytextwrapper' ).width(); - rows[ rows.length - 1 ][ rows[ rows.length - 1 ].length ] = { - $elm: $this, - width: $this.outerWidth(), - imgWidth: imgWidth, - // XXX: can divide by 0 ever happen? - aspect: imgWidth / imgHeight, - captionWidth: captionWidth, - height: imgHeight - }; - - // Save all boundaries so we can restore them on window resize - $this.data( 'imgWidth', imgWidth ); - $this.data( 'imgHeight', imgHeight ); - $this.data( 'width', $this.outerWidth() ); - $this.data( 'captionWidth', captionWidth ); - } ); - - ( function () { - var maxWidth, - combinedAspect, - combinedPadding, - curRow, - curRowHeight, - wantedWidth, - preferredHeight, - newWidth, - padding, - $outerDiv, - $innerDiv, - $imageDiv, - $imageElm, - imageElm, - $caption, - i, - j, - avgZoom, - totalZoom = 0; - - for ( i = 0; i < rows.length; i++ ) { - maxWidth = $gallery.width(); - combinedAspect = 0; - combinedPadding = 0; - curRow = rows[ i ]; - curRowHeight = 0; - - for ( j = 0; j < curRow.length; j++ ) { - if ( curRowHeight === 0 ) { - if ( isFinite( curRow[ j ].height ) ) { - // Get the height of this row, by taking the first - // non-out of bounds height - curRowHeight = curRow[ j ].height; - } - } - - if ( curRow[ j ].aspect === 0 || !isFinite( curRow[ j ].aspect ) ) { - // One of the dimensions are 0. Probably should - // not try to resize. - combinedPadding += curRow[ j ].width; - } else { - combinedAspect += curRow[ j ].aspect; - combinedPadding += curRow[ j ].width - curRow[ j ].imgWidth; - } - } - - // Add some padding for inter-element spacing. - combinedPadding += 5 * curRow.length; - wantedWidth = maxWidth - combinedPadding; - preferredHeight = wantedWidth / combinedAspect; - - if ( preferredHeight > curRowHeight * 1.5 ) { - // Only expand at most 1.5 times current size - // As that's as high a resolution as we have. - // Also on the off chance there is a bug in this - // code, would prevent accidentally expanding to - // be 10 billion pixels wide. - if ( i === rows.length - 1 ) { - // If its the last row, and we can't fit it, - // don't make the entire row huge. - avgZoom = ( totalZoom / ( rows.length - 1 ) ) * curRowHeight; - if ( isFinite( avgZoom ) && avgZoom >= 1 && avgZoom <= 1.5 ) { - preferredHeight = avgZoom; - } else { - // Probably a single row gallery - preferredHeight = curRowHeight; - } - } else { - preferredHeight = 1.5 * curRowHeight; - } - } - if ( !isFinite( preferredHeight ) ) { - // This *definitely* should not happen. - // Skip this row. - continue; - } - if ( preferredHeight < 5 ) { - // Well something clearly went wrong... - // Skip this row. - continue; - } - - if ( preferredHeight / curRowHeight > 1 ) { - totalZoom += preferredHeight / curRowHeight; - } else { - // If we shrink, still consider that a zoom of 1 - totalZoom += 1; - } - - for ( j = 0; j < curRow.length; j++ ) { - newWidth = preferredHeight * curRow[ j ].aspect; - padding = curRow[ j ].width - curRow[ j ].imgWidth; - $outerDiv = curRow[ j ].$elm; - $innerDiv = $outerDiv.children( 'div' ).first(); - $imageDiv = $innerDiv.children( 'div.thumb' ); - $imageElm = $imageDiv.find( 'img' ).first(); - imageElm = $imageElm.length ? $imageElm[ 0 ] : null; - $caption = $outerDiv.find( 'div.gallerytextwrapper' ); - - // Since we are going to re-adjust the height, the vertical - // centering margins need to be reset. - $imageDiv.children( 'div' ).css( 'margin', '0px auto' ); - - if ( newWidth < 60 || !isFinite( newWidth ) ) { - // Making something skinnier than this will mess up captions, - if ( newWidth < 1 || !isFinite( newWidth ) ) { - $innerDiv.height( preferredHeight ); - // Don't even try and touch the image size if it could mean - // making it disappear. - continue; - } - } else { - $outerDiv.width( newWidth + padding ); - $innerDiv.width( newWidth + padding ); - $imageDiv.width( newWidth ); - $caption.width( curRow[ j ].captionWidth + ( newWidth - curRow[ j ].imgWidth ) ); - } - - if ( imageElm ) { - // We don't always have an img, e.g. in the case of an invalid file. - imageElm.width = newWidth; - imageElm.height = preferredHeight; - } else { - // Not a file box. - $imageDiv.height( preferredHeight ); - } - } - } - }() ); - } - - function handleResizeStart() { - $galleries.children( 'li' ).each( function () { - var imgWidth = $( this ).data( 'imgWidth' ), - imgHeight = $( this ).data( 'imgHeight' ), - width = $( this ).data( 'width' ), - captionWidth = $( this ).data( 'captionWidth' ), - $innerDiv = $( this ).children( 'div' ).first(), - $imageDiv = $innerDiv.children( 'div.thumb' ), - $imageElm, imageElm; - - // Restore original sizes so we can arrange the elements as on freshly loaded page - $( this ).width( width ); - $innerDiv.width( width ); - $imageDiv.width( imgWidth ); - $( this ).find( 'div.gallerytextwrapper' ).width( captionWidth ); - - $imageElm = $( this ).find( 'img' ).first(); - imageElm = $imageElm.length ? $imageElm[ 0 ] : null; - if ( imageElm ) { - imageElm.width = imgWidth; - imageElm.height = imgHeight; - } else { - $imageDiv.height( imgHeight ); - } - } ); - } - - function handleResizeEnd() { - $galleries.each( justify ); - } - - mw.hook( 'wikipage.content' ).add( function ( $content ) { - if ( isTouchScreen ) { - // Always show the caption for a touch screen. - $content.find( 'ul.mw-gallery-packed-hover' ) - .addClass( 'mw-gallery-packed-overlay' ) - .removeClass( 'mw-gallery-packed-hover' ); - } else { - // Note use of just "a", not a.image, since we want this to trigger if a link in - // the caption receives focus - $content.find( 'ul.mw-gallery-packed-hover li.gallerybox' ).on( 'focus blur', 'a', function ( e ) { - // Confusingly jQuery leaves e.type as focusout for delegated blur events - var gettingFocus = e.type !== 'blur' && e.type !== 'focusout'; - $( this ).closest( 'li.gallerybox' ).toggleClass( 'mw-gallery-focused', gettingFocus ); - } ); - } - - $galleries = $content.find( 'ul.mw-gallery-packed-overlay, ul.mw-gallery-packed-hover, ul.mw-gallery-packed' ); - // Call the justification asynchronous because live preview fires the hook with detached $content. - setTimeout( function () { - $galleries.each( justify ); - - // Bind here instead of in the top scope as the callbacks use $galleries. - if ( !bound ) { - bound = true; - $( window ) - .resize( $.debounce( 300, true, handleResizeStart ) ) - .resize( $.debounce( 300, handleResizeEnd ) ); - } - } ); - } ); -}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki.page/mediawiki.page.gallery.print.css b/resources/src/mediawiki.page/mediawiki.page.gallery.print.css deleted file mode 100644 index 0c14865e79..0000000000 --- a/resources/src/mediawiki.page/mediawiki.page.gallery.print.css +++ /dev/null @@ -1,35 +0,0 @@ -li.gallerybox { - vertical-align: top; - display: inline-block; -} - -ul.gallery, li.gallerybox { - zoom: 1; - *display: inline; -} - -ul.gallery { - margin: 2px; - padding: 2px; - display: block; -} - -li.gallerycaption { - font-weight: bold; - text-align: center; - display: block; - word-wrap: break-word; -} - -li.gallerybox div.thumb { - text-align: center; - border: 1px solid #ccc; - margin: 2px; -} - -div.gallerytext { - overflow: hidden; - font-size: 94%; - padding: 2px 4px; - word-wrap: break-word; -} diff --git a/resources/src/mediawiki.page/mediawiki.page.image.pagination.js b/resources/src/mediawiki.page/mediawiki.page.image.pagination.js deleted file mode 100644 index 49a51dfc23..0000000000 --- a/resources/src/mediawiki.page/mediawiki.page.image.pagination.js +++ /dev/null @@ -1,143 +0,0 @@ -/*! - * Implement AJAX navigation for multi-page images so the user may browse without a full page reload. - */ -( function ( mw, $ ) { - /*jshint latedef:false */ - var jqXhr, $multipageimage, $spinner, - cache = {}, - cacheOrder = []; - - /* Fetch the next page, caching up to 10 last-loaded pages. - * @param {string} url - * @return {jQuery.Promise} - */ - function fetchPageData( url ) { - if ( jqXhr && jqXhr.abort ) { - // Prevent race conditions and piling up pending requests - jqXhr.abort(); - } - jqXhr = undefined; - - // Try the cache - if ( cache[ url ] ) { - // Update access freshness - cacheOrder.splice( $.inArray( url, cacheOrder ), 1 ); - cacheOrder.push( url ); - return $.Deferred().resolve( cache[ url ] ).promise(); - } - - // @todo Don't fetch the entire page. Ideally we'd only fetch the content portion or the data - // (thumbnail urls) and update the interface manually. - jqXhr = $.ajax( url ).then( function ( data ) { - return $( data ).find( 'table.multipageimage' ).contents(); - } ); - - // Handle cache updates - jqXhr.done( function ( $contents ) { - jqXhr = undefined; - - // Cache the newly loaded page - cache[ url ] = $contents; - cacheOrder.push( url ); - - // Remove the oldest entry if we're over the limit - if ( cacheOrder.length > 10 ) { - delete cache[ cacheOrder[ 0 ] ]; - cacheOrder = cacheOrder.slice( 1 ); - } - } ); - - return jqXhr.promise(); - } - - /* Fetch the next page and use jQuery to swap the table.multipageimage contents. - * @param {string} url - * @param {boolean} [hist=false] Whether this is a load triggered by history navigation (if - * true, this function won't push a new history state, for the browser did so already). - */ - function switchPage( url, hist ) { - var $tr, promise; - - // Start fetching data (might be cached) - promise = fetchPageData( url ); - - // Add a new spinner if one doesn't already exist and the data is not already ready - if ( !$spinner && promise.state() !== 'resolved' ) { - $tr = $multipageimage.find( 'tr' ); - $spinner = $.createSpinner( { - size: 'large', - type: 'block' - } ) - // Copy the old content dimensions equal so that the current scroll position is not - // lost between emptying the table is and receiving the new contents. - .css( { - height: $tr.outerHeight(), - width: $tr.outerWidth() - } ); - - $multipageimage.empty().append( $spinner ); - } - - promise.done( function ( $contents ) { - $spinner = undefined; - - // Replace table contents - $multipageimage.empty().append( $contents.clone() ); - - bindPageNavigation( $multipageimage ); - - // Fire hook because the page's content has changed - mw.hook( 'wikipage.content' ).fire( $multipageimage ); - - // Update browser history and address bar. But not if we came here from a history - // event, in which case the url is already updated by the browser. - if ( history.pushState && !hist ) { - history.pushState( { tag: 'mw-pagination' }, document.title, url ); - } - } ); - } - - function bindPageNavigation( $container ) { - $container.find( '.multipageimagenavbox' ).one( 'click', 'a', function ( e ) { - var page, uri; - - // Generate the same URL on client side as the one generated in ImagePage::openShowImage. - // We avoid using the URL in the link directly since it could have been manipulated (bug 66608) - page = Number( mw.util.getParamValue( 'page', this.href ) ); - uri = new mw.Uri( mw.util.wikiScript() ) - .extend( { title: mw.config.get( 'wgPageName' ), page: page } ) - .toString(); - - switchPage( uri ); - e.preventDefault(); - } ); - - $container.find( 'form[name="pageselector"]' ).one( 'change submit', function ( e ) { - switchPage( this.action + '?' + $( this ).serialize() ); - e.preventDefault(); - } ); - } - - $( function () { - if ( mw.config.get( 'wgNamespaceNumber' ) !== 6 ) { - return; - } - $multipageimage = $( 'table.multipageimage' ); - if ( !$multipageimage.length ) { - return; - } - - bindPageNavigation( $multipageimage ); - - // Update the url using the History API (if available) - if ( history.pushState && history.replaceState ) { - history.replaceState( { tag: 'mw-pagination' }, '' ); - $( window ).on( 'popstate', function ( e ) { - var state = e.originalEvent.state; - if ( state && state.tag === 'mw-pagination' ) { - switchPage( location.href, true ); - } - } ); - } - } ); -}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki.page/mediawiki.page.patrol.ajax.js b/resources/src/mediawiki.page/mediawiki.page.patrol.ajax.js deleted file mode 100644 index f9b0d3564b..0000000000 --- a/resources/src/mediawiki.page/mediawiki.page.patrol.ajax.js +++ /dev/null @@ -1,65 +0,0 @@ -/*! - * Animate patrol links to use asynchronous API requests to - * patrol pages, rather than navigating to a different URI. - * - * @since 1.21 - * @author Marius Hoch - */ -( function ( mw, $ ) { - if ( !mw.user.tokens.exists( 'patrolToken' ) ) { - // Current user has no patrol right, or an old cached version of user.tokens - // that didn't have patrolToken yet. - return; - } - $( function () { - var $patrolLinks = $( '.patrollink a' ); - $patrolLinks.on( 'click', function ( e ) { - var $spinner, href, rcid, apiRequest; - - // Start preloading the notification module (normally loaded by mw.notify()) - mw.loader.load( 'mediawiki.notification' ); - - // Hide the link and create a spinner to show it inside the brackets. - $spinner = $.createSpinner( { - size: 'small', - type: 'inline' - } ); - $( this ).hide().after( $spinner ); - - href = $( this ).attr( 'href' ); - rcid = mw.util.getParamValue( 'rcid', href ); - apiRequest = new mw.Api(); - - apiRequest.postWithToken( 'patrol', { - action: 'patrol', - rcid: rcid - } ) - .done( function ( data ) { - // Remove all patrollinks from the page (including any spinners inside). - $patrolLinks.closest( '.patrollink' ).remove(); - if ( data.patrol !== undefined ) { - // Success - var title = new mw.Title( data.patrol.title ); - mw.notify( mw.msg( 'markedaspatrollednotify', title.toText() ) ); - } else { - // This should never happen as errors should trigger fail - mw.notify( mw.msg( 'markedaspatrollederrornotify' ), { type: 'error' } ); - } - } ) - .fail( function ( error ) { - $spinner.remove(); - // Restore the patrol link. This allows the user to try again - // (or open it in a new window, bypassing this ajax module). - $patrolLinks.show(); - if ( error === 'noautopatrol' ) { - // Can't patrol own - mw.notify( mw.msg( 'markedaspatrollederror-noautopatrol' ), { type: 'warn' } ); - } else { - mw.notify( mw.msg( 'markedaspatrollederrornotify' ), { type: 'error' } ); - } - } ); - - e.preventDefault(); - } ); - } ); -}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki.page/mediawiki.page.ready.js b/resources/src/mediawiki.page/mediawiki.page.ready.js deleted file mode 100644 index 8ec4cf0c67..0000000000 --- a/resources/src/mediawiki.page/mediawiki.page.ready.js +++ /dev/null @@ -1,74 +0,0 @@ -( function ( mw, $ ) { - var supportsPlaceholder = 'placeholder' in document.createElement( 'input' ); - - // Break out of framesets - if ( mw.config.get( 'wgBreakFrames' ) ) { - // Note: In IE < 9 strict comparison to window is non-standard (the standard didn't exist yet) - // it works only comparing to window.self or window.window (http://stackoverflow.com/q/4850978/319266) - if ( window.top !== window.self ) { - // Un-trap us from framesets - window.top.location.href = location.href; - } - } - - mw.hook( 'wikipage.content' ).add( function ( $content ) { - var $sortableTables; - - // Run jquery.placeholder polyfill if placeholder is not supported - if ( !supportsPlaceholder ) { - $content.find( 'input[placeholder]' ).placeholder(); - } - - // Run jquery.makeCollapsible - $content.find( '.mw-collapsible' ).makeCollapsible(); - - // Lazy load jquery.tablesorter - $sortableTables = $content.find( 'table.sortable' ); - if ( $sortableTables.length ) { - mw.loader.using( 'jquery.tablesorter', function () { - $sortableTables.tablesorter(); - } ); - } - - // Run jquery.checkboxShiftClick - $content.find( 'input[type="checkbox"]:not(.noshiftselect)' ).checkboxShiftClick(); - } ); - - // Things outside the wikipage content - $( function () { - var $nodes; - - if ( !supportsPlaceholder ) { - // Exclude content to avoid hitting it twice for the (first) wikipage content - $( 'input[placeholder]' ).not( '#mw-content-text input' ).placeholder(); - } - - // Add accesskey hints to the tooltips - if ( document.querySelectorAll ) { - // If we're running on a browser where we can do this efficiently, - // just find all elements that have accesskeys. We can't use jQuery's - // polyfill for the selector since looping over all elements on page - // load might be too slow. - $nodes = $( document.querySelectorAll( '[accesskey]' ) ); - } else { - // Otherwise go through some elements likely to have accesskeys rather - // than looping over all of them. Unfortunately this will not fully - // work for custom skins with different HTML structures. Input, label - // and button should be rare enough that no optimizations are needed. - $nodes = $( '#column-one a, #mw-head a, #mw-panel a, #p-logo a, input, label, button' ); - } - $nodes.updateTooltipAccessKeys(); - - // Infuse OOUI widgets, if any are present - $nodes = $( '[data-ooui]' ); - if ( $nodes.length ) { - mw.loader.using( 'mediawiki.widgets' ).done( function () { - $nodes.each( function () { - OO.ui.infuse( this ); - } ); - } ); - } - - } ); - -}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki.page/mediawiki.page.startup.js b/resources/src/mediawiki.page/mediawiki.page.startup.js deleted file mode 100644 index 708dcb5c30..0000000000 --- a/resources/src/mediawiki.page/mediawiki.page.startup.js +++ /dev/null @@ -1,32 +0,0 @@ -( function ( mw, $ ) { - - // Support: MediaWiki < 1.26 - // Cached HTML will not yet have this from OutputPage::getHeadScripts. - document.documentElement.className = document.documentElement.className - .replace( /(^|\s)client-nojs(\s|$)/, '$1client-js$2' ); - - mw.page = {}; - - $( function () { - mw.util.init(); - - /** - * Fired when wiki content is being added to the DOM - * - * It is encouraged to fire it before the main DOM is changed (when $content - * is still detatched). However, this order is not defined either way, so you - * should only rely on $content itself. - * - * This includes the ready event on a page load (including post-edit loads) - * and when content has been previewed with LivePreview. - * - * @event wikipage_content - * @member mw.hook - * @param {jQuery} $content The most appropriate element containing the content, - * such as #mw-content-text (regular content root) or #wikiPreview (live preview - * root) - */ - mw.hook( 'wikipage.content' ).fire( $( '#mw-content-text' ) ); - } ); - -}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki.page/mediawiki.page.watch.ajax.js b/resources/src/mediawiki.page/mediawiki.page.watch.ajax.js deleted file mode 100644 index a3197da367..0000000000 --- a/resources/src/mediawiki.page/mediawiki.page.watch.ajax.js +++ /dev/null @@ -1,181 +0,0 @@ -/** - * Animate watch/unwatch links to use asynchronous API requests to - * watch pages, rather than navigating to a different URI. - * - * @class mw.page.watch.ajax - */ -( function ( mw, $ ) { - // The name of the page to watch or unwatch - var title = mw.config.get( 'wgRelevantPageName' ); - - /** - * Update the link text, link href attribute and (if applicable) - * "loading" class. - * - * @param {jQuery} $link Anchor tag of (un)watch link - * @param {string} action One of 'watch', 'unwatch' - * @param {string} [state="idle"] 'idle' or 'loading'. Default is 'idle' - */ - function updateWatchLink( $link, action, state ) { - var msgKey, $li, otherAction; - - // A valid but empty jQuery object shouldn't throw a TypeError - if ( !$link.length ) { - return; - } - - // Invalid actions shouldn't silently turn the page in an unrecoverable state - if ( action !== 'watch' && action !== 'unwatch' ) { - throw new Error( 'Invalid action' ); - } - - // message keys 'watch', 'watching', 'unwatch' or 'unwatching'. - msgKey = state === 'loading' ? action + 'ing' : action; - otherAction = action === 'watch' ? 'unwatch' : 'watch'; - $li = $link.closest( 'li' ); - - // Trigger a 'watchpage' event for this List item. - // Announce the otherAction value as the first param. - // Used to monitor the state of watch link. - // TODO: Revise when system wide hooks are implemented - if ( state === undefined ) { - $li.trigger( 'watchpage.mw', otherAction ); - } - - $link - .text( mw.msg( msgKey ) ) - .attr( 'title', mw.msg( 'tooltip-ca-' + action ) ) - .updateTooltipAccessKeys() - .attr( 'href', mw.util.wikiScript() + '?' + $.param( { - title: title, - action: action - } ) - ); - - // Most common ID style - if ( $li.prop( 'id' ) === 'ca-' + otherAction ) { - $li.prop( 'id', 'ca-' + action ); - } - - if ( state === 'loading' ) { - $link.addClass( 'loading' ); - } else { - $link.removeClass( 'loading' ); - } - } - - /** - * TODO: This should be moved somewhere more accessible. - * - * @private - * @param {string} url - * @return {string} The extracted action, defaults to 'view' - */ - function mwUriGetAction( url ) { - var action, actionPaths, key, i, m, parts; - - // TODO: Does MediaWiki give action path or query param - // precedence? If the former, move this to the bottom - action = mw.util.getParamValue( 'action', url ); - if ( action !== null ) { - return action; - } - - actionPaths = mw.config.get( 'wgActionPaths' ); - for ( key in actionPaths ) { - if ( actionPaths.hasOwnProperty( key ) ) { - parts = actionPaths[ key ].split( '$1' ); - for ( i = 0; i < parts.length; i++ ) { - parts[ i ] = mw.RegExp.escape( parts[ i ] ); - } - m = new RegExp( parts.join( '(.+)' ) ).exec( url ); - if ( m && m[ 1 ] ) { - return key; - } - - } - } - - return 'view'; - } - - // Expose public methods - mw.page.watch = { - updateWatchLink: updateWatchLink - }; - - $( function () { - var $links = $( '.mw-watchlink a, a.mw-watchlink, ' + - '#ca-watch a, #ca-unwatch a, #mw-unwatch-link1, ' + - '#mw-unwatch-link2, #mw-watch-link2, #mw-watch-link1' ); - - // Allowing people to add inline animated links is a little scary - $links = $links.filter( ':not( #bodyContent *, #content * )' ); - - $links.click( function ( e ) { - var action, api, $link; - - // Start preloading the notification module (normally loaded by mw.notify()) - mw.loader.load( 'mediawiki.notification' ); - - action = mwUriGetAction( this.href ); - - if ( action !== 'watch' && action !== 'unwatch' ) { - // Could not extract target action from link url, - // let native browsing handle it further - return true; - } - e.preventDefault(); - e.stopPropagation(); - - $link = $( this ); - - if ( $link.hasClass( 'loading' ) ) { - return; - } - - updateWatchLink( $link, action, 'loading' ); - - api = new mw.Api(); - - api[ action ]( title ) - .done( function ( watchResponse ) { - var otherAction = action === 'watch' ? 'unwatch' : 'watch'; - - mw.notify( $.parseHTML( watchResponse.message ), { - tag: 'watch-self' - } ); - - // Set link to opposite - updateWatchLink( $link, otherAction ); - - // Update the "Watch this page" checkbox on action=edit when the - // page is watched or unwatched via the tab (bug 12395). - $( '#wpWatchthis' ).prop( 'checked', watchResponse.watched !== undefined ); - } ) - .fail( function () { - var cleanTitle, msg, link; - - // Reset link to non-loading mode - updateWatchLink( $link, action ); - - // Format error message - cleanTitle = title.replace( /_/g, ' ' ); - link = mw.html.element( - 'a', { - href: mw.util.getUrl( title ), - title: cleanTitle - }, cleanTitle - ); - msg = mw.message( 'watcherrortext', link ); - - // Report to user about the error - mw.notify( msg, { - tag: 'watch-self', - type: 'error' - } ); - } ); - } ); - } ); - -}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/page/gallery-print.css b/resources/src/mediawiki/page/gallery-print.css new file mode 100644 index 0000000000..0c14865e79 --- /dev/null +++ b/resources/src/mediawiki/page/gallery-print.css @@ -0,0 +1,35 @@ +li.gallerybox { + vertical-align: top; + display: inline-block; +} + +ul.gallery, li.gallerybox { + zoom: 1; + *display: inline; +} + +ul.gallery { + margin: 2px; + padding: 2px; + display: block; +} + +li.gallerycaption { + font-weight: bold; + text-align: center; + display: block; + word-wrap: break-word; +} + +li.gallerybox div.thumb { + text-align: center; + border: 1px solid #ccc; + margin: 2px; +} + +div.gallerytext { + overflow: hidden; + font-size: 94%; + padding: 2px 4px; + word-wrap: break-word; +} diff --git a/resources/src/mediawiki/page/gallery.css b/resources/src/mediawiki/page/gallery.css new file mode 100644 index 0000000000..20deb2142e --- /dev/null +++ b/resources/src/mediawiki/page/gallery.css @@ -0,0 +1,101 @@ +/* Galleries */ +/* These display attributes look nonsensical, but are needed to support IE and FF2 */ +/* Don't forget to update mediawiki.page.gallery.print.css */ +li.gallerybox { + vertical-align: top; + display: -moz-inline-box; + display: inline-block; +} + +ul.gallery, +li.gallerybox { + zoom: 1; + *display: inline; +} + +ul.gallery { + margin: 2px; + padding: 2px; + display: block; +} + +li.gallerycaption { + font-weight: bold; + text-align: center; + display: block; + word-wrap: break-word; +} + +li.gallerybox div.thumb { + text-align: center; + border: 1px solid #ccc; + background-color: #f9f9f9; + margin: 2px; +} + +li.gallerybox div.thumb img { + display: block; + margin: 0 auto; +} + +div.gallerytext { + overflow: hidden; + font-size: 94%; + padding: 2px 4px; + word-wrap: break-word; +} + +/* new gallery stuff */ +ul.mw-gallery-nolines li.gallerybox div.thumb { + background-color: transparent; + border: none; +} + +ul.mw-gallery-nolines li.gallerybox div.gallerytext { + text-align: center; +} + +/* height constrained gallery */ + +ul.mw-gallery-packed li.gallerybox div.thumb, +ul.mw-gallery-packed-overlay li.gallerybox div.thumb, +ul.mw-gallery-packed-hover li.gallerybox div.thumb { + background-color: transparent; + border: none; +} + +ul.mw-gallery-packed li.gallerybox div.thumb img, +ul.mw-gallery-packed-overlay li.gallerybox div.thumb img, +ul.mw-gallery-packed-hover li.gallerybox div.thumb img { + margin: 0 auto; +} + +ul.mw-gallery-packed-hover li.gallerybox, +ul.mw-gallery-packed-overlay li.gallerybox { + position: relative; +} + +ul.mw-gallery-packed-hover div.gallerytextwrapper { + overflow: hidden; + height: 0; +} + +ul.mw-gallery-packed-hover li.gallerybox:hover div.gallerytextwrapper, +ul.mw-gallery-packed-overlay li.gallerybox div.gallerytextwrapper, +ul.mw-gallery-packed-hover li.gallerybox.mw-gallery-focused div.gallerytextwrapper { + position: absolute; + background: white; + background: rgba(255, 255, 255, 0.8); + padding: 5px 10px; + bottom: 0; + left: 0; /* Needed for IE */ + height: auto; + font-weight: bold; + margin: 2px; /* correspond to style on div.thumb */ +} + +ul.mw-gallery-packed-hover, +ul.mw-gallery-packed-overlay, +ul.mw-gallery-packed { + text-align: center; +} diff --git a/resources/src/mediawiki/page/gallery.js b/resources/src/mediawiki/page/gallery.js new file mode 100644 index 0000000000..dfccf21516 --- /dev/null +++ b/resources/src/mediawiki/page/gallery.js @@ -0,0 +1,268 @@ +/*! + * Show gallery captions when focused. Copied directly from jquery.mw-jump.js. + * Also Dynamically resize images to justify them. + */ +( function ( mw, $ ) { + var $galleries, + bound = false, + // Is there a better way to detect a touchscreen? Current check taken from stack overflow. + isTouchScreen = !!( window.ontouchstart !== undefined || + window.DocumentTouch !== undefined && document instanceof window.DocumentTouch + ); + + /** + * Perform the layout justification. + * + * @ignore + * @context {HTMLElement} A `ul.mw-gallery-*` element + */ + function justify() { + var lastTop, + $img, + imgWidth, + imgHeight, + captionWidth, + rows = [], + $gallery = $( this ); + + $gallery.children( 'li' ).each( function () { + // Math.floor to be paranoid if things are off by 0.00000000001 + var top = Math.floor( $( this ).position().top ), + $this = $( this ); + + if ( top !== lastTop ) { + rows[ rows.length ] = []; + lastTop = top; + } + + $img = $this.find( 'div.thumb a.image img' ); + if ( $img.length && $img[ 0 ].height ) { + imgHeight = $img[ 0 ].height; + imgWidth = $img[ 0 ].width; + } else { + // If we don't have a real image, get the containing divs width/height. + // Note that if we do have a real image, using this method will generally + // give the same answer, but can be different in the case of a very + // narrow image where extra padding is added. + imgHeight = $this.children().children( 'div:first' ).height(); + imgWidth = $this.children().children( 'div:first' ).width(); + } + + // Hack to make an edge case work ok + if ( imgHeight < 30 ) { + // Don't try and resize this item. + imgHeight = 0; + } + + captionWidth = $this.children().children( 'div.gallerytextwrapper' ).width(); + rows[ rows.length - 1 ][ rows[ rows.length - 1 ].length ] = { + $elm: $this, + width: $this.outerWidth(), + imgWidth: imgWidth, + // XXX: can divide by 0 ever happen? + aspect: imgWidth / imgHeight, + captionWidth: captionWidth, + height: imgHeight + }; + + // Save all boundaries so we can restore them on window resize + $this.data( 'imgWidth', imgWidth ); + $this.data( 'imgHeight', imgHeight ); + $this.data( 'width', $this.outerWidth() ); + $this.data( 'captionWidth', captionWidth ); + } ); + + ( function () { + var maxWidth, + combinedAspect, + combinedPadding, + curRow, + curRowHeight, + wantedWidth, + preferredHeight, + newWidth, + padding, + $outerDiv, + $innerDiv, + $imageDiv, + $imageElm, + imageElm, + $caption, + i, + j, + avgZoom, + totalZoom = 0; + + for ( i = 0; i < rows.length; i++ ) { + maxWidth = $gallery.width(); + combinedAspect = 0; + combinedPadding = 0; + curRow = rows[ i ]; + curRowHeight = 0; + + for ( j = 0; j < curRow.length; j++ ) { + if ( curRowHeight === 0 ) { + if ( isFinite( curRow[ j ].height ) ) { + // Get the height of this row, by taking the first + // non-out of bounds height + curRowHeight = curRow[ j ].height; + } + } + + if ( curRow[ j ].aspect === 0 || !isFinite( curRow[ j ].aspect ) ) { + // One of the dimensions are 0. Probably should + // not try to resize. + combinedPadding += curRow[ j ].width; + } else { + combinedAspect += curRow[ j ].aspect; + combinedPadding += curRow[ j ].width - curRow[ j ].imgWidth; + } + } + + // Add some padding for inter-element spacing. + combinedPadding += 5 * curRow.length; + wantedWidth = maxWidth - combinedPadding; + preferredHeight = wantedWidth / combinedAspect; + + if ( preferredHeight > curRowHeight * 1.5 ) { + // Only expand at most 1.5 times current size + // As that's as high a resolution as we have. + // Also on the off chance there is a bug in this + // code, would prevent accidentally expanding to + // be 10 billion pixels wide. + if ( i === rows.length - 1 ) { + // If its the last row, and we can't fit it, + // don't make the entire row huge. + avgZoom = ( totalZoom / ( rows.length - 1 ) ) * curRowHeight; + if ( isFinite( avgZoom ) && avgZoom >= 1 && avgZoom <= 1.5 ) { + preferredHeight = avgZoom; + } else { + // Probably a single row gallery + preferredHeight = curRowHeight; + } + } else { + preferredHeight = 1.5 * curRowHeight; + } + } + if ( !isFinite( preferredHeight ) ) { + // This *definitely* should not happen. + // Skip this row. + continue; + } + if ( preferredHeight < 5 ) { + // Well something clearly went wrong... + // Skip this row. + continue; + } + + if ( preferredHeight / curRowHeight > 1 ) { + totalZoom += preferredHeight / curRowHeight; + } else { + // If we shrink, still consider that a zoom of 1 + totalZoom += 1; + } + + for ( j = 0; j < curRow.length; j++ ) { + newWidth = preferredHeight * curRow[ j ].aspect; + padding = curRow[ j ].width - curRow[ j ].imgWidth; + $outerDiv = curRow[ j ].$elm; + $innerDiv = $outerDiv.children( 'div' ).first(); + $imageDiv = $innerDiv.children( 'div.thumb' ); + $imageElm = $imageDiv.find( 'img' ).first(); + imageElm = $imageElm.length ? $imageElm[ 0 ] : null; + $caption = $outerDiv.find( 'div.gallerytextwrapper' ); + + // Since we are going to re-adjust the height, the vertical + // centering margins need to be reset. + $imageDiv.children( 'div' ).css( 'margin', '0px auto' ); + + if ( newWidth < 60 || !isFinite( newWidth ) ) { + // Making something skinnier than this will mess up captions, + if ( newWidth < 1 || !isFinite( newWidth ) ) { + $innerDiv.height( preferredHeight ); + // Don't even try and touch the image size if it could mean + // making it disappear. + continue; + } + } else { + $outerDiv.width( newWidth + padding ); + $innerDiv.width( newWidth + padding ); + $imageDiv.width( newWidth ); + $caption.width( curRow[ j ].captionWidth + ( newWidth - curRow[ j ].imgWidth ) ); + } + + if ( imageElm ) { + // We don't always have an img, e.g. in the case of an invalid file. + imageElm.width = newWidth; + imageElm.height = preferredHeight; + } else { + // Not a file box. + $imageDiv.height( preferredHeight ); + } + } + } + }() ); + } + + function handleResizeStart() { + $galleries.children( 'li' ).each( function () { + var imgWidth = $( this ).data( 'imgWidth' ), + imgHeight = $( this ).data( 'imgHeight' ), + width = $( this ).data( 'width' ), + captionWidth = $( this ).data( 'captionWidth' ), + $innerDiv = $( this ).children( 'div' ).first(), + $imageDiv = $innerDiv.children( 'div.thumb' ), + $imageElm, imageElm; + + // Restore original sizes so we can arrange the elements as on freshly loaded page + $( this ).width( width ); + $innerDiv.width( width ); + $imageDiv.width( imgWidth ); + $( this ).find( 'div.gallerytextwrapper' ).width( captionWidth ); + + $imageElm = $( this ).find( 'img' ).first(); + imageElm = $imageElm.length ? $imageElm[ 0 ] : null; + if ( imageElm ) { + imageElm.width = imgWidth; + imageElm.height = imgHeight; + } else { + $imageDiv.height( imgHeight ); + } + } ); + } + + function handleResizeEnd() { + $galleries.each( justify ); + } + + mw.hook( 'wikipage.content' ).add( function ( $content ) { + if ( isTouchScreen ) { + // Always show the caption for a touch screen. + $content.find( 'ul.mw-gallery-packed-hover' ) + .addClass( 'mw-gallery-packed-overlay' ) + .removeClass( 'mw-gallery-packed-hover' ); + } else { + // Note use of just "a", not a.image, since we want this to trigger if a link in + // the caption receives focus + $content.find( 'ul.mw-gallery-packed-hover li.gallerybox' ).on( 'focus blur', 'a', function ( e ) { + // Confusingly jQuery leaves e.type as focusout for delegated blur events + var gettingFocus = e.type !== 'blur' && e.type !== 'focusout'; + $( this ).closest( 'li.gallerybox' ).toggleClass( 'mw-gallery-focused', gettingFocus ); + } ); + } + + $galleries = $content.find( 'ul.mw-gallery-packed-overlay, ul.mw-gallery-packed-hover, ul.mw-gallery-packed' ); + // Call the justification asynchronous because live preview fires the hook with detached $content. + setTimeout( function () { + $galleries.each( justify ); + + // Bind here instead of in the top scope as the callbacks use $galleries. + if ( !bound ) { + bound = true; + $( window ) + .resize( $.debounce( 300, true, handleResizeStart ) ) + .resize( $.debounce( 300, handleResizeEnd ) ); + } + } ); + } ); +}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/page/image-pagination.js b/resources/src/mediawiki/page/image-pagination.js new file mode 100644 index 0000000000..49a51dfc23 --- /dev/null +++ b/resources/src/mediawiki/page/image-pagination.js @@ -0,0 +1,143 @@ +/*! + * Implement AJAX navigation for multi-page images so the user may browse without a full page reload. + */ +( function ( mw, $ ) { + /*jshint latedef:false */ + var jqXhr, $multipageimage, $spinner, + cache = {}, + cacheOrder = []; + + /* Fetch the next page, caching up to 10 last-loaded pages. + * @param {string} url + * @return {jQuery.Promise} + */ + function fetchPageData( url ) { + if ( jqXhr && jqXhr.abort ) { + // Prevent race conditions and piling up pending requests + jqXhr.abort(); + } + jqXhr = undefined; + + // Try the cache + if ( cache[ url ] ) { + // Update access freshness + cacheOrder.splice( $.inArray( url, cacheOrder ), 1 ); + cacheOrder.push( url ); + return $.Deferred().resolve( cache[ url ] ).promise(); + } + + // @todo Don't fetch the entire page. Ideally we'd only fetch the content portion or the data + // (thumbnail urls) and update the interface manually. + jqXhr = $.ajax( url ).then( function ( data ) { + return $( data ).find( 'table.multipageimage' ).contents(); + } ); + + // Handle cache updates + jqXhr.done( function ( $contents ) { + jqXhr = undefined; + + // Cache the newly loaded page + cache[ url ] = $contents; + cacheOrder.push( url ); + + // Remove the oldest entry if we're over the limit + if ( cacheOrder.length > 10 ) { + delete cache[ cacheOrder[ 0 ] ]; + cacheOrder = cacheOrder.slice( 1 ); + } + } ); + + return jqXhr.promise(); + } + + /* Fetch the next page and use jQuery to swap the table.multipageimage contents. + * @param {string} url + * @param {boolean} [hist=false] Whether this is a load triggered by history navigation (if + * true, this function won't push a new history state, for the browser did so already). + */ + function switchPage( url, hist ) { + var $tr, promise; + + // Start fetching data (might be cached) + promise = fetchPageData( url ); + + // Add a new spinner if one doesn't already exist and the data is not already ready + if ( !$spinner && promise.state() !== 'resolved' ) { + $tr = $multipageimage.find( 'tr' ); + $spinner = $.createSpinner( { + size: 'large', + type: 'block' + } ) + // Copy the old content dimensions equal so that the current scroll position is not + // lost between emptying the table is and receiving the new contents. + .css( { + height: $tr.outerHeight(), + width: $tr.outerWidth() + } ); + + $multipageimage.empty().append( $spinner ); + } + + promise.done( function ( $contents ) { + $spinner = undefined; + + // Replace table contents + $multipageimage.empty().append( $contents.clone() ); + + bindPageNavigation( $multipageimage ); + + // Fire hook because the page's content has changed + mw.hook( 'wikipage.content' ).fire( $multipageimage ); + + // Update browser history and address bar. But not if we came here from a history + // event, in which case the url is already updated by the browser. + if ( history.pushState && !hist ) { + history.pushState( { tag: 'mw-pagination' }, document.title, url ); + } + } ); + } + + function bindPageNavigation( $container ) { + $container.find( '.multipageimagenavbox' ).one( 'click', 'a', function ( e ) { + var page, uri; + + // Generate the same URL on client side as the one generated in ImagePage::openShowImage. + // We avoid using the URL in the link directly since it could have been manipulated (bug 66608) + page = Number( mw.util.getParamValue( 'page', this.href ) ); + uri = new mw.Uri( mw.util.wikiScript() ) + .extend( { title: mw.config.get( 'wgPageName' ), page: page } ) + .toString(); + + switchPage( uri ); + e.preventDefault(); + } ); + + $container.find( 'form[name="pageselector"]' ).one( 'change submit', function ( e ) { + switchPage( this.action + '?' + $( this ).serialize() ); + e.preventDefault(); + } ); + } + + $( function () { + if ( mw.config.get( 'wgNamespaceNumber' ) !== 6 ) { + return; + } + $multipageimage = $( 'table.multipageimage' ); + if ( !$multipageimage.length ) { + return; + } + + bindPageNavigation( $multipageimage ); + + // Update the url using the History API (if available) + if ( history.pushState && history.replaceState ) { + history.replaceState( { tag: 'mw-pagination' }, '' ); + $( window ).on( 'popstate', function ( e ) { + var state = e.originalEvent.state; + if ( state && state.tag === 'mw-pagination' ) { + switchPage( location.href, true ); + } + } ); + } + } ); +}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/page/patrol.js b/resources/src/mediawiki/page/patrol.js new file mode 100644 index 0000000000..f9b0d3564b --- /dev/null +++ b/resources/src/mediawiki/page/patrol.js @@ -0,0 +1,65 @@ +/*! + * Animate patrol links to use asynchronous API requests to + * patrol pages, rather than navigating to a different URI. + * + * @since 1.21 + * @author Marius Hoch + */ +( function ( mw, $ ) { + if ( !mw.user.tokens.exists( 'patrolToken' ) ) { + // Current user has no patrol right, or an old cached version of user.tokens + // that didn't have patrolToken yet. + return; + } + $( function () { + var $patrolLinks = $( '.patrollink a' ); + $patrolLinks.on( 'click', function ( e ) { + var $spinner, href, rcid, apiRequest; + + // Start preloading the notification module (normally loaded by mw.notify()) + mw.loader.load( 'mediawiki.notification' ); + + // Hide the link and create a spinner to show it inside the brackets. + $spinner = $.createSpinner( { + size: 'small', + type: 'inline' + } ); + $( this ).hide().after( $spinner ); + + href = $( this ).attr( 'href' ); + rcid = mw.util.getParamValue( 'rcid', href ); + apiRequest = new mw.Api(); + + apiRequest.postWithToken( 'patrol', { + action: 'patrol', + rcid: rcid + } ) + .done( function ( data ) { + // Remove all patrollinks from the page (including any spinners inside). + $patrolLinks.closest( '.patrollink' ).remove(); + if ( data.patrol !== undefined ) { + // Success + var title = new mw.Title( data.patrol.title ); + mw.notify( mw.msg( 'markedaspatrollednotify', title.toText() ) ); + } else { + // This should never happen as errors should trigger fail + mw.notify( mw.msg( 'markedaspatrollederrornotify' ), { type: 'error' } ); + } + } ) + .fail( function ( error ) { + $spinner.remove(); + // Restore the patrol link. This allows the user to try again + // (or open it in a new window, bypassing this ajax module). + $patrolLinks.show(); + if ( error === 'noautopatrol' ) { + // Can't patrol own + mw.notify( mw.msg( 'markedaspatrollederror-noautopatrol' ), { type: 'warn' } ); + } else { + mw.notify( mw.msg( 'markedaspatrollederrornotify' ), { type: 'error' } ); + } + } ); + + e.preventDefault(); + } ); + } ); +}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/page/ready.js b/resources/src/mediawiki/page/ready.js new file mode 100644 index 0000000000..8ec4cf0c67 --- /dev/null +++ b/resources/src/mediawiki/page/ready.js @@ -0,0 +1,74 @@ +( function ( mw, $ ) { + var supportsPlaceholder = 'placeholder' in document.createElement( 'input' ); + + // Break out of framesets + if ( mw.config.get( 'wgBreakFrames' ) ) { + // Note: In IE < 9 strict comparison to window is non-standard (the standard didn't exist yet) + // it works only comparing to window.self or window.window (http://stackoverflow.com/q/4850978/319266) + if ( window.top !== window.self ) { + // Un-trap us from framesets + window.top.location.href = location.href; + } + } + + mw.hook( 'wikipage.content' ).add( function ( $content ) { + var $sortableTables; + + // Run jquery.placeholder polyfill if placeholder is not supported + if ( !supportsPlaceholder ) { + $content.find( 'input[placeholder]' ).placeholder(); + } + + // Run jquery.makeCollapsible + $content.find( '.mw-collapsible' ).makeCollapsible(); + + // Lazy load jquery.tablesorter + $sortableTables = $content.find( 'table.sortable' ); + if ( $sortableTables.length ) { + mw.loader.using( 'jquery.tablesorter', function () { + $sortableTables.tablesorter(); + } ); + } + + // Run jquery.checkboxShiftClick + $content.find( 'input[type="checkbox"]:not(.noshiftselect)' ).checkboxShiftClick(); + } ); + + // Things outside the wikipage content + $( function () { + var $nodes; + + if ( !supportsPlaceholder ) { + // Exclude content to avoid hitting it twice for the (first) wikipage content + $( 'input[placeholder]' ).not( '#mw-content-text input' ).placeholder(); + } + + // Add accesskey hints to the tooltips + if ( document.querySelectorAll ) { + // If we're running on a browser where we can do this efficiently, + // just find all elements that have accesskeys. We can't use jQuery's + // polyfill for the selector since looping over all elements on page + // load might be too slow. + $nodes = $( document.querySelectorAll( '[accesskey]' ) ); + } else { + // Otherwise go through some elements likely to have accesskeys rather + // than looping over all of them. Unfortunately this will not fully + // work for custom skins with different HTML structures. Input, label + // and button should be rare enough that no optimizations are needed. + $nodes = $( '#column-one a, #mw-head a, #mw-panel a, #p-logo a, input, label, button' ); + } + $nodes.updateTooltipAccessKeys(); + + // Infuse OOUI widgets, if any are present + $nodes = $( '[data-ooui]' ); + if ( $nodes.length ) { + mw.loader.using( 'mediawiki.widgets' ).done( function () { + $nodes.each( function () { + OO.ui.infuse( this ); + } ); + } ); + } + + } ); + +}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/page/startup.js b/resources/src/mediawiki/page/startup.js new file mode 100644 index 0000000000..708dcb5c30 --- /dev/null +++ b/resources/src/mediawiki/page/startup.js @@ -0,0 +1,32 @@ +( function ( mw, $ ) { + + // Support: MediaWiki < 1.26 + // Cached HTML will not yet have this from OutputPage::getHeadScripts. + document.documentElement.className = document.documentElement.className + .replace( /(^|\s)client-nojs(\s|$)/, '$1client-js$2' ); + + mw.page = {}; + + $( function () { + mw.util.init(); + + /** + * Fired when wiki content is being added to the DOM + * + * It is encouraged to fire it before the main DOM is changed (when $content + * is still detatched). However, this order is not defined either way, so you + * should only rely on $content itself. + * + * This includes the ready event on a page load (including post-edit loads) + * and when content has been previewed with LivePreview. + * + * @event wikipage_content + * @member mw.hook + * @param {jQuery} $content The most appropriate element containing the content, + * such as #mw-content-text (regular content root) or #wikiPreview (live preview + * root) + */ + mw.hook( 'wikipage.content' ).fire( $( '#mw-content-text' ) ); + } ); + +}( mediaWiki, jQuery ) ); diff --git a/resources/src/mediawiki/page/watch.js b/resources/src/mediawiki/page/watch.js new file mode 100644 index 0000000000..a3197da367 --- /dev/null +++ b/resources/src/mediawiki/page/watch.js @@ -0,0 +1,181 @@ +/** + * Animate watch/unwatch links to use asynchronous API requests to + * watch pages, rather than navigating to a different URI. + * + * @class mw.page.watch.ajax + */ +( function ( mw, $ ) { + // The name of the page to watch or unwatch + var title = mw.config.get( 'wgRelevantPageName' ); + + /** + * Update the link text, link href attribute and (if applicable) + * "loading" class. + * + * @param {jQuery} $link Anchor tag of (un)watch link + * @param {string} action One of 'watch', 'unwatch' + * @param {string} [state="idle"] 'idle' or 'loading'. Default is 'idle' + */ + function updateWatchLink( $link, action, state ) { + var msgKey, $li, otherAction; + + // A valid but empty jQuery object shouldn't throw a TypeError + if ( !$link.length ) { + return; + } + + // Invalid actions shouldn't silently turn the page in an unrecoverable state + if ( action !== 'watch' && action !== 'unwatch' ) { + throw new Error( 'Invalid action' ); + } + + // message keys 'watch', 'watching', 'unwatch' or 'unwatching'. + msgKey = state === 'loading' ? action + 'ing' : action; + otherAction = action === 'watch' ? 'unwatch' : 'watch'; + $li = $link.closest( 'li' ); + + // Trigger a 'watchpage' event for this List item. + // Announce the otherAction value as the first param. + // Used to monitor the state of watch link. + // TODO: Revise when system wide hooks are implemented + if ( state === undefined ) { + $li.trigger( 'watchpage.mw', otherAction ); + } + + $link + .text( mw.msg( msgKey ) ) + .attr( 'title', mw.msg( 'tooltip-ca-' + action ) ) + .updateTooltipAccessKeys() + .attr( 'href', mw.util.wikiScript() + '?' + $.param( { + title: title, + action: action + } ) + ); + + // Most common ID style + if ( $li.prop( 'id' ) === 'ca-' + otherAction ) { + $li.prop( 'id', 'ca-' + action ); + } + + if ( state === 'loading' ) { + $link.addClass( 'loading' ); + } else { + $link.removeClass( 'loading' ); + } + } + + /** + * TODO: This should be moved somewhere more accessible. + * + * @private + * @param {string} url + * @return {string} The extracted action, defaults to 'view' + */ + function mwUriGetAction( url ) { + var action, actionPaths, key, i, m, parts; + + // TODO: Does MediaWiki give action path or query param + // precedence? If the former, move this to the bottom + action = mw.util.getParamValue( 'action', url ); + if ( action !== null ) { + return action; + } + + actionPaths = mw.config.get( 'wgActionPaths' ); + for ( key in actionPaths ) { + if ( actionPaths.hasOwnProperty( key ) ) { + parts = actionPaths[ key ].split( '$1' ); + for ( i = 0; i < parts.length; i++ ) { + parts[ i ] = mw.RegExp.escape( parts[ i ] ); + } + m = new RegExp( parts.join( '(.+)' ) ).exec( url ); + if ( m && m[ 1 ] ) { + return key; + } + + } + } + + return 'view'; + } + + // Expose public methods + mw.page.watch = { + updateWatchLink: updateWatchLink + }; + + $( function () { + var $links = $( '.mw-watchlink a, a.mw-watchlink, ' + + '#ca-watch a, #ca-unwatch a, #mw-unwatch-link1, ' + + '#mw-unwatch-link2, #mw-watch-link2, #mw-watch-link1' ); + + // Allowing people to add inline animated links is a little scary + $links = $links.filter( ':not( #bodyContent *, #content * )' ); + + $links.click( function ( e ) { + var action, api, $link; + + // Start preloading the notification module (normally loaded by mw.notify()) + mw.loader.load( 'mediawiki.notification' ); + + action = mwUriGetAction( this.href ); + + if ( action !== 'watch' && action !== 'unwatch' ) { + // Could not extract target action from link url, + // let native browsing handle it further + return true; + } + e.preventDefault(); + e.stopPropagation(); + + $link = $( this ); + + if ( $link.hasClass( 'loading' ) ) { + return; + } + + updateWatchLink( $link, action, 'loading' ); + + api = new mw.Api(); + + api[ action ]( title ) + .done( function ( watchResponse ) { + var otherAction = action === 'watch' ? 'unwatch' : 'watch'; + + mw.notify( $.parseHTML( watchResponse.message ), { + tag: 'watch-self' + } ); + + // Set link to opposite + updateWatchLink( $link, otherAction ); + + // Update the "Watch this page" checkbox on action=edit when the + // page is watched or unwatched via the tab (bug 12395). + $( '#wpWatchthis' ).prop( 'checked', watchResponse.watched !== undefined ); + } ) + .fail( function () { + var cleanTitle, msg, link; + + // Reset link to non-loading mode + updateWatchLink( $link, action ); + + // Format error message + cleanTitle = title.replace( /_/g, ' ' ); + link = mw.html.element( + 'a', { + href: mw.util.getUrl( title ), + title: cleanTitle + }, cleanTitle + ); + msg = mw.message( 'watcherrortext', link ); + + // Report to user about the error + mw.notify( msg, { + tag: 'watch-self', + type: 'error' + } ); + } ); + } ); + } ); + +}( mediaWiki, jQuery ) );