Merge "Update docs/hooks.txt for ShowSearchHitTitle"
[mediawiki.git] / resources / src / mediawiki / page / image-pagination.js
blob6038a574c8dea6d552d0476a891976534ab2bb21
1 /*!
2  * Implement AJAX navigation for multi-page images so the user may browse without a full page reload.
3  */
5 /* eslint-disable no-use-before-define */
7 ( function ( mw, $ ) {
8         var jqXhr, $multipageimage, $spinner,
9                 cache = {},
10                 cacheOrder = [];
12         /* Fetch the next page, caching up to 10 last-loaded pages.
13          * @param {string} url
14          * @return {jQuery.Promise}
15          */
16         function fetchPageData( url ) {
17                 if ( jqXhr && jqXhr.abort ) {
18                         // Prevent race conditions and piling up pending requests
19                         jqXhr.abort();
20                 }
21                 jqXhr = undefined;
23                 // Try the cache
24                 if ( cache[ url ] ) {
25                         // Update access freshness
26                         cacheOrder.splice( $.inArray( url, cacheOrder ), 1 );
27                         cacheOrder.push( url );
28                         return $.Deferred().resolve( cache[ url ] ).promise();
29                 }
31                 // TODO Don't fetch the entire page. Ideally we'd only fetch the content portion or the data
32                 // (thumbnail urls) and update the interface manually.
33                 jqXhr = $.ajax( url ).then( function ( data ) {
34                         return $( data ).find( 'table.multipageimage' ).contents();
35                 } );
37                 // Handle cache updates
38                 jqXhr.done( function ( $contents ) {
39                         jqXhr = undefined;
41                         // Cache the newly loaded page
42                         cache[ url ] = $contents;
43                         cacheOrder.push( url );
45                         // Remove the oldest entry if we're over the limit
46                         if ( cacheOrder.length > 10 ) {
47                                 delete cache[ cacheOrder[ 0 ] ];
48                                 cacheOrder = cacheOrder.slice( 1 );
49                         }
50                 } );
52                 return jqXhr.promise();
53         }
55         /* Fetch the next page and use jQuery to swap the table.multipageimage contents.
56          * @param {string} url
57          * @param {boolean} [hist=false] Whether this is a load triggered by history navigation (if
58          *   true, this function won't push a new history state, for the browser did so already).
59          */
60         function switchPage( url, hist ) {
61                 var $tr, promise;
63                 // Start fetching data (might be cached)
64                 promise = fetchPageData( url );
66                 // Add a new spinner if one doesn't already exist and the data is not already ready
67                 if ( !$spinner && promise.state() !== 'resolved' ) {
68                         $tr = $multipageimage.find( 'tr' );
69                         $spinner = $.createSpinner( {
70                                 size: 'large',
71                                 type: 'block'
72                         } )
73                                 // Copy the old content dimensions equal so that the current scroll position is not
74                                 // lost between emptying the table is and receiving the new contents.
75                                 .css( {
76                                         height: $tr.outerHeight(),
77                                         width: $tr.outerWidth()
78                                 } );
80                         $multipageimage.empty().append( $spinner );
81                 }
83                 promise.done( function ( $contents ) {
84                         $spinner = undefined;
86                         // Replace table contents
87                         $multipageimage.empty().append( $contents.clone() );
89                         bindPageNavigation( $multipageimage );
91                         // Fire hook because the page's content has changed
92                         mw.hook( 'wikipage.content' ).fire( $multipageimage );
94                         // Update browser history and address bar. But not if we came here from a history
95                         // event, in which case the url is already updated by the browser.
96                         if ( history.pushState && !hist ) {
97                                 history.pushState( { tag: 'mw-pagination' }, document.title, url );
98                         }
99                 } );
100         }
102         function bindPageNavigation( $container ) {
103                 $container.find( '.multipageimagenavbox' ).one( 'click', 'a', function ( e ) {
104                         var page, url;
106                         // Generate the same URL on client side as the one generated in ImagePage::openShowImage.
107                         // We avoid using the URL in the link directly since it could have been manipulated (bug 66608)
108                         page = Number( mw.util.getParamValue( 'page', this.href ) );
109                         url = mw.util.getUrl( mw.config.get( 'wgPageName' ), { page: page } );
111                         switchPage( url );
112                         e.preventDefault();
113                 } );
115                 $container.find( 'form[name="pageselector"]' ).one( 'change submit', function ( e ) {
116                         switchPage( this.action + '?' + $( this ).serialize() );
117                         e.preventDefault();
118                 } );
119         }
121         $( function () {
122                 if ( mw.config.get( 'wgCanonicalNamespace' ) !== 'File' ) {
123                         return;
124                 }
125                 $multipageimage = $( 'table.multipageimage' );
126                 if ( !$multipageimage.length ) {
127                         return;
128                 }
130                 bindPageNavigation( $multipageimage );
132                 // Update the url using the History API (if available)
133                 if ( history.pushState && history.replaceState ) {
134                         history.replaceState( { tag: 'mw-pagination' }, '' );
135                         $( window ).on( 'popstate', function ( e ) {
136                                 var state = e.originalEvent.state;
137                                 if ( state && state.tag === 'mw-pagination' ) {
138                                         switchPage( location.href, true );
139                                 }
140                         } );
141                 }
142         } );
143 }( mediaWiki, jQuery ) );