Merge "SpecialMovepage: Convert form to use OOUI controls"
[lhc/web/wiklou.git] / resources / src / mediawiki.page / mediawiki.page.gallery.js
1 /*!
2 * Show gallery captions when focused. Copied directly from jquery.mw-jump.js.
3 * Also Dynamically resize images to justify them.
4 */
5 ( function ( mw, $ ) {
6 var $galleries,
7 bound = false,
8 // Is there a better way to detect a touchscreen? Current check taken from stack overflow.
9 isTouchScreen = !!( window.ontouchstart !== undefined ||
10 window.DocumentTouch !== undefined && document instanceof window.DocumentTouch
11 );
12
13 /**
14 * Perform the layout justification.
15 *
16 * @ignore
17 * @context {HTMLElement} A `ul.mw-gallery-*` element
18 */
19 function justify() {
20 var lastTop,
21 $img,
22 imgWidth,
23 imgHeight,
24 captionWidth,
25 rows = [],
26 $gallery = $( this );
27
28 $gallery.children( 'li' ).each( function () {
29 // Math.floor to be paranoid if things are off by 0.00000000001
30 var top = Math.floor( $( this ).position().top ),
31 $this = $( this );
32
33 if ( top !== lastTop ) {
34 rows[ rows.length ] = [];
35 lastTop = top;
36 }
37
38 $img = $this.find( 'div.thumb a.image img' );
39 if ( $img.length && $img[ 0 ].height ) {
40 imgHeight = $img[ 0 ].height;
41 imgWidth = $img[ 0 ].width;
42 } else {
43 // If we don't have a real image, get the containing divs width/height.
44 // Note that if we do have a real image, using this method will generally
45 // give the same answer, but can be different in the case of a very
46 // narrow image where extra padding is added.
47 imgHeight = $this.children().children( 'div:first' ).height();
48 imgWidth = $this.children().children( 'div:first' ).width();
49 }
50
51 // Hack to make an edge case work ok
52 if ( imgHeight < 30 ) {
53 // Don't try and resize this item.
54 imgHeight = 0;
55 }
56
57 captionWidth = $this.children().children( 'div.gallerytextwrapper' ).width();
58 rows[ rows.length - 1 ][ rows[ rows.length - 1 ].length ] = {
59 $elm: $this,
60 width: $this.outerWidth(),
61 imgWidth: imgWidth,
62 // XXX: can divide by 0 ever happen?
63 aspect: imgWidth / imgHeight,
64 captionWidth: captionWidth,
65 height: imgHeight
66 };
67
68 // Save all boundaries so we can restore them on window resize
69 $this.data( 'imgWidth', imgWidth );
70 $this.data( 'imgHeight', imgHeight );
71 $this.data( 'width', $this.outerWidth() );
72 $this.data( 'captionWidth', captionWidth );
73 } );
74
75 ( function () {
76 var maxWidth,
77 combinedAspect,
78 combinedPadding,
79 curRow,
80 curRowHeight,
81 wantedWidth,
82 preferredHeight,
83 newWidth,
84 padding,
85 $outerDiv,
86 $innerDiv,
87 $imageDiv,
88 $imageElm,
89 imageElm,
90 $caption,
91 i,
92 j,
93 avgZoom,
94 totalZoom = 0;
95
96 for ( i = 0; i < rows.length; i++ ) {
97 maxWidth = $gallery.width();
98 combinedAspect = 0;
99 combinedPadding = 0;
100 curRow = rows[ i ];
101 curRowHeight = 0;
102
103 for ( j = 0; j < curRow.length; j++ ) {
104 if ( curRowHeight === 0 ) {
105 if ( isFinite( curRow[ j ].height ) ) {
106 // Get the height of this row, by taking the first
107 // non-out of bounds height
108 curRowHeight = curRow[ j ].height;
109 }
110 }
111
112 if ( curRow[ j ].aspect === 0 || !isFinite( curRow[ j ].aspect ) ) {
113 // One of the dimensions are 0. Probably should
114 // not try to resize.
115 combinedPadding += curRow[ j ].width;
116 } else {
117 combinedAspect += curRow[ j ].aspect;
118 combinedPadding += curRow[ j ].width - curRow[ j ].imgWidth;
119 }
120 }
121
122 // Add some padding for inter-element spacing.
123 combinedPadding += 5 * curRow.length;
124 wantedWidth = maxWidth - combinedPadding;
125 preferredHeight = wantedWidth / combinedAspect;
126
127 if ( preferredHeight > curRowHeight * 1.5 ) {
128 // Only expand at most 1.5 times current size
129 // As that's as high a resolution as we have.
130 // Also on the off chance there is a bug in this
131 // code, would prevent accidentally expanding to
132 // be 10 billion pixels wide.
133 if ( i === rows.length - 1 ) {
134 // If its the last row, and we can't fit it,
135 // don't make the entire row huge.
136 avgZoom = ( totalZoom / ( rows.length - 1 ) ) * curRowHeight;
137 if ( isFinite( avgZoom ) && avgZoom >= 1 && avgZoom <= 1.5 ) {
138 preferredHeight = avgZoom;
139 } else {
140 // Probably a single row gallery
141 preferredHeight = curRowHeight;
142 }
143 } else {
144 preferredHeight = 1.5 * curRowHeight;
145 }
146 }
147 if ( !isFinite( preferredHeight ) ) {
148 // This *definitely* should not happen.
149 // Skip this row.
150 continue;
151 }
152 if ( preferredHeight < 5 ) {
153 // Well something clearly went wrong...
154 // Skip this row.
155 continue;
156 }
157
158 if ( preferredHeight / curRowHeight > 1 ) {
159 totalZoom += preferredHeight / curRowHeight;
160 } else {
161 // If we shrink, still consider that a zoom of 1
162 totalZoom += 1;
163 }
164
165 for ( j = 0; j < curRow.length; j++ ) {
166 newWidth = preferredHeight * curRow[ j ].aspect;
167 padding = curRow[ j ].width - curRow[ j ].imgWidth;
168 $outerDiv = curRow[ j ].$elm;
169 $innerDiv = $outerDiv.children( 'div' ).first();
170 $imageDiv = $innerDiv.children( 'div.thumb' );
171 $imageElm = $imageDiv.find( 'img' ).first();
172 imageElm = $imageElm.length ? $imageElm[ 0 ] : null;
173 $caption = $outerDiv.find( 'div.gallerytextwrapper' );
174
175 // Since we are going to re-adjust the height, the vertical
176 // centering margins need to be reset.
177 $imageDiv.children( 'div' ).css( 'margin', '0px auto' );
178
179 if ( newWidth < 60 || !isFinite( newWidth ) ) {
180 // Making something skinnier than this will mess up captions,
181 if ( newWidth < 1 || !isFinite( newWidth ) ) {
182 $innerDiv.height( preferredHeight );
183 // Don't even try and touch the image size if it could mean
184 // making it disappear.
185 continue;
186 }
187 } else {
188 $outerDiv.width( newWidth + padding );
189 $innerDiv.width( newWidth + padding );
190 $imageDiv.width( newWidth );
191 $caption.width( curRow[ j ].captionWidth + ( newWidth - curRow[ j ].imgWidth ) );
192 }
193
194 if ( imageElm ) {
195 // We don't always have an img, e.g. in the case of an invalid file.
196 imageElm.width = newWidth;
197 imageElm.height = preferredHeight;
198 } else {
199 // Not a file box.
200 $imageDiv.height( preferredHeight );
201 }
202 }
203 }
204 }() );
205 }
206
207 function handleResizeStart() {
208 $galleries.children( 'li' ).each( function () {
209 var imgWidth = $( this ).data( 'imgWidth' ),
210 imgHeight = $( this ).data( 'imgHeight' ),
211 width = $( this ).data( 'width' ),
212 captionWidth = $( this ).data( 'captionWidth' ),
213 $innerDiv = $( this ).children( 'div' ).first(),
214 $imageDiv = $innerDiv.children( 'div.thumb' ),
215 $imageElm, imageElm;
216
217 // Restore original sizes so we can arrange the elements as on freshly loaded page
218 $( this ).width( width );
219 $innerDiv.width( width );
220 $imageDiv.width( imgWidth );
221 $( this ).find( 'div.gallerytextwrapper' ).width( captionWidth );
222
223 $imageElm = $( this ).find( 'img' ).first();
224 imageElm = $imageElm.length ? $imageElm[ 0 ] : null;
225 if ( imageElm ) {
226 imageElm.width = imgWidth;
227 imageElm.height = imgHeight;
228 } else {
229 $imageDiv.height( imgHeight );
230 }
231 } );
232 }
233
234 function handleResizeEnd() {
235 $galleries.each( justify );
236 }
237
238 mw.hook( 'wikipage.content' ).add( function ( $content ) {
239 if ( isTouchScreen ) {
240 // Always show the caption for a touch screen.
241 $content.find( 'ul.mw-gallery-packed-hover' )
242 .addClass( 'mw-gallery-packed-overlay' )
243 .removeClass( 'mw-gallery-packed-hover' );
244 } else {
245 // Note use of just "a", not a.image, since we want this to trigger if a link in
246 // the caption receives focus
247 $content.find( 'ul.mw-gallery-packed-hover li.gallerybox' ).on( 'focus blur', 'a', function ( e ) {
248 // Confusingly jQuery leaves e.type as focusout for delegated blur events
249 var gettingFocus = e.type !== 'blur' && e.type !== 'focusout';
250 $( this ).closest( 'li.gallerybox' ).toggleClass( 'mw-gallery-focused', gettingFocus );
251 } );
252 }
253
254 $galleries = $content.find( 'ul.mw-gallery-packed-overlay, ul.mw-gallery-packed-hover, ul.mw-gallery-packed' );
255 // Call the justification asynchronous because live preview fires the hook with detached $content.
256 setTimeout( function () {
257 $galleries.each( justify );
258
259 // Bind here instead of in the top scope as the callbacks use $galleries.
260 if ( !bound ) {
261 bound = true;
262 $( window )
263 .resize( $.debounce( 300, true, handleResizeStart ) )
264 .resize( $.debounce( 300, handleResizeEnd ) );
265 }
266 } );
267 } );
268 }( mediaWiki, jQuery ) );