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