2 var config
= require( './config.json' ),
3 reasonCodePointLimit
= mw
.config
.get( 'wgCommentCodePointLimit' ),
4 reasonByteLimit
= mw
.config
.get( 'wgCommentByteLimit' );
7 * Get a list of all protection selectors on the page
11 function getLevelSelectors() {
12 return $( 'select[id ^= mwProtect-level-]' );
16 * Get a list of all expiry inputs on the page
20 function getExpiryInputs() {
21 return $( 'input[id ^= mwProtect-][id $= -expires]' );
25 * Get a list of all expiry selector lists on the page
29 function getExpirySelectors() {
30 return $( 'select[id ^= mwProtectExpirySelection-]' );
34 * Enable/disable protection selectors and expiry inputs
36 * @param {boolean} val Enable?
38 function toggleUnchainedInputs( val
) {
39 var setDisabled = function () {
42 getLevelSelectors().slice( 1 ).each( setDisabled
);
43 getExpiryInputs().slice( 1 ).each( setDisabled
);
44 getExpirySelectors().slice( 1 ).each( setDisabled
);
48 * Checks if a certain protection level is cascadeable.
50 * @param {string} level
53 function isCascadeableLevel( level
) {
54 return config
.CascadingRestrictionLevels
.indexOf( level
) !== -1;
58 * Sets the disabled attribute on the cascade checkbox depending on the current selected levels
60 function updateCascadeCheckbox() {
61 getLevelSelectors().each( function () {
62 if ( !isCascadeableLevel( $( this ).val() ) ) {
63 $( '#mwProtect-cascade' ).prop( { checked
: false, disabled
: true } );
66 $( '#mwProtect-cascade' ).prop( 'disabled', false );
72 * Returns true if the named attribute in all objects in the given array are matching
74 * @param {Object[]} objects
75 * @param {string} attrName
78 function matchAttribute( objects
, attrName
) {
79 // eslint-disable-next-line no-jquery/no-map-util
80 return $.map( objects
, function ( object
) {
81 return object
[ attrName
];
82 } ).filter( function ( item
, index
, a
) {
83 return index
=== a
.indexOf( item
);
88 * Are all actions protected at the same level, with the same expiry time?
92 function areAllTypesMatching() {
93 return matchAttribute( getLevelSelectors(), 'selectedIndex' ) &&
94 matchAttribute( getExpirySelectors(), 'selectedIndex' ) &&
95 matchAttribute( getExpiryInputs(), 'value' );
99 * Is protection chaining off?
103 function isUnchained() {
104 var element
= document
.getElementById( 'mwProtectUnchained' );
107 true; // No control, so we need to let the user set both levels
111 * Find the highest protection level in any selector
115 function getMaxLevel() {
116 return Math
.max
.apply( Math
, getLevelSelectors().map( function () {
117 return this.selectedIndex
;
122 * Protect all actions at the specified level
124 * @param {number} index Protection level
126 function setAllSelectors( index
) {
127 getLevelSelectors().prop( 'selectedIndex', index
);
131 * When protection levels are locked together, update the rest
132 * when one action's level changes
134 * @param {Event} event Level selector that changed
136 function updateLevels( event
) {
137 if ( !isUnchained() ) {
138 setAllSelectors( event
.target
.selectedIndex
);
140 updateCascadeCheckbox();
144 * When protection levels are locked together, update the
145 * expiries when one changes
147 * @param {Event} event Expiry input that changed
149 function updateExpiry( event
) {
150 if ( !isUnchained() ) {
151 getExpiryInputs().val( event
.target
.value
);
153 if ( isUnchained() ) {
154 $( '#' + event
.target
.id
.replace( /^mwProtect-(\w+)-expires$/, 'mwProtectExpirySelection-$1' ) ).val( 'othertime' );
156 getExpirySelectors().val( 'othertime' );
161 * When protection levels are locked together, update the
162 * expiry lists when one changes and clear the custom inputs
164 * @param {Event} event Expiry selector that changed
166 function updateExpiryList( event
) {
167 if ( !isUnchained() ) {
168 getExpirySelectors().val( event
.target
.value
);
169 getExpiryInputs().val( '' );
174 * Update chain status and enable/disable various bits of the UI
175 * when the user changes the "unlock move permissions" checkbox
177 function onChainClick() {
178 toggleUnchainedInputs( isUnchained() );
179 if ( !isUnchained() ) {
180 setAllSelectors( getMaxLevel() );
182 updateCascadeCheckbox();
186 * Set up the protection chaining interface (i.e. "unlock move permissions" checkbox)
187 * on the protection form
190 var $cell
= $( '<td>' ),
191 $row
= $( '<tr>' ).append( $cell
);
193 if ( !$( '#mwProtectSet' ).length
) {
197 $( 'form#mw-Protect-Form' ).on( 'submit', toggleUnchainedInputs
.bind( this, true ) );
198 getExpirySelectors().on( 'change', updateExpiryList
);
199 getExpiryInputs().on( 'input change', updateExpiry
);
200 getLevelSelectors().on( 'change', updateLevels
);
202 $( '#mwProtectSet > tbody > tr:first' ).after( $row
);
204 // If there is only one protection type, there is nothing to chain
205 if ( $( '[id ^= mw-protect-table-]' ).length
> 1 ) {
208 .attr( { id
: 'mwProtectUnchained', type
: 'checkbox' } )
209 .on( 'click', onChainClick
)
210 .prop( 'checked', !areAllTypesMatching() ),
211 document
.createTextNode( ' ' ),
213 .attr( 'for', 'mwProtectUnchained' )
214 .text( mw
.msg( 'protect-unchain-permissions' ) )
217 toggleUnchainedInputs( !areAllTypesMatching() );
220 // Arbitrary 75 to leave some space for the autogenerated null edit's summary
221 if ( reasonCodePointLimit
) {
222 $( '#mwProtect-reason' ).codePointLimit( reasonCodePointLimit
- 75 );
223 } else if ( reasonByteLimit
) {
224 $( '#mwProtect-reason' ).byteLimit( reasonByteLimit
- 75 );
227 updateCascadeCheckbox();