2 * Enhance MediaWiki galleries (from the `<gallery>` parser tag).
4 * - Toggle gallery captions when focused.
5 * - Dynamically resize images to fill horizontal space.
10 lastWidth
= window
.innerWidth
,
11 justifyNeeded
= false,
12 // Is there a better way to detect a touchscreen? Current check taken from stack overflow.
13 isTouchScreen
= !!( window
.ontouchstart
!== undefined ||
14 window
.DocumentTouch
!== undefined && document
instanceof window
.DocumentTouch
18 * Perform the layout justification.
21 * @context {HTMLElement} A `ul.mw-gallery-*` element
28 $gallery
.children( 'li.gallerybox' ).each( function () {
29 var $img
, imgWidth
, imgHeight
, outerWidth
, captionWidth
,
30 // Math.floor, to be paranoid if things are off by 0.00000000001
31 top
= Math
.floor( $( this ).position().top
),
34 if ( top
!== lastTop
) {
39 $img
= $this.find( 'div.thumb a.image img' );
40 if ( $img
.length
&& $img
[ 0 ].height
) {
41 imgHeight
= $img
[ 0 ].height
;
42 imgWidth
= $img
[ 0 ].width
;
44 // If we don't have a real image, get the containing divs width/height.
45 // Note that if we do have a real image, using this method will generally
46 // give the same answer, but can be different in the case of a very
47 // narrow image where extra padding is added.
48 imgHeight
= $this.children().children( 'div:first' ).height();
49 imgWidth
= $this.children().children( 'div:first' ).width();
52 // Hack to make an edge case work ok
53 if ( imgHeight
< 30 ) {
54 // Don't try and resize this item.
58 captionWidth
= $this.children().children( 'div.gallerytextwrapper' ).width();
59 outerWidth
= $this.outerWidth();
60 rows
[ rows
.length
- 1 ].push( {
64 // FIXME: Deal with devision by 0.
65 aspect
: imgWidth
/ imgHeight
,
66 captionWidth
: captionWidth
,
70 // Save all boundaries so we can restore them on window resize
71 $this.data( 'imgWidth', imgWidth
);
72 $this.data( 'imgHeight', imgHeight
);
73 $this.data( 'width', outerWidth
);
74 $this.data( 'captionWidth', captionWidth
);
98 for ( i
= 0; i
< rows
.length
; i
++ ) {
99 maxWidth
= $gallery
.width();
105 for ( j
= 0; j
< curRow
.length
; j
++ ) {
106 if ( curRowHeight
=== 0 ) {
107 if ( isFinite( curRow
[ j
].height
) ) {
108 // Get the height of this row, by taking the first
109 // non-out of bounds height
110 curRowHeight
= curRow
[ j
].height
;
114 if ( curRow
[ j
].aspect
=== 0 || !isFinite( curRow
[ j
].aspect
) ) {
115 // One of the dimensions are 0. Probably should
116 // not try to resize.
117 combinedPadding
+= curRow
[ j
].width
;
119 combinedAspect
+= curRow
[ j
].aspect
;
120 combinedPadding
+= curRow
[ j
].width
- curRow
[ j
].imgWidth
;
124 // Add some padding for inter-element spacing.
125 combinedPadding
+= 5 * curRow
.length
;
126 wantedWidth
= maxWidth
- combinedPadding
;
127 preferredHeight
= wantedWidth
/ combinedAspect
;
129 if ( preferredHeight
> curRowHeight
* 1.5 ) {
130 // Only expand at most 1.5 times current size
131 // As that's as high a resolution as we have.
132 // Also on the off chance there is a bug in this
133 // code, would prevent accidentally expanding to
134 // be 10 billion pixels wide.
135 if ( i
=== rows
.length
- 1 ) {
136 // If its the last row, and we can't fit it,
137 // don't make the entire row huge.
138 avgZoom
= ( totalZoom
/ ( rows
.length
- 1 ) ) * curRowHeight
;
139 if ( isFinite( avgZoom
) && avgZoom
>= 1 && avgZoom
<= 1.5 ) {
140 preferredHeight
= avgZoom
;
142 // Probably a single row gallery
143 preferredHeight
= curRowHeight
;
146 preferredHeight
= 1.5 * curRowHeight
;
149 if ( !isFinite( preferredHeight
) ) {
150 // This *definitely* should not happen.
154 if ( preferredHeight
< 5 ) {
155 // Well something clearly went wrong...
160 if ( preferredHeight
/ curRowHeight
> 1 ) {
161 totalZoom
+= preferredHeight
/ curRowHeight
;
163 // If we shrink, still consider that a zoom of 1
167 for ( j
= 0; j
< curRow
.length
; j
++ ) {
168 newWidth
= preferredHeight
* curRow
[ j
].aspect
;
169 padding
= curRow
[ j
].width
- curRow
[ j
].imgWidth
;
170 $outerDiv
= curRow
[ j
].$elm
;
171 $innerDiv
= $outerDiv
.children( 'div' ).first();
172 $imageDiv
= $innerDiv
.children( 'div.thumb' );
173 $imageElm
= $imageDiv
.find( 'img' ).first();
174 $caption
= $outerDiv
.find( 'div.gallerytextwrapper' );
176 // Since we are going to re-adjust the height, the vertical
177 // centering margins need to be reset.
178 $imageDiv
.children( 'div' ).css( 'margin', '0px auto' );
180 if ( newWidth
< 60 || !isFinite( newWidth
) ) {
181 // Making something skinnier than this will mess up captions,
182 if ( newWidth
< 1 || !isFinite( newWidth
) ) {
183 $innerDiv
.height( preferredHeight
);
184 // Don't even try and touch the image size if it could mean
185 // making it disappear.
189 $outerDiv
.width( newWidth
+ padding
);
190 $innerDiv
.width( newWidth
+ padding
);
191 $imageDiv
.width( newWidth
);
192 $caption
.width( curRow
[ j
].captionWidth
+ ( newWidth
- curRow
[ j
].imgWidth
) );
195 // We don't always have an img, e.g. in the case of an invalid file.
196 if ( $imageElm
[ 0 ] ) {
197 imageElm
= $imageElm
[ 0 ];
198 imageElm
.width
= newWidth
;
199 imageElm
.height
= preferredHeight
;
202 $imageDiv
.height( preferredHeight
);
209 function handleResizeStart() {
210 // Only do anything if window width changed. We don't care about the height.
211 if ( lastWidth
=== window
.innerWidth
) {
215 justifyNeeded
= true;
216 // Temporarily set min-height, so that content following the gallery is not reflowed twice
217 $galleries
.css( 'min-height', function () {
218 return $( this ).height();
220 $galleries
.children( 'li.gallerybox' ).each( function () {
221 var imgWidth
= $( this ).data( 'imgWidth' ),
222 imgHeight
= $( this ).data( 'imgHeight' ),
223 width
= $( this ).data( 'width' ),
224 captionWidth
= $( this ).data( 'captionWidth' ),
225 $innerDiv
= $( this ).children( 'div' ).first(),
226 $imageDiv
= $innerDiv
.children( 'div.thumb' ),
229 // Restore original sizes so we can arrange the elements as on freshly loaded page
230 $( this ).width( width
);
231 $innerDiv
.width( width
);
232 $imageDiv
.width( imgWidth
);
233 $( this ).find( 'div.gallerytextwrapper' ).width( captionWidth
);
235 $imageElm
= $( this ).find( 'img' ).first();
236 if ( $imageElm
[ 0 ] ) {
237 imageElm
= $imageElm
[ 0 ];
238 imageElm
.width
= imgWidth
;
239 imageElm
.height
= imgHeight
;
241 $imageDiv
.height( imgHeight
);
246 function handleResizeEnd() {
247 // If window width never changed during the resize, don't do anything.
248 if ( justifyNeeded
) {
249 justifyNeeded
= false;
250 lastWidth
= window
.innerWidth
;
252 // Remove temporary min-height
253 .css( 'min-height', '' )
254 // Recalculate layout
259 mw
.hook( 'wikipage.content' ).add( function ( $content
) {
260 if ( isTouchScreen
) {
261 // Always show the caption for a touch screen.
262 $content
.find( 'ul.mw-gallery-packed-hover' )
263 .addClass( 'mw-gallery-packed-overlay' )
264 .removeClass( 'mw-gallery-packed-hover' );
266 // Note use of just `a`, not `a.image`, since we also want this to trigger if a link
267 // within the caption text receives focus.
268 $content
.find( 'ul.mw-gallery-packed-hover li.gallerybox' ).on( 'focus blur', 'a', function ( e
) {
269 // Confusingly jQuery leaves e.type as focusout for delegated blur events
270 var gettingFocus
= e
.type
!== 'blur' && e
.type
!== 'focusout';
271 $( this ).closest( 'li.gallerybox' ).toggleClass( 'mw-gallery-focused', gettingFocus
);
275 $galleries
= $content
.find( 'ul.mw-gallery-packed-overlay, ul.mw-gallery-packed-hover, ul.mw-gallery-packed' );
276 // Call the justification asynchronous because live preview fires the hook with detached $content.
277 setTimeout( function () {
278 $galleries
.each( justify
);
280 // Bind here instead of in the top scope as the callbacks use $galleries.
284 .resize( $.debounce( 300, true, handleResizeStart
) )
285 .resize( $.debounce( 300, handleResizeEnd
) );
289 }( mediaWiki
, jQuery
) );