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