summaryrefslogtreecommitdiff
path: root/resources/src/mediawiki.page/mediawiki.page.image.pagination.js
blob: 9ad9c30a791aa4a49f7c2ec36afbc9377708e6d3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*!
 * Implement AJAX navigation for multi-page images so the user may browse without a full page reload.
 */
( function ( mw, $ ) {
	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 ) );