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