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