MediaWiki:Script/LastContribs.js

Версия от 11:23, 18 июня 2020; Mansur700 (обсуждение | вклад)
(разн.) ← Предыдущая версия | Текущая версия (разн.) | Следующая версия → (разн.)

Возможно, этот код документирован.

var script = {
 name: 'LastContribs',
 page: 'ТП:Персональные скрипты/Редактированные страницы'
} 
  
var lastContribs = new function(){


// Localization
var msg={
legend:'Pages edited by user',
info:'Edits checked: $1, pages found: $2.',
next:'Request 50 next edits (200 | 500)'
}
if (wgUserLanguage=='ru') msg={
legend:'Страницы, которые правил участник',
only:'только',
articles:'статьи',
Find:'Найти',
Show:'Показывать',
unchanged:'неизменённые',
patrolled:'патрулированные',
Page:'Страница ',
'Number of edits':'Количество правок участника',
'Following changes': 'Последующие изменения ',
'No edits found': 'Таких правок не найдено',
info:'Просмотрено правок: $1, найдено страниц: $2.',
next:'Запросить 50 следующих правок (200 | 500)'
}
function mm(txt){ return msg[txt] || txt }


var pages //obj: current set of pages with their info
var known //obj: known[pageid]==5 means page was edited 5 times by this user
var ucLimit, ucContinue //parameters for api request
var editsN //number of requested edits


this.start = function (){ //createForm

appendCSS('\
#output {position: absolute; left:0; top:0; width:98%; min-height:95%;\
 padding:0 1em; background:white; font-size:small}\
td.time {text-align:right}\
td.edits {text-align:right; font-size:90%}\
span.one {opacity:0.4}\
tr.patrolled td.page {background:#f0f5f0}\
span.sort {display:none}\
span.more {color:#0645AD; cursor:pointer}\
select {vertical-align:middle} th {font-weight:normal}\
#dialog {margin-top:0}')

$(document.body).children().hide()

var ns = parseInt(getURLParamValue('ucnamespace') || getURLParamValue('ns') || 0)

$('<div id=output>\
<fieldset id=dialog><legend>'+mm('legend')+'</legend>\
 <form style="display:inline">'+ wgFormattedNamespaces[2]+
  ':<input name=ucuser size=15 value="'+(wgUserName||'')+'">&nbsp; '+
  checkbox('ucnamespace', mm('only') + ' ' + 
    (ns ? '«'+wgFormattedNamespaces[ns]+':»' : mm('articles')), 
    'value='+ns+' checked') + '&nbsp; '+
  '<input type=submit value="'+mm('Find')+'">'+
 '</form><span style="border-left:1px solid gray; margin:0 2em" />'+
 mm('Show') + ':&nbsp;' +
 checkbox('same', mm('unchanged'), 'checked') + '&nbsp;' +
 checkbox('patrolled', mm('patrolled'), 'checked') +
 '<input type=hidden name=uclimit value=50>' +
'</fieldset>\
<div id=messages style="float:right; margin:1em 0 0 1em; background:#f5f5f5; padding:5px" />\
<div id=results />\
</div>').click(onClick).appendTo(document.body)

$('#same, #patrolled').click( function (){ 
  toggleCSS('tr.'+this.id+'{display:none}', this.checked)
})
UrlToForm('#dialog')

displayHelp() //temporary disabled: description page not ready
$('#dialog form').submit(formSubmit)

}//start



function formSubmit(e){
 if (! $(this).find('input[name=ucuser]').val()) return false
 $('#messages').empty()
 $('#results').empty()
 known = {};  ucContinue = '';  editsN = 0
 ucLimit = $('#dialog input[name=uclimit]').val()
 //simulate(); displayResult(); return false
 requestContribs()
 return false
}



function onClick(e){
 var el = e.target
 switch (el.className){
   case 'page': //add history link on click
     $('table#contribs span.hist').remove()
     $('<span class=hist>(<a href="' + 
       $(el).find('a').attr('href')+'?action=history">h</a>)</span>')
       .css('float','right').appendTo(el)
     break
  case 'thtime':
   toggleCSS('td.time span.sort {display:inline}\
    td.time span.ago {display:none}') 
   break
 }
} 



function requestContribs(){
 editsN += parseInt(ucLimit)
 spinner2('#dialog form')
 api('&list=usercontribs&ucprop=ids|timestamp|size&ucstart='+ucContinue
  + '&uclimit='+ucLimit + '&'+$('#dialog form').serialize(), 
   recvContribs
 )
}


function recvContribs(data){ //received user contribs
 ucContinue = data['query-continue'] ? data['query-continue'].usercontribs.ucstart : ''
 //check for empty results
 if (!(data=data.query) || !(data=data.usercontribs)) 
   return displayResult('Server returned empty data')
 if (data.length == 0) 
  return displayResult(mm('No eidts found'))
 //find pages we haven't seen before
 pages = {}
 var list = []
 $.each(data, function(i,pg){
   if (known[pg.pageid]) return known[pg.pageid]++
   known[pg.pageid] = 1;  list.push(pg.pageid)
   pages[pg.pageid] = {revid:pg.revid, timestamp:pg.timestamp, size:pg.size}
 })
 //prepare to display results
 $('#output').ajaxStop(function(){
   $('#output').off('ajaxStop')
   displayResult()
 })  
 //request the last revision of each page, 50 pageids at a time
 while (list.length > 0)
   api({prop:'revisions', pageids: list.splice(0,50).join('|'), 
   rvprop:'ids|user|size|timestamp|flagged'}, recvLastRevisions)
}



function recvLastRevisions(data, status){ //add title and last revision to each page in pages{}
 $.each(data.query.pages, function(i,v){
   if (!pages[v.pageid]) 
     dispMsg('Could not find '+v.pageid+' '+v.title+' in pages{}')
   pages[v.pageid].title = v.title
   pages[v.pageid].ns = v.ns
   pages[v.pageid].last = v.revisions[0]
 })
}




function displayResult(txt){

 spinner2()
 if (txt) return $('#results').text(txt)
 
 if ($('table#contribs').length == 0){ //createTable
  $('#results').html('\
   <div class=info />\
   <table class=wikitable id=contribs><tr>\
   <th class=thtime title="hh:mm or dd,hh">'+
    outputIcon('2/26/Clock_simple.svg',15,'class=thtime')+'</th>\
   <th>' + mm('Page')+' </th>\
   <th title="'+mm('Number of edits')+'"></th>\
   <th>' + mm('Following changes')+' </th></tr></table>\
   <div class=info />')
  ts_makeSortable($('table#contribs')[0])
 }
//outputIcon('e/ec/Btn_edit.gif','','title="'+mm('Number of edits')+'"')

 var clss, arr=[], htm='', pgid, pg

 //update number of edits on old rows
 $('table#contribs tr').each(function(i){
   if (i==0) return
   $(this).find('td.edits').html( showEdits($(this).attr('pg')) )
 })
 
 //sort by timestamp using temp array
 $.each(pages, function(pgid,v){ arr.push([pgid,v.timestamp]) })
 arr.sort(function(a,b){return a[1]>b[1]?-1:1})

 //add rows to the table
 $.each(arr, function(i,v){
   pgid = v[0]
   pg = pages[pgid]
   clss = pg.revid==pg.last.revid ? 'same' : 'changed'
   if (pg.last.flagged) clss += ' patrolled'
   htm += '<tr class="'+clss+ '" pg='+pgid+'>'+
    '<td class=time><span class=sort>'+pg.timestamp+'</span><span class=ago>'+
    outputPeriodInHours(serverTime-parseTimestamp(pg.timestamp))+'</span></td>'+
    '<td class=page><span class=sort>'+pad0(pg.ns,3)+'</span>'
     + outputPageLink(pg.title)+'</td>'+
    '<td class=edits>'+showEdits(pgid)+'</td>'+
    '<td class=lastedit><span class=sort>'+pg.last.timestamp+'</span>'
   if (/changed/.test(clss)) htm += 
     '<span'+(pg.revid==pg.last.parentid ? ' style=visibility:hidden':'')
      +'>&nbsp;<b>…</b> </span>'+  //…
     outputUserLink(pg.last.user)+
     '<span style="float:right; margin-left:1em">' + outputIntLink(
      'oldid='+pg.revid+'&diff='+pg.last.revid, 
      pg.last.size - pg.size, 
      'class='+outputDiffClass(pg.last.size - pg.size))+
     '</span>'
   htm += '</td></tr>'
 })
 
 $('table#contribs').append('<tbody>'+htm+'</tbody>')
 
 $('div.info').html(
  mm('info').replace('$1', editsN).replace('$2', arr.length) + 
  (ucContinue ? ' '+mm('next').replace(/\d+/g, '<span class=more>$&</span>') :'')
 )
 $('span.more').click(function(){
   ucLimit = $(this).text()
   requestContribs()
 })  

}

function showEdits(pgid){
 var nn = known[pgid]
 if (nn == 1) nn = '<span class=one>'+nn+'</span>'
 return nn
}

} //lastContribs


/*start*/
var maxTitleLength = 80

if( /^Тептар:Персональные_скрипты/.test(wgPageName) ) $(lastContribs.start)






/* small API library */

//initialization
var serverTime = ''
var $debug = getURLParamValue('debug')

$(function(){
 $().ajaxSuccess( function(e, aj){ //get server time from other requests
   serverTime = parseServerTime(aj.getResponseHeader('Date')) 
 })
 $.ajaxSetup({ cache:false })
})


 
function api(data, callback){
 if (typeof data=='object') data = $.param(data)
 var url = wgScriptPath+'/api.php?action=query&'+data
 //display request URL
 if (window.$debug) dispMsg($('<a>').text(/&(list|prop)=(\w+)/.exec(url)[2])
 .css('margin-right','1em').attr('href',url))
 //request
 $.getJSON(url+'&format=json', function(d,s){
   if (d.error) dispMsg('API error: '+d.error.code+': '+d.error.info, true)
   if (d.warnings) dispMsg('API warning: '+dumpJSON(d.warnings).replace(/\n/g,'<br>'), true)
   callback(d,s)
 })
}

function dispMsg(htm, isErr){ //div#messages should exist
 if (isErr) htm = '<p class=error>'+htm+'</p>'
 $('#messages').append(htm,'<br />').show()
} 
 
function dumpJSON(o){
 var s = ''
 for (var k in o)
   if (typeof o[k] == 'object') return k+':'+dumpJSON(o[k])
   else s+=o[k]+'\n'
 return s  
}

function checkbox(name, text, attr){
 return '<input type=checkbox name='+name+' id='+name +
   (attr?' '+attr:'')+'><label for='+name+'>'+text+'</label>'
}

function getURLParamValue(name){
 var m = RegExp('[&?]'+name+'=([^&#]*)').exec(document.URL)
 if (m && m.length > 1) return decodeURIComponent(m[1])
 return null
}






function UrlToForm(obj){ //autofill form from URL
 obj = $(obj)
 var val
 obj.find('input').add(obj.find('select')).each(function(t,el){
   val = getURLParamValue(el.name)
   if (val==null) return
   switch (el.type){
    case 'radio': 
      obj.find('input [type=radio][value="'+val+'"]').attr('selected',true)
      break
    case 'checkbox': 
      if (el.value==val || val=='on') if (!el.checked) el.click()
      else if (val==''  || val=='off') if (el.checked) el.click()
      else el.value = val
      break
    default: //text, select
     el.value = val.replace(/_/g,' ')
   }
 })
}


function formToUrl(obj){
 obj = $(obj)
 var url = '&'+obj.serialize().replace(/\+/g,'_')
 obj.find('input:checkbox:not(:checked)').each( function(t,el){ 
     url += '&' + el.name + '=' //also "remember" unchecked checkboxes
 })
 return url
}


function displayHelp(e){
 if (!e) //called from Script just to add help link to #dialog
   $(outputIcon('/4/44/Help-browser.svg', 25,
    'alt=help style="cursor:pointer;float:right"'))
   .click(displayHelp).appendTo('#dialog')
 else if ($('#help').length !=0) 
   $('#help').remove()
 else {
  var url = wgArticlePath.replace(/\$1/,'') + wgPageName +
   '?run='+script.name + formToUrl('#dialog')
  var hlp = $('<div id=help style="position:absolute;top:30px; right:70px;\
   border:2px outset gray; background:white; padding:1em"><div id=page /><br><hr>\
   Подробнее см. ' + outputPageLink(script.page) + '<br><hr>\
   Ссылка на скрипт с текущими параметрами: <a href="'+url+'">'+script.name+'</a>')
   .appendTo(document.body).click(function(){$(this).remove()})
   loadTemplate('#page', script.page)
 }
} 

function loadTemplate(sel, page){ 
 $.get(wgScriptPath+'/api.php?action=parse&format=xml&prop=text&text={\{'
  +encodeURIComponent(page)+'}}', function(data){
    $(sel).html($(data).find('text').text())
 })
 // https://massarn.com/w/api.php?action=parse&text={{user%20talk:Alex%20Smotrov}}&prop=text
}


function parseServerTime(ts){ //"Tue, 29 Jan 2008 22:32:21 GMT" -> date
 if (!ts) return null
 var m = ts.match(/(\d\d?) ([A-Z][a-z][a-z]) (\d\d\d\d) (\d\d):(\d\d):(\d\d)/)
 if (m){
   var d = new Date(); d.setYear(m[3]); d.setDate(m[1]); 
   d.setMonth('janfebmaraprmayjunjulaugsepoctnovdec'.indexOf(m[2].toLowerCase()) / 3)
   d.setHours(m[4]); d.setMinutes(m[5]); d.setSeconds(m[6])
 }else
   var d = new Date(ts.substring(0, ts.length-4)) //cut "GMT" part
 return d
}

function parseTimestamp(ts){ //20071226220605  or  2008-01-26T06:34:19Z   -> date
 if (!ts) return null
 ts = ts.replace(/\D/g,'')
 var d = new Date()
 d.setYear(ts.substring(0,4))
 d.setMonth(ts.substring(4,6)-1)
 d.setDate(ts.substring(6,8))
 d.setHours(ts.substring(8,10))
 d.setMinutes(ts.substring(10,12))
 d.setSeconds(ts.substring(12,14))
 return d
}

function outputPeriodInHours(mlsec){ //milliseconds -> "2:30" or 5,06 or 21
 var mm = Math.floor(mlsec/60000)
 var hh = Math.floor(mm/60); mm = mm % 60
 var dd = Math.floor(hh/24); hh = hh % 24
 if (dd) return dd + (dd<10?'<small>,'+pad0(hh,2)+'</small>':'')
 else return '<small>'+hh + ':' + pad0(mm,2)+'</small>'
}


function outputIntLink(link, name, attr){
 return '<a href="' + wgScript + '?' + link + '" '+ (attr||'') + '>'+name+'</a>'
}

function outputPageLink(page, name){
 name = name || page
 if (window.maxTitleLength && name.length > maxTitleLength) 
   name = name.substring(0,maxTitleLength)+'…'
 return '<a href="' + wgArticlePath.replace(/\$1/,'') 
 + encodeURI(page).replace(/\?/g,'%3F').replace(/%20/g,'_') 
 + '" title="'+page.replace(/"/g,'&quot;')+'">' + name + '</a>'
 //encodeURIComponent(page).replace(/%2F/g,'/')
}

function outputUserLink(user){
 return outputPageLink('Special:Contributions/'+user, user)
}

function outputDiffClass(n){
 return 'mw-plusminus-'+ (n>0 ? 'pos' : (n<0?'neg':'null'))
}

function outputIcon(src, size, attr){ //returns "<img ...>"
 if (size) src = 'thumb/'+src+'/'+size+'px-'+src.split('/')[2]+'.png' //for svg
 return '<img src="//upload.wikimedia.org/wikipedia/commons/'
 + src + '" ' + (attr||'')+'>'
}

function pad0(v, len){ v = v.toString();  while (v.length < len) v = '0'+v; return v } // 6 -> '06'


var toggleCSSObj = {}
function toggleCSS(css, isDisabled){
 if (!toggleCSSObj[css]) toggleCSSObj[css] = addCSS(css)
 toggleCSSObj[css].disabled = isDisabled
}


function addCSS(c){ var s = appendCSS(c); return s.sheet || s } // Safari compat


function spinner2(sel){
 if (!sel) $('img.spinner').remove()
 else $(sel).append('<img class=spinner style="margin-left:1em" src="'+stylepath
  +'/common/images/spinner.gif" alt="..." title="..." />')
}