protect.js 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. window.ProtectionForm = {
  2. 'existingMatch': false,
  3. /**
  4. * Set up the protection chaining interface (i.e. "unlock move permissions" checkbox)
  5. * on the protection form
  6. *
  7. * @param opts Object : parameters with members:
  8. * tableId Identifier of the table containing UI bits
  9. * labelText Text to use for the checkbox label
  10. * numTypes The number of protection types
  11. * existingMatch True if all the existing expiry times match
  12. */
  13. 'init': function( opts ) {
  14. if( !( document.createTextNode && document.getElementById && document.getElementsByTagName ) )
  15. return false;
  16. var box = document.getElementById( opts.tableId );
  17. if( !box )
  18. return false;
  19. var boxbody = box.getElementsByTagName('tbody')[0];
  20. var row = document.createElement( 'tr' );
  21. boxbody.insertBefore( row, boxbody.firstChild.nextSibling );
  22. this.existingMatch = opts.existingMatch;
  23. var cell = document.createElement( 'td' );
  24. row.appendChild( cell );
  25. // If there is only one protection type, there is nothing to chain
  26. if( opts.numTypes > 1 ) {
  27. var check = document.createElement( 'input' );
  28. check.id = 'mwProtectUnchained';
  29. check.type = 'checkbox';
  30. cell.appendChild( check );
  31. addClickHandler( check, function() { ProtectionForm.onChainClick(); } );
  32. cell.appendChild( document.createTextNode( ' ' ) );
  33. var label = document.createElement( 'label' );
  34. label.htmlFor = 'mwProtectUnchained';
  35. label.appendChild( document.createTextNode( opts.labelText ) );
  36. cell.appendChild( label );
  37. check.checked = !this.areAllTypesMatching();
  38. this.enableUnchainedInputs( check.checked );
  39. }
  40. $( '#mwProtect-reason' ).byteLimit( 180 );
  41. this.updateCascadeCheckbox();
  42. return true;
  43. },
  44. /**
  45. * Sets the disabled attribute on the cascade checkbox depending on the current selected levels
  46. */
  47. 'updateCascadeCheckbox': function() {
  48. // For non-existent titles, there is no cascade option
  49. if( !document.getElementById( 'mwProtect-cascade' ) ) {
  50. return;
  51. }
  52. var lists = this.getLevelSelectors();
  53. for( var i = 0; i < lists.length; i++ ) {
  54. if( lists[i].selectedIndex > -1 ) {
  55. var items = lists[i].getElementsByTagName( 'option' );
  56. var selected = items[ lists[i].selectedIndex ].value;
  57. if( !this.isCascadeableLevel(selected) ) {
  58. document.getElementById( 'mwProtect-cascade' ).checked = false;
  59. document.getElementById( 'mwProtect-cascade' ).disabled = true;
  60. return;
  61. }
  62. }
  63. }
  64. document.getElementById( 'mwProtect-cascade' ).disabled = false;
  65. },
  66. /**
  67. * Checks if a cerain protection level is cascadeable.
  68. * @param level {String}
  69. * @return {Boolean}
  70. */
  71. 'isCascadeableLevel': function( level ) {
  72. var cascadeLevels, len, i;
  73. cascadeLevels = mw.config.get( 'wgCascadeableLevels' );
  74. // cascadeLevels isn't defined on all pages
  75. if ( cascadeLevels ) {
  76. for ( i = 0, len = cascadeLevels.length; i < len; i += 1 ) {
  77. if ( cascadeLevels[i] === level ) {
  78. return true;
  79. }
  80. }
  81. }
  82. return false;
  83. },
  84. /**
  85. * When protection levels are locked together, update the rest
  86. * when one action's level changes
  87. *
  88. * @param source Element Level selector that changed
  89. */
  90. 'updateLevels': function(source) {
  91. if( !this.isUnchained() )
  92. this.setAllSelectors( source.selectedIndex );
  93. this.updateCascadeCheckbox();
  94. },
  95. /**
  96. * When protection levels are locked together, update the
  97. * expiries when one changes
  98. *
  99. * @param source Element expiry input that changed
  100. */
  101. 'updateExpiry': function(source) {
  102. if( !this.isUnchained() ) {
  103. var expiry = source.value;
  104. this.forEachExpiryInput(function(element) {
  105. element.value = expiry;
  106. });
  107. }
  108. var listId = source.id.replace( /^mwProtect-(\w+)-expires$/, 'mwProtectExpirySelection-$1' );
  109. var list = document.getElementById( listId );
  110. if (list && list.value != 'othertime' ) {
  111. if ( this.isUnchained() ) {
  112. list.value = 'othertime';
  113. } else {
  114. this.forEachExpirySelector(function(element) {
  115. element.value = 'othertime';
  116. });
  117. }
  118. }
  119. },
  120. /**
  121. * When protection levels are locked together, update the
  122. * expiry lists when one changes and clear the custom inputs
  123. *
  124. * @param source Element expiry selector that changed
  125. */
  126. 'updateExpiryList': function(source) {
  127. if( !this.isUnchained() ) {
  128. var expiry = source.value;
  129. this.forEachExpirySelector(function(element) {
  130. element.value = expiry;
  131. });
  132. this.forEachExpiryInput(function(element) {
  133. element.value = '';
  134. });
  135. }
  136. },
  137. /**
  138. * Update chain status and enable/disable various bits of the UI
  139. * when the user changes the "unlock move permissions" checkbox
  140. */
  141. 'onChainClick': function() {
  142. if( this.isUnchained() ) {
  143. this.enableUnchainedInputs( true );
  144. } else {
  145. this.setAllSelectors( this.getMaxLevel() );
  146. this.enableUnchainedInputs( false );
  147. }
  148. this.updateCascadeCheckbox();
  149. },
  150. /**
  151. * Returns true if the named attribute in all objects in the given array are matching
  152. */
  153. 'matchAttribute' : function( objects, attrName ) {
  154. var value = null;
  155. // Check levels
  156. for ( var i = 0; i < objects.length; i++ ) {
  157. var element = objects[i];
  158. if ( value == null ) {
  159. value = element[attrName];
  160. } else {
  161. if ( value != element[attrName] ) {
  162. return false;
  163. }
  164. }
  165. }
  166. return true;
  167. },
  168. /**
  169. * Are all actions protected at the same level, with the same expiry time?
  170. *
  171. * @return boolean
  172. */
  173. 'areAllTypesMatching': function() {
  174. return this.existingMatch
  175. && this.matchAttribute( this.getLevelSelectors(), 'selectedIndex' )
  176. && this.matchAttribute( this.getExpirySelectors(), 'selectedIndex' )
  177. && this.matchAttribute( this.getExpiryInputs(), 'value' );
  178. },
  179. /**
  180. * Is protection chaining off?
  181. *
  182. * @return bool
  183. */
  184. 'isUnchained': function() {
  185. var element = document.getElementById( 'mwProtectUnchained' );
  186. return element
  187. ? element.checked
  188. : true; // No control, so we need to let the user set both levels
  189. },
  190. /**
  191. * Find the highest protection level in any selector
  192. */
  193. 'getMaxLevel': function() {
  194. var maxIndex = -1;
  195. this.forEachLevelSelector(function(element) {
  196. if (element.selectedIndex > maxIndex) {
  197. maxIndex = element.selectedIndex;
  198. }
  199. });
  200. return maxIndex;
  201. },
  202. /**
  203. * Protect all actions at the specified level
  204. *
  205. * @param index int Protection level
  206. */
  207. 'setAllSelectors': function(index) {
  208. this.forEachLevelSelector(function(element) {
  209. if (element.selectedIndex != index) {
  210. element.selectedIndex = index;
  211. }
  212. });
  213. },
  214. /**
  215. * Apply a callback to each protection selector
  216. *
  217. * @param func callable Callback function
  218. */
  219. 'forEachLevelSelector': function(func) {
  220. var selectors = this.getLevelSelectors();
  221. for (var i = 0; i < selectors.length; i++) {
  222. func(selectors[i]);
  223. }
  224. },
  225. /**
  226. * Get a list of all protection selectors on the page
  227. *
  228. * @return Array
  229. */
  230. 'getLevelSelectors': function() {
  231. var all = document.getElementsByTagName("select");
  232. var ours = [];
  233. for (var i = 0; i < all.length; i++) {
  234. var element = all[i];
  235. if (element.id.match(/^mwProtect-level-/)) {
  236. ours[ours.length] = element;
  237. }
  238. }
  239. return ours;
  240. },
  241. /**
  242. * Apply a callback to each expiry input
  243. *
  244. * @param func callable Callback function
  245. */
  246. 'forEachExpiryInput': function(func) {
  247. var inputs = this.getExpiryInputs();
  248. for (var i = 0; i < inputs.length; i++) {
  249. func(inputs[i]);
  250. }
  251. },
  252. /**
  253. * Get a list of all expiry inputs on the page
  254. *
  255. * @return Array
  256. */
  257. 'getExpiryInputs': function() {
  258. var all = document.getElementsByTagName("input");
  259. var ours = [];
  260. for (var i = 0; i < all.length; i++) {
  261. var element = all[i];
  262. if (element.name.match(/^mwProtect-expiry-/)) {
  263. ours[ours.length] = element;
  264. }
  265. }
  266. return ours;
  267. },
  268. /**
  269. * Apply a callback to each expiry selector list
  270. * @param func callable Callback function
  271. */
  272. 'forEachExpirySelector': function(func) {
  273. var inputs = this.getExpirySelectors();
  274. for (var i = 0; i < inputs.length; i++) {
  275. func(inputs[i]);
  276. }
  277. },
  278. /**
  279. * Get a list of all expiry selector lists on the page
  280. *
  281. * @return Array
  282. */
  283. 'getExpirySelectors': function() {
  284. var all = document.getElementsByTagName("select");
  285. var ours = [];
  286. for (var i = 0; i < all.length; i++) {
  287. var element = all[i];
  288. if (element.id.match(/^mwProtectExpirySelection-/)) {
  289. ours[ours.length] = element;
  290. }
  291. }
  292. return ours;
  293. },
  294. /**
  295. * Enable/disable protection selectors and expiry inputs
  296. *
  297. * @param val boolean Enable?
  298. */
  299. 'enableUnchainedInputs': function(val) {
  300. var first = true;
  301. this.forEachLevelSelector(function(element) {
  302. if (first) {
  303. first = false;
  304. } else {
  305. element.disabled = !val;
  306. }
  307. });
  308. first = true;
  309. this.forEachExpiryInput(function(element) {
  310. if (first) {
  311. first = false;
  312. } else {
  313. element.disabled = !val;
  314. }
  315. });
  316. first = true;
  317. this.forEachExpirySelector(function(element) {
  318. if (first) {
  319. first = false;
  320. } else {
  321. element.disabled = !val;
  322. }
  323. });
  324. }
  325. };