Merge "Make FormatMetadata accept RequestContext, instead of hard coding $wgLang."
[lhc/web/wiklou.git] / skins / common / protect.js
1 ( function ( mw, $ ) {
2
3 var ProtectionForm = window.ProtectionForm = {
4 existingMatch: false,
5
6 /**
7 * Set up the protection chaining interface (i.e. "unlock move permissions" checkbox)
8 * on the protection form
9 *
10 * @param opts Object : parameters with members:
11 * tableId Identifier of the table containing UI bits
12 * labelText Text to use for the checkbox label
13 * numTypes The number of protection types
14 * existingMatch True if all the existing expiry times match
15 */
16 init: function ( opts ) {
17 var box, boxbody, row, cell, check, label;
18
19 if ( !( document.createTextNode && document.getElementById && document.getElementsByTagName ) ) {
20 return false;
21 }
22
23 box = document.getElementById( opts.tableId );
24 if ( !box ) {
25 return false;
26 }
27
28 boxbody = box.getElementsByTagName( 'tbody' )[0];
29 row = document.createElement( 'tr' );
30 boxbody.insertBefore( row, boxbody.firstChild.nextSibling );
31
32 this.existingMatch = opts.existingMatch;
33
34 cell = document.createElement( 'td' );
35 row.appendChild( cell );
36 // If there is only one protection type, there is nothing to chain
37 if ( opts.numTypes > 1 ) {
38 check = document.createElement( 'input' );
39 check.id = 'mwProtectUnchained';
40 check.type = 'checkbox';
41 cell.appendChild( check );
42 window.addClickHandler( check, function () {
43 ProtectionForm.onChainClick();
44 } );
45
46 cell.appendChild( document.createTextNode( ' ' ) );
47 label = document.createElement( 'label' );
48 label.htmlFor = 'mwProtectUnchained';
49 label.appendChild( document.createTextNode( opts.labelText ) );
50 cell.appendChild( label );
51
52 check.checked = !this.areAllTypesMatching();
53 this.enableUnchainedInputs( check.checked );
54 }
55
56 $( '#mwProtect-reason' ).byteLimit( 180 );
57
58 this.updateCascadeCheckbox();
59
60 return true;
61 },
62
63 /**
64 * Sets the disabled attribute on the cascade checkbox depending on the current selected levels
65 */
66 updateCascadeCheckbox: function () {
67 var i, lists, items, selected;
68
69 // For non-existent titles, there is no cascade option
70 if ( !document.getElementById( 'mwProtect-cascade' ) ) {
71 return;
72 }
73 lists = this.getLevelSelectors();
74 for ( i = 0; i < lists.length; i++ ) {
75 if ( lists[i].selectedIndex > -1 ) {
76 items = lists[i].getElementsByTagName( 'option' );
77 selected = items[ lists[i].selectedIndex ].value;
78 if ( !this.isCascadeableLevel( selected ) ) {
79 document.getElementById( 'mwProtect-cascade' ).checked = false;
80 document.getElementById( 'mwProtect-cascade' ).disabled = true;
81 return;
82 }
83 }
84 }
85 document.getElementById( 'mwProtect-cascade' ).disabled = false;
86 },
87
88 /**
89 * Checks if a cerain protection level is cascadeable.
90 * @param level {String}
91 * @return {Boolean}
92 */
93 isCascadeableLevel: function ( level ) {
94 var cascadeLevels, len, i;
95
96 cascadeLevels = mw.config.get( 'wgCascadeableLevels' );
97 // cascadeLevels isn't defined on all pages
98 if ( cascadeLevels ) {
99 for ( i = 0, len = cascadeLevels.length; i < len; i += 1 ) {
100 if ( cascadeLevels[i] === level ) {
101 return true;
102 }
103 }
104 }
105 return false;
106 },
107
108 /**
109 * When protection levels are locked together, update the rest
110 * when one action's level changes
111 *
112 * @param source Element Level selector that changed
113 */
114 updateLevels: function ( source ) {
115 if ( !this.isUnchained() ) {
116 this.setAllSelectors( source.selectedIndex );
117 }
118 this.updateCascadeCheckbox();
119 },
120
121 /**
122 * When protection levels are locked together, update the
123 * expiries when one changes
124 *
125 * @param source Element expiry input that changed
126 */
127
128 updateExpiry: function ( source ) {
129 var expiry, listId, list;
130
131 if ( !this.isUnchained() ) {
132 expiry = source.value;
133 this.forEachExpiryInput( function ( element ) {
134 element.value = expiry;
135 } );
136 }
137 listId = source.id.replace( /^mwProtect-(\w+)-expires$/, 'mwProtectExpirySelection-$1' );
138 list = document.getElementById( listId );
139 if ( list && list.value !== 'othertime' ) {
140 if ( this.isUnchained() ) {
141 list.value = 'othertime';
142 } else {
143 this.forEachExpirySelector( function ( element ) {
144 element.value = 'othertime';
145 } );
146 }
147 }
148 },
149
150 /**
151 * When protection levels are locked together, update the
152 * expiry lists when one changes and clear the custom inputs
153 *
154 * @param source Element expiry selector that changed
155 */
156 updateExpiryList: function ( source ) {
157 var expiry;
158 if ( !this.isUnchained() ) {
159 expiry = source.value;
160 this.forEachExpirySelector( function ( element ) {
161 element.value = expiry;
162 } );
163 this.forEachExpiryInput( function ( element ) {
164 element.value = '';
165 } );
166 }
167 },
168
169 /**
170 * Update chain status and enable/disable various bits of the UI
171 * when the user changes the "unlock move permissions" checkbox
172 */
173 onChainClick: function () {
174 if ( this.isUnchained() ) {
175 this.enableUnchainedInputs( true );
176 } else {
177 this.setAllSelectors( this.getMaxLevel() );
178 this.enableUnchainedInputs( false );
179 }
180 this.updateCascadeCheckbox();
181 },
182
183 /**
184 * Returns true if the named attribute in all objects in the given array are matching
185 */
186 matchAttribute: function ( objects, attrName ) {
187 var i, element, value;
188
189 // Check levels
190 value = null;
191 for ( i = 0; i < objects.length; i++ ) {
192 element = objects[i];
193 if ( value === null ) {
194 value = element[attrName];
195 } else {
196 if ( value !== element[attrName] ) {
197 return false;
198 }
199 }
200 }
201 return true;
202 },
203
204 /**
205 * Are all actions protected at the same level, with the same expiry time?
206 *
207 * @return boolean
208 */
209 areAllTypesMatching: function () {
210 return this.existingMatch
211 && this.matchAttribute( this.getLevelSelectors(), 'selectedIndex' )
212 && this.matchAttribute( this.getExpirySelectors(), 'selectedIndex' )
213 && this.matchAttribute( this.getExpiryInputs(), 'value' );
214 },
215
216 /**
217 * Is protection chaining off?
218 *
219 * @return bool
220 */
221 isUnchained: function () {
222 var element = document.getElementById( 'mwProtectUnchained' );
223 return element
224 ? element.checked
225 : true; // No control, so we need to let the user set both levels
226 },
227
228 /**
229 * Find the highest protection level in any selector
230 */
231 getMaxLevel: function () {
232 var maxIndex = -1;
233 this.forEachLevelSelector( function ( element ) {
234 if ( element.selectedIndex > maxIndex ) {
235 maxIndex = element.selectedIndex;
236 }
237 } );
238 return maxIndex;
239 },
240
241 /**
242 * Protect all actions at the specified level
243 *
244 * @param index int Protection level
245 */
246 setAllSelectors: function ( index ) {
247 this.forEachLevelSelector( function ( element ) {
248 if ( element.selectedIndex !== index ) {
249 element.selectedIndex = index;
250 }
251 } );
252 },
253
254 /**
255 * Apply a callback to each protection selector
256 *
257 * @param func callable Callback function
258 */
259 forEachLevelSelector: function ( func ) {
260 var i, selectors;
261
262 selectors = this.getLevelSelectors();
263 for ( i = 0; i < selectors.length; i++ ) {
264 func( selectors[i] );
265 }
266 },
267
268 /**
269 * Get a list of all protection selectors on the page
270 *
271 * @return Array
272 */
273 getLevelSelectors: function () {
274 var i, ours, all, element;
275
276 all = document.getElementsByTagName( 'select' );
277 ours = [];
278 for ( i = 0; i < all.length; i++ ) {
279 element = all[i];
280 if ( element.id.match( /^mwProtect-level-/ ) ) {
281 ours[ours.length] = element;
282 }
283 }
284 return ours;
285 },
286
287 /**
288 * Apply a callback to each expiry input
289 *
290 * @param func callable Callback function
291 */
292 forEachExpiryInput: function ( func ) {
293 var i, inputs;
294
295 inputs = this.getExpiryInputs();
296 for ( i = 0; i < inputs.length; i++ ) {
297 func( inputs[i] );
298 }
299 },
300
301 /**
302 * Get a list of all expiry inputs on the page
303 *
304 * @return Array
305 */
306 getExpiryInputs: function () {
307 var i, all, element, ours;
308
309 all = document.getElementsByTagName( 'input' );
310 ours = [];
311 for ( i = 0; i < all.length; i++ ) {
312 element = all[i];
313 if ( element.name.match( /^mwProtect-expiry-/ ) ) {
314 ours[ours.length] = element;
315 }
316 }
317 return ours;
318 },
319
320 /**
321 * Apply a callback to each expiry selector list
322 * @param func callable Callback function
323 */
324 forEachExpirySelector: function ( func ) {
325 var i, inputs;
326
327 inputs = this.getExpirySelectors();
328 for ( i = 0; i < inputs.length; i++ ) {
329 func( inputs[i] );
330 }
331 },
332
333 /**
334 * Get a list of all expiry selector lists on the page
335 *
336 * @return Array
337 */
338 getExpirySelectors: function () {
339 var i, all, ours, element;
340
341 all = document.getElementsByTagName( 'select' );
342 ours = [];
343 for ( i = 0; i < all.length; i++ ) {
344 element = all[i];
345 if ( element.id.match( /^mwProtectExpirySelection-/ ) ) {
346 ours[ours.length] = element;
347 }
348 }
349 return ours;
350 },
351
352 /**
353 * Enable/disable protection selectors and expiry inputs
354 *
355 * @param val boolean Enable?
356 */
357 enableUnchainedInputs: function ( val ) {
358 var first = true;
359
360 this.forEachLevelSelector( function ( element ) {
361 if ( first ) {
362 first = false;
363 } else {
364 element.disabled = !val;
365 }
366 } );
367 first = true;
368 this.forEachExpiryInput( function ( element ) {
369 if ( first ) {
370 first = false;
371 } else {
372 element.disabled = !val;
373 }
374 } );
375 first = true;
376 this.forEachExpirySelector( function ( element ) {
377 if ( first ) {
378 first = false;
379 } else {
380 element.disabled = !val;
381 }
382 } );
383 }
384 };
385
386 }( mediaWiki, jQuery ) );