Участник:Js/rcExtraData.js

Материал из Википедии — свободной энциклопедии
Страница персонального оформления. У этого JS-кода есть документация: Участник:Js/rcExtraData.
После сохранения очистите кэш браузера.
//var rcDataSortable = true

if( /Watchlist|Recentchanges/.test(mw.config.get('wgCanonicalSpecialPageName')) ){
  $.when(
    $.ready,
    mw.loader.using(['mediawiki.cookie', 'mediawiki.util']),
    $.getScript(mw.config.get('wgScript') + '?title=User:Js/apl.js&action=raw&ctype=text/javascript')
  )
	.then(rcExtraData);
  if( window.rcDataSortable ) mw.loader.load('jquery.tablesorter')
}




function rcExtraData(){

 var pgTitle = {},
 rcQuery = { list:'abuselog', afllimit:100, aflprop:'ids|filter|user|title|action|result|timestamp' }
 
 switch( mw.config.get('wgCanonicalSpecialPageName') ){
 
  case 'Watchlist': //add request for new pages
    //calculate from parameter from URL &days=
    var from = /[?&]days=([\d\.]+)/.exec( document.URL )
    if( !from || $('#namespace').val() ) return false //do not work on namespace-filtered watchlist
    var d = new Date() //and then try to ajust to server time using time from [[MediaWiki:Wlnote‎]] 
    var ma = $('#mw-watchlist-options').text().match(/(\d\d):(\d\d),[^\d]* (\d\d?)/)
    if(ma) { d.setUTCDate(ma[3]); d.setUTCHours(ma[1]); d.setUTCMinutes(ma[2]); d.setUTCSeconds(0) }
    var dateDays = new Date(d - from[1] * 24 * 3600 * 1000);
    
    // Код этого блока был добавлен позже
    var fromParam = /[?&]from=([\d\.]+)/.exec( document.URL );
	var dateFromMatches = fromParam && fromParam[1].match(/^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/);
	if (dateFromMatches) {
		var year = Number(dateFromMatches[1]);
		var month = Number(dateFromMatches[2]) - 1;
		var day = Number(dateFromMatches[3]);
		var hours = Number(dateFromMatches[4]);
		var minutes = Number(dateFromMatches[5]);
		var seconds = Number(dateFromMatches[6]);
		var dateFrom = new Date(Date.UTC(year, month, day, hours, minutes, seconds));
	}
	var date = dateFrom > dateDays ? dateFrom : dateDays;
    
    from = date.toISOString();
    rcQuery.aflend = from
    //also request new pages, unless disabled
    if( !window.rcDataNoNewPages ){
      rcQuery.list += '|recentchanges'
      $.extend(rcQuery, { rctype: 'new', rclimit: 50, rcshow: '!bot', rcend: from,
                          rcprop: 'title|user|parsedcomment|sizes|flags|redirect' })
    }                          
    //also request some usercontribs

    if( !window.rcDataNoContribs ){
      var users = wlContribs()
      //mw.util.$content.find('a[href*=":Contributions/"]').hover(hoverContribLink)
      mw.util.$content.delegate('a[href*=":Contributions/"]', 'mouseover', hoverContribLink)
      if( users ){
        rcQuery.list += '|usercontribs'
        $.extend(rcQuery, { ucuser: users, uclimit: 100, ucend: from, 
                            ucprop:'ids|title|timestamp|parsedcomment' } )
      }
    
    }
    
    break

  case 'Recentchanges': 
    //extra feature: make main tab on top to be "only new" link
    var url = mw.util.$content.children('fieldset.rcoptions').children('hr').prev('a').attr('href')
    var tab = $('#ca-special, #ca-nstab-special').eq(0).find('a')
    if( url && tab.length ) tab.attr( 'href', url ).text( tab.text() + ' Δ' )
    //calc "from"
    var from = /&from=(\d+)/.exec(document.URL)
    if( !from ) return
    rcQuery.aflend = from[1]
    break
  
  default: return //some other special page
  
 }//switch
 
 mw.util.addCSS('\
div.apiresults table td {padding: 0 1em}\
div.apiresults table th {font-weight:normal; font-size:smaller}\
div.apiresults table {border-spacing:0}\
div.afl table {background-color: #f2f2f2}\
div.afl td.title {font-size:smaller}\
div.afl tr.odd {background-color:#eaeaea}\
div.uc table {background-color: #fff5f5}\
td.parsedcomment {font-size:85%}\
td.title.redirect a {color:gray}\
tr.touched td.afl-result {font-weight:bold}\
div.watched {background:#ccf}\
span.wx {cursor:pointer; margin-right:1em}\
')

 $.extend(rcQuery, window.rcDataRequest) //possible user-defind config
 apl.queryAPI(rcQuery, receiveData)
 return

 
 
 
//--------------------------- Functions


function receiveData(resp){

 //prepare output div
 $('<div id="js-output" />').appendTo( mw.util.$content )
 //notify user if our time was wrong
 var timeErr =  ( new Date() - wgServerTime ) / 1000
 if( timeErr > 60 ) showMsg('Your time is different from server time: ' + timeErr + ' sec')

 //show data
 if( resp.error ) showMsg('API responded with ' + resp.error.code + ':' + resp.error.info + '<br />')
 if( !resp || !(resp=resp.query) ) return showMsg('<span style="color:gray">--</span>')

 $('#js-output').append(
  '<hr style="clear:both" />',
  showAbuseLog( resp.abuselog ),
  '<hr style="clear:both" />',
  showContribs( resp.usercontribs ),
  $('<span style="cursor:pointer; float:left; margin-right:1em"\
    id=users-button title="watch contribs">&gt;</span>')
   .click(editContribs)
 )

 if( window.markBlocked ) markBlocked('#js-output') // for ru:MediaWiki:Gadget-markblocked.js
 
 $('#js-output').append(
    '<hr style="clear:both" />',
    showNewPages( resp.recentchanges ) 
 )

 if( window.rcDataSortable ) setTimeout("$('#js-output table').tablesorter()", 1000)

 requestTitlesProperties()
 
}


 





function showAbuseLog(log){

 if( !log || log.length == 0 ) return ''
 var lastShown = mw.cookie.get('aflast') //avoid showing the same events over and over

 //group by user
 var kk = 0, ii, tmp
 do{ //check all events below and move events with the same user next to this one
   for( ii=kk+1; ii<log.length; ii++)
     if( log[kk].user == log[ii].user ){
       kk++; 
       if( kk != ii){  tmp = log[kk]; log[kk] = log[ii]; log[ii] = tmp } //swap
     }
 } while( ++kk < log.length )
 
 var item, result, tbody = '', isOdd = true
 for( ii=0; ii<log.length; ii++ ){
   item = log[ii]
   if( lastShown && item.id <= parseInt(lastShown[1]) ) continue
   rememberTitle(item, 'afl' + ii)
   result = {'':'✓', tag: '✓', warn: 'w', disallow: 'x'}[item.result] || '?'
   if( ii > 0 && item.user != log[ii-1].user ) isOdd = ! isOdd //change background with another user
   tbody += '<tr id=afl' + ii
    + ' class="' + result + ' ' + item.action + (isOdd?' odd':'') + '">'
    + apl.outputCell(item, 'title')
    + apl.outputCell(item, 'user')
    + '<td class=filter>' + apl.outputPage('Special:AbuseFilter/'+item.filter_id, item.filter) + '</td>'
    + '<td class="afl-result">' + apl.outputPage('Special:AbuseLog/'+item.id, result) + '</td>'
    + '<td class=ago>' + Math.round( (wgServerTime - apl.parseTimestamp(item.timestamp) )/1000)+'</td>'
    + '</tr>'
 }

 mw.cookie.set('aflast', log[log.length-1].id)
 return createTable(['title','user','filter','','ago'], tbody, 'afl')
}




function showContribs(log){

 if( !log || log.length == 0 ) return ''
 
 var item, result, tbody = '', isOdd = true
 for( ii=0; ii<log.length; ii++ ){
   item = log[ii]
//   if( ii > 0 && item.user != log[ii-1].user ) isOdd = ! isOdd //change background with another user
   tbody += '<tr id=uc' + ii + '>'
    + apl.outputCell(item, 'title')
    + apl.outputCell(item, 'user')
    + '<td>' + apl.outputLink('diff=' + item.revid, 'Δ') + '</td>'
    + '<td>' + Math.round( (wgServerTime - apl.parseTimestamp(item.timestamp) )/1000)+'</td>'
    + apl.outputCell( item, 'parsedcomment' )
    + '</tr>'
 }
 
 
 return createTable(['title','user','Δ','ago',''], tbody, 'uc')
}



function hoverContribLink(e){
 $('.watcher').remove()
 var aa = $(e.target)
 var usr = /Contributions\/([^?&]+)$/.exec( aa.attr('href'))
 if( e.target.nodeName != 'A' || !usr ) return

 usr = decodeURIComponent(usr[1]).replace(/_/g, ' ')
 //var pos = aa.offset()

 var watcher = $('<div class=watcher title="watch contribs"\
  style="position:absolute; border:1px dotted gray; cursor:pointer" />')
 .css({top: aa.offset().top, left:aa.offset().left-10, width:10, height:10 })
 .appendTo('body')
 .toggleClass('watched', wlContribs(usr) )
 .mouseleave( function(){ $(this).remove() } )
 .click(function(){
    $(this).toggleClass('watched')
    wlContribs( usr, $(this).hasClass('watched') )
  })

 
 
}

function editContribs(e){

 $('#users-edit').remove()
 var btn = $('#users-button')
 var state = parseInt( btn.attr('state') || '0' )
 if( e != false ) state++ //switch to next state
 
 var users = (wlContribs() || '').split('|')
 //if( !users ) return
 var uList = $('<div id=users-edit />').insertAfter('#users-button')

 switch( state ){ // 

 case 1: //edit mode
   var htm = ''
   for( var i=0; i<users.length; i++)
     if(users[i] )
       htm += apl.output(users[i], 'user') 
       + ' <span class=wx user="' + users[i] + '">x</span> '
   if( !htm ) htm = '--'     
   uList.html(' ' + htm)
   uList.find('.wx').click(function(){
     wlContribs( $(this).attr('user'), false )
     editContribs(false)
   })
   break
   
  case 2: //raw mode
    uList.append(
     $('<input type=button value=Save />')
      .click(function(){
       wlContribs( mw.html.escape($('#users-list').val()).replace(/\n/g,'|'), 'list' )
       $(this).closest('div').remove()
      })
     , 
     mw.html.escape(
      $('<textarea id=users-list rows=' + (users.length+1) +'/>')
       .val( users.join('\n') )
     )
    ) 
    break
 }
 
 btn.attr('state', state % 3)

}



function showNewPages(rcc){
 if( !rcc || rcc.length == 0 ) return ''
 var item, comm, ma, tbody = ''
 for( var ii=0; ii< rcc.length; ii++ ){
   item = rcc[ii]
   //simplify "new page" comment
   comm = item.parsedcomment
   if ( ma = comm.match(/^<a[^>]+>←<\/a> Новая страница: «(.*)»$/) )
     comm = '<span class="page-quote">«' + ma[1] + '»</span>'
   else if ( ma = comm.match(/^<a[^>]+>←<\/a> <a[^>]+>Перенаправление<\/a> на «(.*)»(.*)$/) )
     comm = '→ ' + ma[1] + ma[2]
   else 
     comm = '<i>' + comm + '</i>'
   //add to table   
   tbody += '<tr id=np' + ii + '>'
   + apl.outputCell( item, 'title' )
   + apl.outputCell( item, 'user' )
   + apl.outputCell( comm, 'parsedcomment' )
   + apl.outputCell( item, 'newlen' )
   + '</tr>'
 }  
 return createTable(['new page', 'user', '', 'size'], tbody, 'npg', 2)
}



// ------------------------ 2nd API request for title properties to:
//1) mark red links 2) make bold filter events when title was "touched" - this usually means the page was recently edited

function rememberTitle(it, domId){ //"save" title into our object
  if( ! pgTitle[it.title] ) pgTitle[it.title] = []
  pgTitle[it.title].push( {id: domId, ts: it.timestamp } )
}


function requestTitlesProperties(){ 
 //reques titles properties, to mark red links
 var arr = []
 for (var ttl in pgTitle) arr.push(ttl)
 while( arr.length > 0 ) //API only accepts 50 titles per request
   apl.queryAPI( { prop: 'info', titles: arr.splice(0,50).join('|') }, showTitlesProperties )
} 

function showTitlesProperties(pages){
 pages = apl.getChild(pages, 'query.pages')
 var pg, savedTitle, tr
 for (var id in pages){
   pg = pages[id]
   savedTitle = pgTitle[ pg.title ] //[ { id:domId, ts: timestamp }, ... ]
   for (var i=0; i<savedTitle.length; i++){
     tr = $('#'+savedTitle[i].id)
     if( typeof pg.missing == 'string' )
       tr.find('td.title').find('a').addClass('new')
     else if( pg.touched >= savedTitle[i].ts ) //most likely was edited after triggering
       tr.addClass('touched')
   }
 }
}



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

function showMsg(txt){
  $('#js-output').append(txt + '<br />')
}

function createTable(THs, tbody, clss, sortByColumn){
 if( !tbody ) return ''
 else return '<div class="apiresults ' + (clss||'') + '">'
  + '<table class=sortable><tr><th>' + THs.join('</th><th>') + '</th></tr>'
  + tbody + '</table></div>'
 
  
}


}



function wlContribs(user, action){
 var list = mw.cookie.get('wco') || ''
 if( !user ) return list
 switch( action ){
  case undefined: return list.indexOf(user) != -1
  case false: list = list.replace(user,''); break
  case true: list += '|' + user; break
  case 'list': list = user; break
 }
 mw.cookie.set( 
   'wco',
   list.replace(/\|\|/,'|').replace(/^\||\|$/g,''),
   {expires: 7}
 )
}