1 /* eslint-disable no-use-before-define */
3 var config
= require( './config.json' ),
4 reasonCodePointLimit
= mw
.config
.get( 'wgCommentCodePointLimit' ),
5 reasonByteLimit
= mw
.config
.get( 'wgCommentByteLimit' );
8 * Set up the protection chaining interface (i.e. "unlock move permissions" checkbox)
9 * on the protection form
14 var $cell
= $( '<td>' ),
15 $row
= $( '<tr>' ).append( $cell
);
17 if ( !$( '#mwProtectSet' ).length
) {
21 $( 'form#mw-Protect-Form' ).on( 'submit', toggleUnchainedInputs
.bind( this, true ) );
22 getExpirySelectors().each( function () {
23 $( this ).on( 'change', updateExpiryList
);
25 getExpiryInputs().each( function () {
26 $( this ).on( 'keyup change', updateExpiry
);
28 getLevelSelectors().each( function () {
29 $( this ).on( 'change', updateLevels
);
32 $( '#mwProtectSet > tbody > tr:first' ).after( $row
);
34 // If there is only one protection type, there is nothing to chain
35 if ( $( '[id ^= mw-protect-table-]' ).length
> 1 ) {
38 .attr( { id
: 'mwProtectUnchained', type
: 'checkbox' } )
39 .on( 'click', onChainClick
)
40 .prop( 'checked', !areAllTypesMatching() ),
41 document
.createTextNode( ' ' ),
43 .attr( 'for', 'mwProtectUnchained' )
44 .text( mw
.msg( 'protect-unchain-permissions' ) )
47 toggleUnchainedInputs( !areAllTypesMatching() );
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 );
57 updateCascadeCheckbox();
62 * Sets the disabled attribute on the cascade checkbox depending on the current selected levels
64 function updateCascadeCheckbox() {
65 getLevelSelectors().each( function () {
66 if ( !isCascadeableLevel( $( this ).val() ) ) {
67 $( '#mwProtect-cascade' ).prop( { checked
: false, disabled
: true } );
70 $( '#mwProtect-cascade' ).prop( 'disabled', false );
76 * Checks if a certain protection level is cascadeable.
78 * @param {string} level
81 function isCascadeableLevel( level
) {
82 return config
.CascadingRestrictionLevels
.indexOf( level
) !== -1;
86 * When protection levels are locked together, update the rest
87 * when one action's level changes
89 * @param {Event} event Level selector that changed
91 function updateLevels( event
) {
92 if ( !isUnchained() ) {
93 setAllSelectors( event
.target
.selectedIndex
);
95 updateCascadeCheckbox();
99 * When protection levels are locked together, update the
100 * expiries when one changes
102 * @param {Event} event Expiry input that changed
105 function updateExpiry( event
) {
106 if ( !isUnchained() ) {
107 getExpiryInputs().each( function () {
108 this.value
= event
.target
.value
;
111 if ( isUnchained() ) {
112 $( '#' + event
.target
.id
.replace( /^mwProtect-(\w+)-expires$/, 'mwProtectExpirySelection-$1' ) ).val( 'othertime' );
114 getExpirySelectors().each( function () {
115 this.value
= 'othertime';
121 * When protection levels are locked together, update the
122 * expiry lists when one changes and clear the custom inputs
124 * @param {Event} event Expiry selector that changed
126 function updateExpiryList( event
) {
127 if ( !isUnchained() ) {
128 getExpirySelectors().each( function () {
129 this.value
= event
.target
.value
;
131 getExpiryInputs().each( function () {
138 * Update chain status and enable/disable various bits of the UI
139 * when the user changes the "unlock move permissions" checkbox
141 function onChainClick() {
142 toggleUnchainedInputs( isUnchained() );
143 if ( !isUnchained() ) {
144 setAllSelectors( getMaxLevel() );
146 updateCascadeCheckbox();
150 * Returns true if the named attribute in all objects in the given array are matching
152 * @param {Object[]} objects
153 * @param {string} attrName
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
);
166 * Are all actions protected at the same level, with the same expiry time?
170 function areAllTypesMatching() {
171 return matchAttribute( getLevelSelectors(), 'selectedIndex' ) &&
172 matchAttribute( getExpirySelectors(), 'selectedIndex' ) &&
173 matchAttribute( getExpiryInputs(), 'value' );
177 * Is protection chaining off?
181 function isUnchained() {
182 var element
= document
.getElementById( 'mwProtectUnchained' );
185 true; // No control, so we need to let the user set both levels
189 * Find the highest protection level in any selector
193 function getMaxLevel() {
194 return Math
.max
.apply( Math
, getLevelSelectors().map( function () {
195 return this.selectedIndex
;
200 * Protect all actions at the specified level
202 * @param {number} index Protection level
204 function setAllSelectors( index
) {
205 getLevelSelectors().each( function () {
206 this.selectedIndex
= index
;
211 * Get a list of all protection selectors on the page
215 function getLevelSelectors() {
216 return $( 'select[id ^= mwProtect-level-]' );
220 * Get a list of all expiry inputs on the page
224 function getExpiryInputs() {
225 return $( 'input[id ^= mwProtect-][id $= -expires]' );
229 * Get a list of all expiry selector lists on the page
233 function getExpirySelectors() {
234 return $( 'select[id ^= mwProtectExpirySelection-]' );
238 * Enable/disable protection selectors and expiry inputs
240 * @param {boolean} val Enable?
242 function toggleUnchainedInputs( val
) {
243 var setDisabled = function () {
244 this.disabled
= !val
;
246 getLevelSelectors().slice( 1 ).each( setDisabled
);
247 getExpiryInputs().slice( 1 ).each( setDisabled
);
248 getExpirySelectors().slice( 1 ).each( setDisabled
);