Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
// ===============================================
// User script to add a dropdown box to the admin
// block form, offering CDIR range block codes
// when blocking IPs
// ===============================================

var patternIP = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/;

function isIP(txt) {
   var found = patternIP.exec(txt);
   if (found) {
      for (var i = 0; i < 4; i++) {
         if (found[i] > 255) return false;
      }
      return true;  
   } else return false;
}

// =============================================
// create the dropdown box if not done already,
// and (re-)populate it with CDIR ranges based
// on the one currently in the user input box
// =============================================
function makeOptions() {
   var frm = getBlockForm();
   if (frm) {
      var userBox = document.getElementsByName('wpTarget')[0];
      if (userBox) {
         var user = userBox.value;

         // check if our added div already exists
         var div = document.getElementById('rangeBlockSelectionDiv');

         // only proceed if the current user is an IP
         if (isIP(user)) {
            
            var select = document.getElementById('rangeBlockSelector');
            if (div) {
               // if div has already been created: reset the
               // dropdown box before repopulating it
               select.innerHTML = "";
            }
            else {
               // create the selection dropdown, inside a new
               // div
               div = document.createElement('div');
               div.id = "rangeBlockSelectionDiv";
               div.appendChild(document.createTextNode("Range block? "));
               select = document.createElement('select');
               // install onchange event handler on dropdown box:
               select.onchange = resetUser;
               select.id = "rangeBlockSelector";
               div.appendChild(select);
               var parent = frm.parentNode;
               var sibling = frm.nextSibling;
               if (sibling) {
                  parent.insertBefore(div, sibling)
               }
               else {
                  parent.appendChild(div);
               }
            }
            // default value in dropdown box is single IP
            var opt = document.createElement('option');
            opt.value = user;
            opt.innerHTML = "single IP (" + user + ")";
            select.appendChild(opt);
            // offer range blocks from /26 to /16            
            var maxMask = 26;
            var minMask = 16;
            for (var i = maxMask; i >= minMask; i--) {
               opt = makeOption(user, i);
               select.appendChild(opt);
            }
            // make div visible
            div.style.display = null;
         }
         else {
            // make div invisible if it exists
            if (div) div.style.display = "none";
         }
      }
   }
}

function makeOption(ip, mask) {
   var parts = patternIP.exec(ip);
   // turn IP string into a single 32-bit integer:
   var ipVal = (parts[1] * 0x1000000) | 
               (parts[2] * 0x10000)   |
               (parts[3] * 0x100)     |
               parts[4];
   // get the binary mask
   var maskVal = Math.pow(2, (32-mask)) - 1;
   var rgVal = ipVal & maskVal;
   // make sure the result is treated as an unsigned integer:
   if (rgVal < 0) rgVal += 0x100000000;
   rgVal = (ipVal - rgVal);
   // translate back into four-byte representation:
   parts = [0,0,0,0];
   for (i = 3; i>=0; i--) {
      parts[i] = rgVal % 0x100;
      rgVal -= parts[i];
      rgVal = rgVal >>> 8;
   }
   var cdir = parts[0] + "." + 
              parts[1] + "." + 
              parts[2] + "." + 
              parts[3] + "/" + mask;
   var size = Math.pow(2, (32-mask));
   var desc = cdir + " (" + size + " IPs )";
   var opt = document.createElement('option');
   opt.value = cdir;
   opt.innerHTML = desc;
   return opt;
}

function getBlockForm() {
   // the standard block form has no id, so we need to identify it
   // by its action attribute
   for (var i = document.forms.length - 1; i >= 0; i--) {
      var frm = document.forms[i];
      if (frm.action.search(/\bSpecial:Block\b/) >= 0) {
         if (! frm.id) frm.id = "_myBlockForm";
         document.forms._myBlockForm = frm;   
         return frm;
      }
   }
   return null;
}

function resetUser() {
   // Onchange event handler of the dropdown box.
   // Copy selected value back into user input field. 
   var val = this.value;
   var userBox = document.getElementsByName('wpTarget')[0];
   userBox.value = val;
   var act = document.forms._myBlockForm.action;
   act = act.replace(/Special:Block\/.+/, "Special:Block/" + val);
   document.forms._myBlockForm.action = act;
}

// install this script as an onload hook
$(function() { 
   // only proceed if we are on the Special:Block page
   if (document.title.match(/^Block user - Wikipedia, the free encyclopedia$/)) {
      var userBox = document.getElementsByName('wpTarget')[0];
      if (userBox) {
         // call the update function both now, and
         // on later onchange events
         makeOptions();
         userBox.onchange = makeOptions;  
      }
   }
});