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