Difference between revisions of "User:Eighty5cacao/markBlockedPlus.js"

From Pin Eight
Jump to: navigation, search
m (oops, I had JavaScript mixed up with Lua)
m (defaultsort)
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
 
/* Based on the original markblocked script [[wikipedia:ru:MediaWiki:Gadget-markblocked.js]]
 
/* Based on the original markblocked script [[wikipedia:ru:MediaWiki:Gadget-markblocked.js]]
   by Alex Smotrov et al., __NOINDEX__ original idea by Kalan
+
   by Alex Smotrov et al., original idea by Kalan __NOINDEX__{{DEFAULTSORT:markBlockedPlus.js}}
 
   Additional inspiration from [[wikipedia:User:Equazcion/sysopdetector.js]]
 
   Additional inspiration from [[wikipedia:User:Equazcion/sysopdetector.js]]
 
*/
 
*/
Line 13: Line 13:
 
         try {
 
         try {
 
             return Array.isArray( maybeArray );
 
             return Array.isArray( maybeArray );
 +
            // throws if browser is too old for Array.isArray to exist
 
         } catch ( eUnused ) {
 
         } catch ( eUnused ) {
 
             try {
 
             try {
 
                 return ( typeof maybeArray !== 'string' && ( maybeArray === [] || maybeArray[0] !== undefined ) );
 
                 return ( typeof maybeArray !== 'string' && ( maybeArray === [] || maybeArray[0] !== undefined ) );
 +
                // maybeArray[0] throws if not an array
 
             } catch ( eUnused2 ) {
 
             } catch ( eUnused2 ) {
 
                 return false;
 
                 return false;

Latest revision as of 04:23, 22 August 2019

/* Based on the original markblocked script [[wikipedia:ru:MediaWiki:Gadget-markblocked.js]]
   by Alex Smotrov et al., original idea by Kalan __NOINDEX__{{DEFAULTSORT:markBlockedPlus.js}}
   Additional inspiration from [[wikipedia:User:Equazcion/sysopdetector.js]]
*/
var MarkBlockedPlus = {

    defaultPref: function ( cfig, dflt ) {
        return ( typeof cfig === typeof dflt ? cfig : dflt );
    },

    // replacement for jQuery's deprecated isArray
    hackIsArray: function ( maybeArray ) {
        try {
            return Array.isArray( maybeArray );
            // throws if browser is too old for Array.isArray to exist
        } catch ( eUnused ) {
            try {
                return ( typeof maybeArray !== 'string' && ( maybeArray === [] || maybeArray[0] !== undefined ) );
                // maybeArray[0] throws if not an array
            } catch ( eUnused2 ) {
                return false;
            }
        }
        // should not be reached
        return false;
    },

    // expanded later
}

// TODO: Store the defaulted prefs in MarkBlockedPlus instead of writing them back to window
window.mbShowUnregistered = MarkBlockedPlus.defaultPref( window.mbShowUnregistered, true );
window.mbShowGroups = MarkBlockedPlus.defaultPref( window.mbShowGroups, true );
window.mbShowEditCount = MarkBlockedPlus.defaultPref( window.mbShowEditCount, true );
window.mbShowRegDateAbs = MarkBlockedPlus.defaultPref( window.mbShowRegDateAbs, true );
window.mbRemoveWaitingCSSOnError = MarkBlockedPlus.defaultPref( window.mbRemoveWaitingCSSOnError, true );
window.mbLinkClassifierRedirWarn = MarkBlockedPlus.defaultPref( window.mbLinkClassifierRedirWarn, true );
window.mbLinkClassifierTimeoutHack = MarkBlockedPlus.defaultPref( window.mbLinkClassifierTimeoutHack, true );

MarkBlockedPlus.showRedirectWarning = window.mbLinkClassifierRedirWarn;

// Exposed globally for compatibility with original markblocked
function markBlocked( container ) {
    var contribPageNames = [ 'Contributions' ];

    function scrapeContribLink( ) {
        //something went wrong, so get local alias for "Contributions" from "my contribs" link on top
        var mwCont = /:([^\/]+)\//.exec( $( '#pt-mycontris a' ).attr( 'href' ) );
        if( mwCont ) { mwCont = mwCont[1]; contribPageNames[0] = mwCont; }
        MarkBlockedPlus.reallyMarkBlocked( container, contribPageNames );
    }

    function getContribNameCallback( r, sts, xhr ) {
        if ( !r || !r.query ) { scrapeContribLink( ); return; }
        if ( !r.query.specialpagealiases ) { scrapeContribLink( ); return; }
        for ( pag in r.query.specialpagealiases ) {
           if ( pag.realname == 'Contributions' ) {
               if ( pag.aliases ) { contribPageNames = pag.aliases; break; }
           }
        }
        MarkBlockedPlus.reallyMarkBlocked( container, contribPageNames );
    }

    function getContribNamePanic( xhr, textStatus, errorThrown ) {
        //TODO: error reporting
        scrapeContribLink( );
    }

    if ( typeof window.mbLocalContribsName == 'string' ) {
        contribPageNames[0] = window.mbLocalContribsName;
        MarkBlockedPlus.reallyMarkBlocked( container, contribPageNames );
    }
    else if ( MarkBlockedPlus.hackIsArray( window.mbLocalContribsName ) ) {
        contribPageNames = window.mbLocalContribsName;
        MarkBlockedPlus.reallyMarkBlocked( container, contribPageNames );
    }
    else {
        var q = {
            format:'json',
            action:'query',
            meta:'siteinfo',
            siprop:'specialpagealiases'
        }
        $.ajax( {
            url:mw.util.wikiScript('api'),
            dataType:'json',
            type:'POST',
            data:q,
            rawdata:q,
            success:getContribNameCallback,
            error:getContribNamePanic
        } );
    }
}

MarkBlockedPlus.reallyMarkBlocked = function ( container, contribPageNames ) {
    //----- start of reallyMarkBlocked

var contentLinks = container ? $( container ).find( 'a' ) : $( '#content a' ).add( '#ca-nstab-user a' );

if ( typeof window.mbTempStyle == 'string' && typeof window.mbTemp2Style != 'string' ) window.mbTemp2Style = window.mbTempStyle;

mw.util.addCSS('\
.user-blocked-temp{'   + MarkBlockedPlus.defaultPref(window.mbTempStyle,  'opacity: 0.7; text-decoration: line-through') + '}\
.user-blocked-temp2{'  + MarkBlockedPlus.defaultPref(window.mbTemp2Style, 'opacity: 0.7; text-decoration: line-through') + '}\
.user-blocked-indef{'  + MarkBlockedPlus.defaultPref(window.mbIndefStyle, 'opacity: 0.4; font-style: italic; text-decoration: line-through') + '}\
.user-blocked-tipbox{' + MarkBlockedPlus.defaultPref(window.mbTipBoxStyle,     'font-size:smaller; background:#FFFFF0; border:1px solid #FEA; padding:0 0.3em; color:#AAA') + '}\
.user-info-tipbox{'    + MarkBlockedPlus.defaultPref(window.mbTipBoxInfoStyle, 'font-size:smaller; background:#FFFFF0; border:1px solid #FEA; padding:0 0.3em; color:#AAA') + '}\
')
MarkBlockedPlus.tooltip = MarkBlockedPlus.defaultPref( window.mbTooltip, '; blocked ($1) by $2: $3 ($4 ago)' );
MarkBlockedPlus.tipBoxCharsToTrim = MarkBlockedPlus.defaultPref( window.mbTipBoxCharsToTrim, 2 );
//TODO: Figure out a sane way to let users customize the rest of the tooltip.

//get all aliases for user:, user_talk: and special:
MarkBlockedPlus.userNS = [ ];
var userNonTalkNS = [ ], specialNS = [ ];
var firstSpecialNS = '';
var nIDs = mw.config.get( 'wgNamespaceIds' );
for (var ns in nIDs) {
  var colonifiedNS = ns + ':';
  var cleanNS = colonifiedNS.replace(/_/g, ' ');
  if ( nIDs[ns] == 2 ) {
    MarkBlockedPlus.userNS.push( cleanNS );
    userNonTalkNS.push( cleanNS );
  }
  else if ( nIDs[ns] == 3 ) {
    MarkBlockedPlus.userNS.push( cleanNS );
  }
  else if ( nIDs[ns] == -1 ) {
    if ( firstSpecialNS === '' ) { firstSpecialNS = colonifiedNS.charAt(0).toUpperCase( ) + colonifiedNS.substr(1); }
    specialNS.push( cleanNS );
  }
}

var contribNames = contribPageNames;

//RegExp for all titles that are  User:| User_talk: | Special:Contributions/ (for userscripts)
var userTitleRX = new RegExp('^'
 + '(' + MarkBlockedPlus.userNS.join('|')
 + '|(?:' + specialNS.join('|') + ')(?:' + contribNames.join('|') + ')\\/'
 + ')'
 + '([^\\/#]+)$', 'i');

//RegExp for links. XXX: Is it safe for us to assume that $1 is always at the end of wgArticlePath?
var wAP = mw.config.get( 'wgArticlePath' ), wS = mw.config.get( 'wgScript' );
var cleanArticlePath = wAP.replace('$1', '');
var articleRX = new RegExp( '^' + cleanArticlePath + '([^#]+)' );
var scriptRX =  new RegExp( '^' + wS + '\\?title=([^#&]+)' );

MarkBlockedPlus.userLinks = { };
var url, ma, pgTitle;


//find all "user" links and save them in userLinks : { 'users': [<link1>, <link2>, ...], 'user2': [<link3>, <link3>, ...], ... }
contentLinks.each(function(i/* unused */, lnk) {
   //exclude links added to user(talk) pages by [[wikipedia:User:Bility/copySectionLink]] or built-in MW feature
   var $lnk = $( lnk );
   var myId = $lnk.attr( 'id' );
   if ( myId && /^sectiontitlecopy\d+$/.test( myId ) ) return;
   if ( $lnk.hasClass( 'mw-headline-anchor' ) ) return;
   url = $lnk.attr( 'href' );
   if ( !url ) return;

   //for compatibility with other scripts which may add redirect=no to links
   var redirNoRegex = ( wAP.indexOf( '?' ) == -1 ? /\?redirect=no$/ : /&redirect=no$/ );
   url = url.replace( redirNoRegex, '' );

   if ( url.charAt(0) != '/' || url.indexOf( 'friendlywelcome=' ) != -1 ) { return; }
   else if ( ma = articleRX.exec( url ) ) { pgTitle = ma[1]; }
   else if ( ma =  scriptRX.exec( url ) ) { pgTitle = ma[1]; }
   else { return; }
   pgTitle = decodeURIComponent( pgTitle ).replace( /_/g, ' ' );
   user = userTitleRX.exec( pgTitle );
   if ( !user ) return;
   user = user[2];
   user = user.replace( /^\s+|\s+$/g, '' );
   if ( user == '0' || user == 'newbies' || user.length > 64 ) { $lnk.addClass( 'baduserlink' ); return; }
   if ( /[\u0080-\u009F\u00A0\u2000-\u200F\u2028-\u202F\u3000\uE000-\uF8FF\uFFFD]/.test( user ) ) { $lnk.addClass( 'baduserlink' ); return; }
   user = user.charAt(0).toUpperCase( ) + user.substr(1);
   var doubledPrefix = userTitleRX.exec( user );
   if( doubledPrefix ) { $lnk.addClass( 'baduserlink' ); return; }
   $lnk.addClass( 'userlink' );
   if( !( MarkBlockedPlus.hackIsArray( MarkBlockedPlus.userLinks[user] ) ) ) MarkBlockedPlus.userLinks[user] = [ ];
   MarkBlockedPlus.userLinks[user].push( lnk );

   //optionally replace userpage redlinks with contributions links
   if ( window.mbReplaceUserRedLinksWithContribs === true && $lnk.hasClass( 'new' ) ) {
     var userNonTalkNSJoined = userNonTalkNS.join('|');
     var userNonTalkTitleRX = new RegExp('^'
      + '(' + userNonTalkNSJoined
      + ')'
      + '([^\\/#]+)$', 'i');

     if ( !( userNonTalkTitleRX.test( pgTitle ) ) ) { return; }

     // "Special:" "Contributions" "/"
     var contribTitlePfx = firstSpecialNS + contribNames[0] + '/';
     // "Special:" "Contributions" "/" "user_name"
     var contribFullTitle = contribTitlePfx + user.replace( / /g, '_' );

     //if we're viewing a contributions page, don't create self links
     if ( mw.config.get( 'wgPageName' ) === contribFullTitle ) { return; }

     //in all: wgArticlePath-without-$1 Special: Contributions / user_name
     var newURL = cleanArticlePath + contribFullTitle;
     $lnk.attr( 'href', newURL );

     var newTitle = $lnk.attr( 'title' );
     if ( newTitle ) {
       var userNonTalkPrefixRX = new RegExp('^'
        + '(' + userNonTalkNSJoined
        + ')', 'i');
       newTitle = newTitle.replace( userNonTalkPrefixRX, contribTitlePfx );
       //XXX: make compatible with non-English wikis
       newTitle = newTitle.replace( ' (page does not exist)', ' (no user page)' );
       $lnk.attr( 'title', newTitle );
     }

     $lnk.removeClass( 'new' );
     $lnk.addClass( 'userlink-redreplaced' );
   }
})


//convert users into array
var users = [ ];
for ( var u in MarkBlockedPlus.userLinks ) { users.push( u ); }
if ( users.length === 0 ) { return; }

//API request
MarkBlockedPlus.apiRequests = 0;
MarkBlockedPlus.waitingCSS = mw.util.addCSS( 'a.userlink {opacity:' + MarkBlockedPlus.defaultPref(window.mbLoadingOpacity, 0.85) + '}' );
while ( users.length > 0 ) {
  MarkBlockedPlus.apiRequests++;
  var formattedUsers = users.splice(0,50).join('|');
  $.post(
    mw.util.wikiScript('api') + '?format=json&action=query',
    { list: 'blocks|users',
      bklimit: 150, bkusers: formattedUsers,    //XXX: I increased this limit from 100...could that cause problems?
      bkprop: 'user|by|timestamp|expiry|reason',
      uslimit: 51, ususers: formattedUsers,     //+1 just to be safe (XXX: why? could this actually cause problems?)
      usprop: 'groups|editcount|registration' },
    MarkBlockedPlus.markLinks
  )
}

return; //done sending API requests
}//-- end of reallyMarkBlocked


//callback: receive data and mark links
MarkBlockedPlus.markLinks = function ( resp, status, xhr ) {
  function countRequestAsDone( bSuccess ) {
    if( --MarkBlockedPlus.apiRequests == 0 ){ //last response
      MarkBlockedPlus.waitingCSS.disabled = ( bSuccess || window.mbRemoveWaitingCSSOnError );
      $( '#ca-showblocks' ).remove( ); //remove added portlet link (TODO: only if success?)
    }
  }

  MarkBlockedPlus.wgServerTime = new Date( xhr.getResponseHeader( 'Date' ) );
  var list, list2, blk, usr, tip, tip2, links, kmax, lnk, curLinkRW, curLinkOldTitle;

  if( !resp ){
    if ( window.mbReportApiErrors === true ){
      MarkBlockedPlus.waitingCSS.disabled = window.mbRemoveWaitingCSSOnError;
      throw new Error('markBlockedPlus: No response from API');
    }
    else{
      countRequestAsDone(false); return;
    }
  }

  if( !(list=resp.query) ){
    if ( window.mbReportApiErrors === true ){
      MarkBlockedPlus.waitingCSS.disabled = window.mbRemoveWaitingCSSOnError;
      var errStr = 'markBlockedPlus: API error';
      if ( resp.error ){
        errStr += ': code: ' + resp.error.code + ', info: ' + resp.error.info;
      }
      throw new Error(errStr);
    }
    else{
      countRequestAsDone(false); return;
    }
  }
  else {
    list2=list.users;
    list=list.blocks;
  }

  var mbShowRegDate = window.mbShowRegDateAbs || window.mbShowRegDateRel;
  var mbUsingNewFeatures = window.mbShowUnregistered || window.mbShowGroups || window.mbShowEditCount || mbShowRegDate;
  var mbNewFeaturesValid = mbUsingNewFeatures && list2;
  var userNSRX = new RegExp( '^' + '(' + MarkBlockedPlus.userNS.join('|') + ')', 'i' );

  if ( list ){
    for( var i=0; i<list.length; i++){
      blk = list[i]
      if( /^in/.test(blk.expiry) ){
        clss = 'user-blocked-indef';
        blTime = blk.expiry;
      }else{
        var rawBlTime = MarkBlockedPlus.parseTS(blk.expiry) - MarkBlockedPlus.parseTS(blk.timestamp);
        var blTimeRem = MarkBlockedPlus.parseTS(blk.expiry) - MarkBlockedPlus.wgServerTime;
        var timeToCheck = ( window.mbLongThreshIsRemainingTime === true ? blTimeRem : rawBlTime );
        if ( typeof window.mbPseudoIndefThreshold == 'number' && timeToCheck >= window.mbPseudoIndefThreshold ) {
          clss = 'user-blocked-indef';
        }
        else {
          clss = 'user-blocked-temp';
          if ( typeof window.mbLongThreshold == 'number' && timeToCheck >= window.mbLongThreshold ) clss += '2';
        }
        blTime = MarkBlockedPlus.inHours( rawBlTime );
      }
      if( blk.reason ) {
        var preventive = /(pr(otec|even)t|in case|[ei]nsure)/i.test(blk.reason);

        //XXX: uw-compblock blocks should not be considered preventive...
        if ( /(compr[io]mise|(been|got|was) ?hack|pass[- ]?word|hijack|uw-compblock)/i.test(blk.reason) ) {
          clss += ( preventive ? ' user-blocked-preventcompromise' : ' user-blocked-compromised' );
        }

        if ( /(wiki[- ]?break|(self|user)[- ]?request|retir(ed|ement|ing))/i.test(blk.reason) && !( /\b(abus|ban|sock)/i.test(blk.reason) ) ) {
          clss += ' user-blocked-wikibreakenforce';
        }

        if ( /decease|ha(d|s|ve) died|pass(ed|ing)? (away|on)|mortem/i.test(blk.reason) ) {
          clss += ' user-blocked-deceased';
        }
        else if ( /is dead/i.test(blk.reason) && preventive ) {
          clss += ' user-blocked-deceased';
        }
      }
      if ( blk.user == blk.by ) clss += ' user-blocked-self';
      tip = MarkBlockedPlus.tooltip.replace('$1', blTime).replace('$2', blk.by).replace('$3', blk.reason)
            .replace('$4', MarkBlockedPlus.inHours( MarkBlockedPlus.wgServerTime - MarkBlockedPlus.parseTS(blk.timestamp) ) );
      links = MarkBlockedPlus.userLinks[blk.user];
      kmax = ( links === undefined ? 0 : links.length ); //TODO: add error reporting
      for (var k=0; k<kmax; k++){
         lnk = $(links[k]).addClass(clss);
         curLinkRW = '';
         curLinkOldTitle = lnk.attr('title') + '';
         if( MarkBlockedPlus.showRedirectWarning && !mbNewFeaturesValid && ( lnk.hasClass( 'redirect' ) || lnk.hasClass( 'mw-redirect' ) ) ){
           if ( userNSRX.test( MarkBlockedPlus.skipLCAppend( curLinkOldTitle ) ) === false ) {
             curLinkRW = '\nBlock log for ' + blk.user + '; redirects outside this wiki\'s user namespaces';
             lnk.addClass( 'userlink-redirnonuser' );
           }
           else if ( MarkBlockedPlus.tipNameDiffers( curLinkOldTitle, blk.user ) ) {
             curLinkRW = '\nBlock log for ' + blk.user + '; redirects to other username or IP';
             lnk.addClass( 'userlink-redirothername' );
           }
         }
         if( window.mbTipBox === true ){
           $('<span class="user-blocked-tipbox">'+MarkBlockedPlus.defaultPref( window.mbTipBoxText, 'B' )+'</span>').attr('title', tip.substr(MarkBlockedPlus.tipBoxCharsToTrim) + curLinkRW).insertBefore(lnk);
         }else{
           if ( lnk.children( ) ) lnk.children( ).removeAttr( 'title' );
           lnk.attr( 'title', curLinkOldTitle + tip + curLinkRW );
         }
      }
    }
  }

  if ( mbNewFeaturesValid ){
    for( var i=0; i<list2.length; i++){
      usr = list2[i];
      tip2 = '';
      if( usr.missing === '' ){
        if( window.mbShowUnregistered === true ) tip2 = MarkBlockedPlus.defaultPref( window.mbUnregisteredText, '\nnot registered' )
      }
      else if( usr.invalid !== '' ){
        if ( (window.mbShowGroups === true) && usr.groups ){
          tip2 += '\n' + MarkBlockedPlus.remStar( usr.groups );
        }
        if ( (window.mbShowEditCount === true) && usr.editcount ){
          tip2 += '\n' + usr.editcount + ' edit';
          if ( usr.editcount != '1' ) tip2 += 's';
          if ( mbShowRegDate && usr.registration ){
            tip2 += ' since ';
            if ( (window.mbShowRegDateAbs === true) && (window.mbShowRegDateRel === true) ){
              tip2 += usr.registration + ' (' + MarkBlockedPlus.inHours( MarkBlockedPlus.wgServerTime - MarkBlockedPlus.parseTS(usr.registration) ) + ' ago)';
            }
            else if ( window.mbShowRegDateAbs === true ){
              tip2 += usr.registration;
            }
            else /* relative only */{
              tip2 += MarkBlockedPlus.inHours( MarkBlockedPlus.wgServerTime - MarkBlockedPlus.parseTS(usr.registration) ) + ' ago';
            }
          }
        }
        else{
          if ( mbShowRegDate && usr.registration ){
            tip2 += '\ncreated ';
            if ( (window.mbShowRegDateAbs === true) && (window.mbShowRegDateRel === true) ){
              tip2 += usr.registration + ' (' + MarkBlockedPlus.inHours( MarkBlockedPlus.wgServerTime - MarkBlockedPlus.parseTS(usr.registration) ) + ' ago)';
            }
            else if ( window.mbShowRegDateAbs === true ){
              tip2 += usr.registration;
            }
            else /* relative only */{
              tip2 += MarkBlockedPlus.inHours( MarkBlockedPlus.wgServerTime - MarkBlockedPlus.parseTS(usr.registration) ) + ' ago';
            }
          }
        }
      }
      links = MarkBlockedPlus.userLinks[usr.name];
      kmax = ( links === undefined ? 0 : links.length ); //TODO: add error reporting
      for (var k=0; k<kmax; k++){
         lnk = $(links[k]);
         curLinkRW = '';
         curLinkOldTitle = lnk.attr('title') + '';
         if ( MarkBlockedPlus.showRedirectWarning && ( lnk.hasClass( 'redirect' ) || lnk.hasClass( 'mw-redirect' ) ) ){
           if ( userNSRX.test( MarkBlockedPlus.skipLCAppend( curLinkOldTitle ) ) === false ) {
             curLinkRW = '\nInfo shown for ' + usr.name + '; redirects outside this wiki\'s user namespaces';
             lnk.addClass( 'userlink-redirnonuser' );
           }
           else if ( MarkBlockedPlus.tipNameDiffers( curLinkOldTitle, usr.name ) ) {
             curLinkRW = '\nInfo shown for ' + usr.name + '; redirects to other username or IP';
             lnk.addClass( 'userlink-redirothername' );
           }
         }
         if( (window.mbTipBoxInfo === true) && tip2 != '' ){
           $('<span class="user-info-tipbox">'+MarkBlockedPlus.defaultPref( window.mbTipBoxInfoText, 'i' )+'</span>').attr('title', tip2.substr(1) + curLinkRW).insertBefore(lnk)
         }else{
           if ( lnk.children( ) ) lnk.children( ).removeAttr( 'title' );
           lnk.attr( 'title', curLinkOldTitle + tip2 + curLinkRW );
         }
         if ( usr.missing === '' ) { lnk.addClass( 'userlink-missing' ); }
      }
    }
  }

  countRequestAsDone(true);
} //-- end of markLinks


//--------AUX functions

//20081226220605  or  2008-01-26T06:34:19Z   -> date
MarkBlockedPlus.parseTS = function ( ts ) {
 var m = ts.replace(/\D/g,'').match(/(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/);
 return new Date ( Date.UTC(m[1], m[2]-1, m[3], m[4], m[5], m[6]) );
}

//milliseconds -> "2:30" or 5d 6h or 21d
MarkBlockedPlus.inHours = function ( ms ) {
 var mm = Math.round(ms/60000);
 if( !mm ) return Math.round(ms/1000)+'s';
 var hh = Math.floor(mm/60); mm %= 60;
 var dd = Math.floor(hh/24); hh %= 24;
 var yy = Math.floor(dd/365);
 if ( yy && ( window.mbShowYears === true ) ){
   dd %= 365;
   return yy + (dd ? 'yr ' + dd + 'd' : 'yr');
 }
 else if (dd) return dd + 'd' + ((dd<10 && hh>0)?' '+hh+'h':'');
 else return hh + ':' + MarkBlockedPlus.leadZero(mm);
}

//add leading zero to single digits: 6 -> '06'
//was named "zz" in original markblocked
MarkBlockedPlus.leadZero = function ( v ) {
 if( v <= 9 ) v = '0' + v;
 return v;
}

MarkBlockedPlus.remStar = function ( oldGrps ) {
    var cleanGrps = [ ];
    for ( var i = 0; i < oldGrps.length; i++ ) {
        if ( oldGrps[i] != '*' ) cleanGrps.push( oldGrps[i] );
    }
    return cleanGrps;
}

MarkBlockedPlus.skipLCAppend = function ( ttip ) {
    var myTtip = ttip;
    if ( window.LinkClassifierRedirTitleAppend === true ) {
       var lcAppendStrToCheck = '\n⤷';
       var lcAppendedMsgPos = myTtip.indexOf( lcAppendStrToCheck );
       myTtip = myTtip.substr( lcAppendedMsgPos + lcAppendStrToCheck.length );
    }
    return myTtip;
}

MarkBlockedPlus.tipNameDiffers = function ( ttip, usrnm ) {
    var myTtip = MarkBlockedPlus.skipLCAppend( ttip );

    var startPos = myTtip.indexOf( ':' );
    if ( startPos <= 0 ) return false; //no valid username found - don't show warning

    var fragEnd = MarkBlockedPlus.tooltip.indexOf( '$' );
    var fragment;
    if ( fragEnd == -1 ) {
        fragment = MarkBlockedPlus.tooltip;
    }
    else if ( fragEnd == 0 ) {
        //TODO: To handle this case properly, we should pass the actual values of the
        //$-parameters into this function. Until then, document that window.mbTooltip
        //should not begin with $
        fragment = 'This wont' + Math.random( ) + 'match anything';
    }
    else {
        fragment = MarkBlockedPlus.tooltip.substr( 0, fragEnd );
    }

    var slashPos   = myTtip.indexOf( '/' );      if ( slashPos   == -1 ) slashPos   = myTtip.length;
    var anchorPos  = myTtip.indexOf( '#' );      if ( anchorPos  == -1 ) anchorPos  = myTtip.length;
    var lineBrkPos = myTtip.indexOf( '\n' );     if ( lineBrkPos == -1 ) lineBrkPos = myTtip.length;
    var blkMsgPos  = myTtip.indexOf( fragment ); if ( blkMsgPos  == -1 ) blkMsgPos  = myTtip.length;
    var endPos = Math.min( slashPos, anchorPos, lineBrkPos, blkMsgPos );

    startPos++;
    if ( endPos <= startPos ) return false; //no valid username found - don't show warning

    return ( usrnm != myTtip.substr( startPos, endPos - startPos ) );
}



//start on some pages, making sure [-script we don't use here-] has finished
MarkBlockedPlus.setup = function ( ) {
   // If the wiki supports ResourceLoader, make sure mw.util has loaded
   if ( mw.loader && typeof mw.loader.using === 'function' ) {
       mw.loader.using( [ 'mediawiki.util' ], function ( ) {
           /*  dummy
               XXX: this might not be useful as written...
           */
       });
   }

   var linkclassifierDetected = typeof LinkClassifier === 'object';

   MarkBlockedPlus.showRedirectWarning =
     MarkBlockedPlus.showRedirectWarning && linkclassifierDetected;

   //don't really need strict comparisons here
   var shouldWaitForLinkClassifier = linkclassifierDetected &&
     window.LinkClassifierSupportsFuncChain &&
     !window.LinkClassifierOnDemand &&
     !window.mbNoAutoStart;

   if ( shouldWaitForLinkClassifier ) {
     if ( window.mbLinkClassifierTimeoutHack ) {
       var wBRT = mw.config.get( 'wgBackendResponseTime' );
       if ( typeof wBRT !== 'number' || !isFinite( wBRT ) ) { wBRT = 100; }
       else { wBRT = Math.min( Math.max( wBRT, 50 ), 1000 ); }
       var rndTOMin = Math.round( Math.sqrt( wBRT ) * Math.log1p( wBRT ) );
       var rndTOMax = Math.round( wBRT * Math.E );
       var rndTO = rndTOMin + Math.floor( Math.random( ) * ( rndTOMax - rndTOMin + 1 ) );
       if ( typeof window.LinkClassifierChainedFunc === 'function' ) {
         //Don't overwrite the function if it exists, because it may already have been called.
         //It might have been used to import us, after all.
         setTimeout( markBlocked, rndTO );
       }
       else {
         window.LinkClassifierChainedFunc = function ( ) {
           setTimeout( markBlocked, rndTO );
         }
       }
     }
     else {
       if ( typeof window.LinkClassifierChainedFunc === 'function' ) {
         markBlocked( );
       }
       else {
         window.LinkClassifierChainedFunc = markBlocked;
       }
     }
   }
   else
     $(function(){
       if( window.mbNoAutoStart === true ) {
         var plnk = mw.util.addPortletLink( MarkBlockedPlus.defaultPref( window.mbOnDemandLinkLoc, 'p-cactions' ), 
           '#mb-' + Math.random( ),
           MarkBlockedPlus.defaultPref( window.mbOnDemandLinkText, 'XX' ),
           'ca-showblocks'
         );
         $( plnk ).click( function ( e ) {
           e.preventDefault( );
           markBlocked( );
         });
       }
       else { markBlocked( ); }
     })
}

switch ( mw.config.get( 'wgAction' ) ) {
 case 'edit':
 case 'submit':
   if ( window.mbEnableWhenEditing ) { MarkBlockedPlus.setup( ); }
   break;
 case 'purge':
   //shouldn't happen on recent MW; action=purge should redirect (or prompt for confirmation?) rather than showing page content
   //just give up
   break;
 case 'view':
   if ( mw.config.get( 'wgNamespaceNumber' ) === 0 && !( window.mbEnableOnMainspaceDiff === true && document.URL.indexOf( 'diff=' ) != -1 ) ) { break; }
   //otherwise continue with default
 default: //'history', etc.
   MarkBlockedPlus.setup( );
}