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