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().each( function () {
128 this.selectedIndex
= index
;
133 * When protection levels are locked together, update the rest
134 * when one action's level changes
136 * @param {Event} event Level selector that changed
138 function updateLevels( event
) {
139 if ( !isUnchained() ) {
140 setAllSelectors( event
.target
.selectedIndex
);
142 updateCascadeCheckbox();
146 * When protection levels are locked together, update the
147 * expiries when one changes
149 * @param {Event} event Expiry input that changed
152 function updateExpiry( event
) {
153 if ( !isUnchained() ) {
154 getExpiryInputs().each( function () {
155 this.value
= event
.target
.value
;
158 if ( isUnchained() ) {
159 $( '#' + event
.target
.id
.replace( /^mwProtect-(\w+)-expires$/, 'mwProtectExpirySelection-$1' ) ).val( 'othertime' );
161 getExpirySelectors().each( function () {
162 this.value
= 'othertime';
168 * When protection levels are locked together, update the
169 * expiry lists when one changes and clear the custom inputs
171 * @param {Event} event Expiry selector that changed
173 function updateExpiryList( event
) {
174 if ( !isUnchained() ) {
175 getExpirySelectors().each( function () {
176 this.value
= event
.target
.value
;
178 getExpiryInputs().each( function () {
185 * Update chain status and enable/disable various bits of the UI
186 * when the user changes the "unlock move permissions" checkbox
188 function onChainClick() {
189 toggleUnchainedInputs( isUnchained() );
190 if ( !isUnchained() ) {
191 setAllSelectors( getMaxLevel() );
193 updateCascadeCheckbox();
197 * Set up the protection chaining interface (i.e. "unlock move permissions" checkbox)
198 * on the protection form
203 var $cell
= $( '<td>' ),
204 $row
= $( '<tr>' ).append( $cell
);
206 if ( !$( '#mwProtectSet' ).length
) {
210 $( 'form#mw-Protect-Form' ).on( 'submit', toggleUnchainedInputs
.bind( this, true ) );
211 getExpirySelectors().each( function () {
212 $( this ).on( 'change', updateExpiryList
);
214 getExpiryInputs().each( function () {
215 $( this ).on( 'keyup change', updateExpiry
);
217 getLevelSelectors().each( function () {
218 $( this ).on( 'change', updateLevels
);
221 $( '#mwProtectSet > tbody > tr:first' ).after( $row
);
223 // If there is only one protection type, there is nothing to chain
224 if ( $( '[id ^= mw-protect-table-]' ).length
> 1 ) {
227 .attr( { id
: 'mwProtectUnchained', type
: 'checkbox' } )
228 .on( 'click', onChainClick
)
229 .prop( 'checked', !areAllTypesMatching() ),
230 document
.createTextNode( ' ' ),
232 .attr( 'for', 'mwProtectUnchained' )
233 .text( mw
.msg( 'protect-unchain-permissions' ) )
236 toggleUnchainedInputs( !areAllTypesMatching() );
239 // Arbitrary 75 to leave some space for the autogenerated null edit's summary
240 if ( reasonCodePointLimit
) {
241 $( '#mwProtect-reason' ).codePointLimit( reasonCodePointLimit
- 75 );
242 } else if ( reasonByteLimit
) {
243 $( '#mwProtect-reason' ).byteLimit( reasonByteLimit
- 75 );
246 updateCascadeCheckbox();