jquery.getAttrs: Add jsduck documentation
[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 $( function () {
7 var isTouchScreen,
8 gettingFocus,
9 galleries = 'ul.mw-gallery-packed-overlay, ul.mw-gallery-packed-hover, ul.mw-gallery-packed';
10
11 // Is there a better way to detect a touchscreen? Current check taken from stack overflow.
12 isTouchScreen = !!( window.ontouchstart !== undefined || window.DocumentTouch !== undefined && document instanceof window.DocumentTouch );
13
14 if ( isTouchScreen ) {
15 // Always show the caption for a touch screen.
16 $( 'ul.mw-gallery-packed-hover' )
17 .addClass( 'mw-gallery-packed-overlay' )
18 .removeClass( 'mw-gallery-packed-hover' );
19 } else {
20 // Note use of just "a", not a.image, since we want this to trigger if a link in
21 // the caption receives focus
22 $( 'ul.mw-gallery-packed-hover li.gallerybox' ).on( 'focus blur', 'a', function ( e ) {
23 // Confusingly jQuery leaves e.type as focusout for delegated blur events
24 gettingFocus = e.type !== 'blur' && e.type !== 'focusout';
25 $( this ).closest( 'li.gallerybox' ).toggleClass( 'mw-gallery-focused', gettingFocus );
26 } );
27 }
28
29 // Now on to justification.
30 // We may still get ragged edges if someone resizes their window. Could bind to
31 // that event, otoh do we really want to constantly be resizing galleries?
32 $( galleries ).each( function () {
33 var lastTop,
34 $img,
35 imgWidth,
36 imgHeight,
37 rows = [],
38 $gallery = $( this );
39
40 $gallery.children( 'li' ).each( function () {
41 // Math.floor to be paranoid if things are off by 0.00000000001
42 var top = Math.floor( $( this ).position().top ),
43 $this = $( this );
44
45 if ( top !== lastTop ) {
46 rows[rows.length] = [];
47 lastTop = top;
48 }
49
50 $img = $this.find( 'div.thumb a.image img' );
51 if ( $img.length && $img[0].height ) {
52 imgHeight = $img[0].height;
53 imgWidth = $img[0].width;
54 } else {
55 // If we don't have a real image, get the containing divs width/height.
56 // Note that if we do have a real image, using this method will generally
57 // give the same answer, but can be different in the case of a very
58 // narrow image where extra padding is added.
59 imgHeight = $this.children().children( 'div:first' ).height();
60 imgWidth = $this.children().children( 'div:first' ).width();
61 }
62
63 // Hack to make an edge case work ok
64 if ( imgHeight < 30 ) {
65 // Don't try and resize this item.
66 imgHeight = 0;
67 }
68
69 rows[rows.length - 1][rows[rows.length - 1].length] = {
70 $elm: $this,
71 width: $this.outerWidth(),
72 imgWidth: imgWidth,
73 // XXX: can divide by 0 ever happen?
74 aspect: imgWidth / imgHeight,
75 captionWidth: $this.children().children( 'div.gallerytextwrapper' ).width(),
76 height: imgHeight
77 };
78 } );
79
80 ( function () {
81 var maxWidth,
82 combinedAspect,
83 combinedPadding,
84 curRow,
85 curRowHeight,
86 wantedWidth,
87 preferredHeight,
88 newWidth,
89 padding,
90 $outerDiv,
91 $innerDiv,
92 $imageDiv,
93 $imageElm,
94 imageElm,
95 $caption,
96 hookInfo,
97 i,
98 j,
99 avgZoom,
100 totalZoom = 0;
101
102 for ( i = 0; i < rows.length; i++ ) {
103 maxWidth = $gallery.width();
104 combinedAspect = 0;
105 combinedPadding = 0;
106 curRow = rows[i];
107 curRowHeight = 0;
108
109 for ( j = 0; j < curRow.length; j++ ) {
110 if ( curRowHeight === 0 ) {
111 if ( isFinite( curRow[j].height ) ) {
112 // Get the height of this row, by taking the first
113 // non-out of bounds height
114 curRowHeight = curRow[j].height;
115 }
116 }
117
118 if ( curRow[j].aspect === 0 || !isFinite( curRow[j].aspect ) ) {
119 mw.log( 'Skipping item ' + j + ' due to aspect: ' + curRow[j].aspect );
120 // One of the dimensions are 0. Probably should
121 // not try to resize.
122 combinedPadding += curRow[j].width;
123 } else {
124 combinedAspect += curRow[j].aspect;
125 combinedPadding += curRow[j].width - curRow[j].imgWidth;
126 }
127 }
128
129 // Add some padding for inter-element spacing.
130 combinedPadding += 5 * curRow.length;
131 wantedWidth = maxWidth - combinedPadding;
132 preferredHeight = wantedWidth / combinedAspect;
133
134 if ( preferredHeight > curRowHeight * 1.5 ) {
135 // Only expand at most 1.5 times current size
136 // As that's as high a resolution as we have.
137 // Also on the off chance there is a bug in this
138 // code, would prevent accidentally expanding to
139 // be 10 billion pixels wide.
140 mw.log( 'mw.page.gallery: Cannot fit row, aspect is ' + preferredHeight / curRowHeight );
141 if ( i === rows.length - 1 ) {
142 // If its the last row, and we can't fit it,
143 // don't make the entire row huge.
144 avgZoom = ( totalZoom / ( rows.length - 1 ) ) * curRowHeight;
145 if ( isFinite( avgZoom ) && avgZoom >= 1 && avgZoom <= 1.5 ) {
146 preferredHeight = avgZoom;
147 } else {
148 // Probably a single row gallery
149 preferredHeight = curRowHeight;
150 }
151 } else {
152 preferredHeight = 1.5 * curRowHeight;
153 }
154 }
155 if ( !isFinite( preferredHeight ) ) {
156 // This *definitely* should not happen.
157 mw.log( 'mw.page.gallery: Trying to resize row ' + i + ' to ' + preferredHeight + '?!' );
158 // Skip this row.
159 continue;
160 }
161 if ( preferredHeight < 5 ) {
162 // Well something clearly went wrong...
163 mw.log( {
164 maxWidth: maxWidth,
165 combinedPadding: combinedPadding,
166 combinedAspect: combinedAspect,
167 wantedWidth: wantedWidth
168 } );
169 mw.log( 'mw.page.gallery: [BUG!] Fitting row ' + i + ' to too small a size: ' + preferredHeight );
170 // Skip this row.
171 continue;
172 }
173
174 if ( preferredHeight / curRowHeight > 1 ) {
175 totalZoom += preferredHeight / curRowHeight;
176 } else {
177 // If we shrink, still consider that a zoom of 1
178 totalZoom += 1;
179 }
180
181 for ( j = 0; j < curRow.length; j++ ) {
182 newWidth = preferredHeight * curRow[j].aspect;
183 padding = curRow[j].width - curRow[j].imgWidth;
184 $outerDiv = curRow[j].$elm;
185 $innerDiv = $outerDiv.children( 'div' ).first();
186 $imageDiv = $innerDiv.children( 'div.thumb' );
187 $imageElm = $imageDiv.find( 'img' ).first();
188 imageElm = $imageElm.length ? $imageElm[0] : null;
189 $caption = $outerDiv.find( 'div.gallerytextwrapper' );
190
191 // Since we are going to re-adjust the height, the vertical
192 // centering margins need to be reset.
193 $imageDiv.children( 'div' ).css( 'margin', '0px auto' );
194
195 if ( newWidth < 60 || !isFinite( newWidth ) ) {
196 // Making something skinnier than this will mess up captions,
197 mw.log( 'mw.page.gallery: Tried to make image ' + newWidth + 'px wide but too narrow.' );
198 if ( newWidth < 1 || !isFinite( newWidth ) ) {
199 $innerDiv.height( preferredHeight );
200 // Don't even try and touch the image size if it could mean
201 // making it disappear.
202 continue;
203 }
204 } else {
205 $outerDiv.width( newWidth + padding );
206 $innerDiv.width( newWidth + padding );
207 $imageDiv.width( newWidth );
208 $caption.width( curRow[j].captionWidth + ( newWidth - curRow[j].imgWidth ) );
209 }
210
211 hookInfo = {
212 fullWidth: newWidth + padding,
213 imgWidth: newWidth,
214 imgHeight: preferredHeight,
215 $innerDiv: $innerDiv,
216 $imageDiv: $imageDiv,
217 $outerDiv: $outerDiv,
218 // Whether the hook took action
219 resolved: false
220 };
221
222 /**
223 * Gallery resize.
224 *
225 * If your handler resizes an image, it should also set the resolved
226 * property to true. Additionally, because this module only exposes this
227 * logic temporarily, you should load your module in position top to
228 * ensure it is registered before this runs (FIXME: Don't use mw.hook)
229 *
230 * See TimedMediaHandler for an example.
231 *
232 * @event mediawiki_page_gallery_resize
233 * @member mw.hook
234 * @param {Object} hookInfo
235 */
236 mw.hook( 'mediawiki.page.gallery.resize' ).fire( hookInfo );
237
238 if ( !hookInfo.resolved ) {
239 if ( imageElm ) {
240 // We don't always have an img, e.g. in the case of an invalid file.
241 imageElm.width = newWidth;
242 imageElm.height = preferredHeight;
243 } else {
244 // Not a file box.
245 $imageDiv.height( preferredHeight );
246 }
247 }
248 }
249 }
250 } )();
251 } );
252 } );
253 } )( jQuery, mediaWiki );