2da51222c6e499400959ed2bae0a2dbd14cc8c72
[lhc/web/wiklou.git] / resources / src / mediawiki.legacy / protect.js
1 ( function () {
2 var ProtectionForm,
3 reasonCodePointLimit = mw.config.get( 'wgCommentCodePointLimit' ),
4 reasonByteLimit = mw.config.get( 'wgCommentByteLimit' );
5
6 ProtectionForm = window.ProtectionForm = {
7 /**
8 * Set up the protection chaining interface (i.e. "unlock move permissions" checkbox)
9 * on the protection form
10 *
11 * @return {boolean}
12 */
13 init: function () {
14 var $cell = $( '<td>' ),
15 $row = $( '<tr>' ).append( $cell );
16
17 if ( !$( '#mwProtectSet' ).length ) {
18 return false;
19 }
20
21 if ( mw.config.get( 'wgCascadeableLevels' ) !== undefined ) {
22 $( 'form#mw-Protect-Form' ).submit( this.toggleUnchainedInputs.bind( ProtectionForm, true ) );
23 }
24 this.getExpirySelectors().each( function () {
25 $( this ).change( ProtectionForm.updateExpiryList.bind( ProtectionForm, this ) );
26 } );
27 this.getExpiryInputs().each( function () {
28 $( this ).on( 'keyup change', ProtectionForm.updateExpiry.bind( ProtectionForm, this ) );
29 } );
30 this.getLevelSelectors().each( function () {
31 $( this ).change( ProtectionForm.updateLevels.bind( ProtectionForm, this ) );
32 } );
33
34 $( '#mwProtectSet > tbody > tr:first' ).after( $row );
35
36 // If there is only one protection type, there is nothing to chain
37 if ( $( '[id ^= mw-protect-table-]' ).length > 1 ) {
38 $cell.append(
39 $( '<input>' )
40 .attr( { id: 'mwProtectUnchained', type: 'checkbox' } )
41 .click( this.onChainClick.bind( this ) )
42 .prop( 'checked', !this.areAllTypesMatching() ),
43 document.createTextNode( ' ' ),
44 $( '<label>' )
45 .attr( 'for', 'mwProtectUnchained' )
46 .text( mw.msg( 'protect-unchain-permissions' ) )
47 );
48
49 this.toggleUnchainedInputs( !this.areAllTypesMatching() );
50 }
51
52 // Arbitrary 75 to leave some space for the autogenerated null edit's summary
53 if ( reasonCodePointLimit ) {
54 $( '#mwProtect-reason' ).codePointLimit( reasonCodePointLimit - 75 );
55 } else if ( reasonByteLimit ) {
56 $( '#mwProtect-reason' ).byteLimit( reasonByteLimit - 75 );
57 }
58
59 this.updateCascadeCheckbox();
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 this.getLevelSelectors().each( function () {
68 if ( !ProtectionForm.isCascadeableLevel( $( this ).val() ) ) {
69 $( '#mwProtect-cascade' ).prop( { checked: false, disabled: true } );
70 return false;
71 } else {
72 $( '#mwProtect-cascade' ).prop( 'disabled', false );
73 }
74 } );
75 },
76
77 /**
78 * Checks if a certain protection level is cascadeable.
79 *
80 * @param {string} level
81 * @return {boolean}
82 */
83 isCascadeableLevel: function ( level ) {
84 var cascadeableLevels = mw.config.get( 'wgCascadeableLevels' );
85
86 if ( !Array.isArray( cascadeableLevels ) ) {
87 return false;
88 }
89
90 return cascadeableLevels.indexOf( level ) !== -1;
91 },
92
93 /**
94 * When protection levels are locked together, update the rest
95 * when one action's level changes
96 *
97 * @param {Element} source Level selector that changed
98 */
99 updateLevels: function ( source ) {
100 if ( !this.isUnchained() ) {
101 this.setAllSelectors( source.selectedIndex );
102 }
103 this.updateCascadeCheckbox();
104 },
105
106 /**
107 * When protection levels are locked together, update the
108 * expiries when one changes
109 *
110 * @param {Element} source expiry input that changed
111 */
112
113 updateExpiry: function ( source ) {
114 if ( !this.isUnchained() ) {
115 this.getExpiryInputs().each( function () {
116 this.value = source.value;
117 } );
118 }
119 if ( this.isUnchained() ) {
120 $( '#' + source.id.replace( /^mwProtect-(\w+)-expires$/, 'mwProtectExpirySelection-$1' ) ).val( 'othertime' );
121 } else {
122 this.getExpirySelectors().each( function () {
123 this.value = 'othertime';
124 } );
125 }
126 },
127
128 /**
129 * When protection levels are locked together, update the
130 * expiry lists when one changes and clear the custom inputs
131 *
132 * @param {Element} source Expiry selector that changed
133 */
134 updateExpiryList: function ( source ) {
135 if ( !this.isUnchained() ) {
136 this.getExpirySelectors().each( function () {
137 this.value = source.value;
138 } );
139 this.getExpiryInputs().each( function () {
140 this.value = '';
141 } );
142 }
143 },
144
145 /**
146 * Update chain status and enable/disable various bits of the UI
147 * when the user changes the "unlock move permissions" checkbox
148 */
149 onChainClick: function () {
150 this.toggleUnchainedInputs( this.isUnchained() );
151 if ( !this.isUnchained() ) {
152 this.setAllSelectors( this.getMaxLevel() );
153 }
154 this.updateCascadeCheckbox();
155 },
156
157 /**
158 * Returns true if the named attribute in all objects in the given array are matching
159 *
160 * @param {Object[]} objects
161 * @param {string} attrName
162 * @return {boolean}
163 */
164 matchAttribute: function ( objects, attrName ) {
165 // eslint-disable-next-line jquery/no-map-util
166 return $.map( objects, function ( object ) {
167 return object[ attrName ];
168 } ).filter( function ( item, index, a ) {
169 return index === a.indexOf( item );
170 } ).length === 1;
171 },
172
173 /**
174 * Are all actions protected at the same level, with the same expiry time?
175 *
176 * @return {boolean}
177 */
178 areAllTypesMatching: function () {
179 return this.matchAttribute( this.getLevelSelectors(), 'selectedIndex' ) &&
180 this.matchAttribute( this.getExpirySelectors(), 'selectedIndex' ) &&
181 this.matchAttribute( this.getExpiryInputs(), 'value' );
182 },
183
184 /**
185 * Is protection chaining off?
186 *
187 * @return {boolean}
188 */
189 isUnchained: function () {
190 var element = document.getElementById( 'mwProtectUnchained' );
191 return element ?
192 element.checked :
193 true; // No control, so we need to let the user set both levels
194 },
195
196 /**
197 * Find the highest protection level in any selector
198 *
199 * @return {number}
200 */
201 getMaxLevel: function () {
202 return Math.max.apply( Math, this.getLevelSelectors().map( function () {
203 return this.selectedIndex;
204 } ) );
205 },
206
207 /**
208 * Protect all actions at the specified level
209 *
210 * @param {number} index Protection level
211 */
212 setAllSelectors: function ( index ) {
213 this.getLevelSelectors().each( function () {
214 this.selectedIndex = index;
215 } );
216 },
217
218 /**
219 * Get a list of all protection selectors on the page
220 *
221 * @return {jQuery}
222 */
223 getLevelSelectors: function () {
224 return $( 'select[id ^= mwProtect-level-]' );
225 },
226
227 /**
228 * Get a list of all expiry inputs on the page
229 *
230 * @return {jQuery}
231 */
232 getExpiryInputs: function () {
233 return $( 'input[id ^= mwProtect-][id $= -expires]' );
234 },
235
236 /**
237 * Get a list of all expiry selector lists on the page
238 *
239 * @return {jQuery}
240 */
241 getExpirySelectors: function () {
242 return $( 'select[id ^= mwProtectExpirySelection-]' );
243 },
244
245 /**
246 * Enable/disable protection selectors and expiry inputs
247 *
248 * @param {boolean} val Enable?
249 */
250 toggleUnchainedInputs: function ( val ) {
251 var setDisabled = function () {
252 this.disabled = !val;
253 };
254 this.getLevelSelectors().slice( 1 ).each( setDisabled );
255 this.getExpiryInputs().slice( 1 ).each( setDisabled );
256 this.getExpirySelectors().slice( 1 ).each( setDisabled );
257 }
258 };
259
260 $( ProtectionForm.init.bind( ProtectionForm ) );
261
262 }() );