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

Материал из Википедии — свободной энциклопедии
//*** Script to archive discussion pages, beta version ***
//In edit mode click on "(a)" next to "watch", then follow directions

//Interface: en
//Localization: en & ru (see parseSigStamp())
//compat with secure server: ??



//Customization 
if (mw.config.get('wgServer') == 'http://ru.wikipedia.org'){
 archiveName = mw.config.get('wgPageName') + '/Архив'
 archiveHeader = '{\{закрыто}}'
 if (/Википедия:Форум\/./.test(mw.config.get('wgPageName'))){
   archiveByMonth = true
   archivePageDir = -1
   archiveName = mw.config.get('wgPageName').split('/')[0] + '/Архив/' + mw.config.get('wgPageName').split('/')[1]
   archiveHeader = '{\{архив форума}}'
 }else if (mw.config.get('wgPageName') == 'Википедия:Вниманию_участников'){
   archiveByMonth = true //archivePageDir = -1
 }else if (mw.config.get('wgPageName') == 'Википедия:Форум_администраторов'){
   archiveByMonth = true
 }else if (mw.config.get('wgPageName') == 'Википедия:Запросы_к_администраторам'){
   archiveByMonth = true //archivePageDir = -1
   archiveThreshold = 4
 }
}else if (mw.config.get('wgServer') == 'http://meta.wikimedia.org'){
 archiveHeader = '{\{archive-header|}}'
 switch(mw.config.get('wgPageName')){
 //case 'Requests_for_permissions' // page layout is too complicated 
 case  'Requests_for_bot_status':
   archiveByMonth = true
   archiveStartFrom = '\n==Bot status requests=='
   archiveH3Sections = true
   break
 case 'Requests_for_CheckUser_information':
   break 
   //
 }
}

//LATER: move inside object
//parameters; also: archiveStartFrom, archiverAutoDiff
var archiveName = window.archiveName || mw.config.get('wgPageName') + '/Archive'
var archiveByMonth = window.archiveByMonth || false 
var archiveHeader = window.archiveHeader || '' 
var archiveThreshold = window.archiveThreshold || 7
var archivePageDir = window.archivePageDir || 0 // 1: new sections on bottom, -1: on top, 0: determine automatically
//vars
var secStart, secEnd, hdrStart, hdrEnd
var sec, pageText, pageTextSize, archives, moveTextSize = 0, moveSecN = 0



var archiver = new function(){

 scriptName = 'Archiver'
 this.start = function(){ start() }
 this.autoMarkSections = autoMarkSections
 this.startArchiving = startArchiving
 this.doArchiving = doArchiving
 this.markSection = function(i){ toggleSection(i);  showDialog() }  //on click


function showDialog(){
 var html = '<h2>Archiving</h2>'
 + 'Archive page: ' + archiveName  
 + (archiveByMonth ? '<small>/&lt;year&gt;/&lt;month&gt;</small>' : '') + '<br />'
 + (archivePageDir == -1 ? 'New sections are on top<br />' : '') 
 + 'Auto marked sections older than ' + archiveThreshold + ' days<br />'
// + ' <b><a href="javascript:archiveThreshold--; archiver.autoMarkSections()">-</a>'
// + ' <a href="javascript:archiveThreshold++; archiver.autoMarkSections()">+</a></b><br />'
// + 'Or mark sections manually<br />'
 + '<br />'
 + 'Whole text: ' + pageTextSize + ' bytes in ' + sec.length + ' sections<br />'
 + 'To archive: ' + moveTextSize + ' bytes in ' + moveSecN +   ' sections<br />'
 + '<br />'
 + 'Mark sections, then '
 + '<input type=button id=startArchiving value=Archive onclick=archiver.startArchiving()>'
 jsMsg(html)
}


function start(){

 msg(scriptName + ' started.')
 document.getElementById('mw-js-message').style.backgroundColor = '#F0FAF0'

 if (window.archiveH3Sections){ 
  hdrStart = 'H3'
  hdrEnd = 'H2'
  secStart = /^===[^=].*=== *$/m
  secEnd = /^={1,3}[^=].*={1,3} *$/m
 }else{
  hdrStart = 'H2'
  hdrEnd = 'H1'
  secStart = /^==[^=].*== *$/m
  secEnd = /^==?[^=].*== *$/m
 }

 msg(' analyzing text ...')
 pageText = document.editform.wpTextbox1.value
 pageTextSize = countBytes(pageText)
 currentTime = parseTimestamp(document.editform.wpStarttime.value) 
 var startFrom = 0
 if (window.archiveStartFrom){
   startFrom = pageText.indexOf(archiveStartFrom)
   if (startFrom == -1) err('Unable to find this in text: "' + archiveStartFrom + '"')
 }

 msg(' getting sections ...')
 sec = getTextSections(pageText, startFrom)

 //var wikiPreview = document.getElementById('wikiPreview')
 //if (!wikiPreview) {  jsMsg('<b>Error</b>: archiver needs Page Preview');  return }

 //var 
 toc = document.getElementById('toc')
 if (!toc) err('Script needs preview with TOC')
 toc = getElementsByClassName(toc, 'span', 'toctext')

 /*was trying to get TOC items without API, impossible with complex cases such as when templates are used
   //convert section header into text
   hdr = sec[i].content.match(secStart)[0]
   hdr = hdr.match(/^=+ *([^=].*)/)[1].match(/(.*?[^=]) *=+$/)[1] //trim spaces and "=" on edges
   hdr = hdr.replace(/\[\[:?[^|]+\|([^\]]+)\]\]/g, '$1') //[[foo|bar]] -> bar
   hdr = hdr.replace(/\[\[:?([^\]]+)\]\]/g, '$1') //[[bar]] -> bar
   hdr = hdr.replace(/<.*?>/g, '').replace(/ +/g,' ') //strip tags and double spaces
  // outdated note:  finding corresponding TOC elements is easier than finding H2/H3 directly, because section header can contain e.g. <br>
 */
 
 //convert section headers into TOC text using API
 msg(' getting TOC elements using API query ...')
 var queryText = '', aj = sajax_init_object(), q, i
 tocAPI = []
 for (i=0; i<sec.length; i++){
   queryText += encodeURIComponent(sec[i].content.match(secStart)[0]) + '%0A'
   if (queryText.length < 3000 && i < sec.length-1) continue
   //do API query if URL is too long or this is the last loop
   aj.open('GET', '/w/api.php?format=json&action=parse&prop=sections&text='+queryText+'__TOC__', false)
   aj.send(null)
   if (aj.status != 200) err('query failed:\n' + aj.responseText)
   eval('q='+aj.responseText)
   if (!(q=q.parse) || !(q=q.sections)) err('unrecognized query result:\n' + aj.responseText)
   for (var j=0; j<q.length; j++) tocAPI.push(q[j].line)
   queryText = ''
 }
 if (tocAPI.length != sec.length) err('there are '+sec.length+' sections, query returned '+tocAPI.length+' headers') 



 //for each section in text  find corresponding TOC item and section header in preview
 var el, hh = new Array(sec.length), tocIdx = 0
 for (i=0; i<sec.length; i++){
   hdr = tocAPI[i].replace(/ +/g, ' ').replace(/&#123;/g,'{')
   while (tocIdx < toc.length && toc[tocIdx].firstChild.data.replace(/\xA0/g,' ') != hdr ) tocIdx++
   //about replace: french spaces (?) in http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/Parser.php?view=markup
   if (tocIdx >= toc.length) err('Could not find this section header in TOC:\n' + hdr)
  sec[i].toc = toc[tocIdx].parentNode.parentNode //assign custom attribute
  //using anchor, find corresponding H2/H3 in text
  el = document.getElementsByName(toc[tocIdx].parentNode.getAttribute('href', 2).substring(1))[0]
  if (!el) err('Could not find anchor for this section header:\n' + hdr)
  el = el.parentNode //enclosing <p>
  while ((el=el.nextSibling) && el.nodeName != hdrStart);
  if (!el) err('Could not find '+hdrStart+' for this section header:\n' + hdr)
  hh[i] = el
 }

 //enclose each preview section in  div
 var div, daysAgo, toggleLink, toggleDiv
 for (i=0; i<sec.length; i++){
  div = document.createElement('div')
  div.style.cssText = 'margin-top:20px; border:1px dotted gray; padding:5px'
  hh[i].parentNode.insertBefore(div, hh[i])
  sec[i].div = div
  el = div.nextSibling
  do {
    //obj = el
    el=el.nextSibling
    div.appendChild(el.previousSibling)
  } while (el && el.nodeName != hdrStart && el.nodeName != hdrEnd && el.nodeName != '#comment')
  //add toggle link showing section age
  daysAgo = Math.floor(sec[i].age)
  if (daysAgo == -1) daysAgo = '<small>??</small>'
  else if (daysAgo > 10) daysAgo += '<small>d</small> '
  else daysAgo += '<small>d</small> ' + Math.floor((sec[i].age-daysAgo)*24) + '<small>h</small>'
  var toggleLink = document.createElement('a')
  toggleLink.style.cssFloat = toggleLink.style.styleFloat = 'right'
  toggleLink.style.fontSize = '70%'
  toggleLink.href = 'javascript:archiver.markSection('+i+')'
  toggleLink.innerHTML = daysAgo
  hh[i].insertBefore(toggleLink, hh[i].firstChild)
  toggleDiv = document.createElement('div')
  toggleDiv.appendChild(toggleLink.cloneNode(true))
  toggleDiv.appendChild(document.createTextNode('\u00A0'))
  hh[i].parentNode.appendChild(toggleDiv)
 }

 autoMarkSections()

}




function getTextSections(text, startFrom){ //returns array of sections in text; needs currentTime as Date()
 //get all sections
 var sect = new Array (), processedIdx = startFrom || 0, begin, end
 while ((begin=text.substring(processedIdx).search(secStart)) != -1){
   begin += processedIdx //correct relative to whole text
   //find end of section
   processedIdx = text.indexOf('\n', begin)
   end = text.substring(processedIdx).search(secEnd) //search from next line
   if (end == -1) end = text.length
   else end += processedIdx
   //remember section in array
   sect.push({begin:begin, end:end, content:text.substring(begin, end), marked:false})
   //get ready for the next loop
   processedIdx = end
 }
 //look for timestamps and find each section firstDate and age
 var dates, d, firstStamp, lastStamp, age
 for (i=0; i<sect.length; i++){
   dates = sect[i].content.replace(/<blockquote>.*?<\/blockquote>/gi, '') //do not search in blockquote
   dates = dates.match(/\d\d:\d\d, \d\d? \S{3,8} 20\d\d \(UTC\)/g)
   firstStamp = currentTime; lastStamp = 0
   if (dates){
     for (var j=0; j<dates.length; j++){
       d = parseSigStamp(dates[j])
       if (d < firstStamp) firstStamp = d
       if (d > lastStamp) lastStamp = d
     }
     age = (currentTime-lastStamp)/(1000*3600*24) //in days
   }else{
     firstStamp = 0
     age = -1
   }
   sect[i].firstDate = firstStamp
   sect[i].age = age
 }
  //determine if newer sections are on top or bottom
 if (archivePageDir == 0){ 
   for (i=1; i<sect.length; i++)
     if (sect[i-1].firstDate < sect[i].firstDate) archivePageDir++
     else if (sect[i-1].firstDate > sect[i].firstDate) archivePageDir--
   archivePageDir = (archivePageDir >=0) ? 1 : -1
 }
 //fix firstDate for no-timestamps sections  by adding/removing one minute
 for (i=1; i<sect.length; i++)
   if (sect[i].firstDate == 0 && sect[i-1].firstDate != 0){
     sect[i].firstDate = sect[i-1].firstDate
     sect[i].firstDate.setMinutes(sect[i].firstDate.getMinutes() + archivePageDir)
   }
 for (i=sect.length-2; i>=0; i--)
   if (sect[i].firstDate == 0 && sect[i+1].firstDate != 0){
     sect[i].firstDate = sect[i+1].firstDate
     sect[i].firstDate.setMinutes(sect[i].firstDate.getMinutes() - archivePageDir)
   }
 return sect
}



function autoMarkSections(){
 for (var i=0; i<sec.length; i++){
   if ((sec[i].age >= archiveThreshold) != sec[i].marked)
     toggleSection(i)
 }
 showDialog()
} 




function toggleSection(i){
 var marked = ! sec[i].marked
 sec[i].marked = marked
 moveSecN += marked ? 1 : -1
 moveTextSize += (marked ? 1 : -1) * countBytes(sec[i].content)
 sec[i].div.style.background = marked ? '#E5E5F0' : ''
 sec[i].toc.style.background = marked ? '#E5E5F0' : ''
 //document.getElementById('section'+i).style.background = marked ? '#E5E5F0' : ''
 //document.getElementById('tocsec'+i).style.background = marked ? '#E5E5F0' : ''
}


function startArchiving(){
 var pg
 archives = {}
 for (var i=sec.length-1; i>=0; i--){
   if (!sec[i].marked) continue
   pg = archiveName
   if (archiveByMonth) pg += '/' + sec[i].firstDate.getFullYear() 
                           + '/' + zz(sec[i].firstDate.getMonth()+1)
   if (!archives[pg]) archives[pg] = {header:archiveHeader, text:''}
   archives[pg].text = sec[i].content + archives[pg].text
   pageText = pageText.substring(0, sec[i].begin) + pageText.substring(sec[i].end)
 }
 doArchiving()
}


function doArchiving(){
 var pg, win
 for (pg in archives){
   win = window.open(mw.config.get('wgServer') + mw.config.get('wgScript') + '?action=edit&preview=no&title=' + pg)
   win.archiveText = archives[pg].text
   win.archiveHeader = archives[pg].header
   win.onload = archivePageOnLoad
 }
 document.editform.wpTextbox1.value = pageText
 document.editform.wpSummary.value = '::архивировано ' + moveSecN + ' '
  +plural(moveSecN,'секция','секции','секций') + '::' // + aChars + ' символов'
 jsMsg('Archiving is ready. Check that everything is ok and press Save buttons in all open windows.')
 //setTimeout('window.focus()', 2000)
 self.focus()
}


function archivePageOnLoad(){
 var win = this, i
 var box = win.document.editform.wpTextbox1
 var txt = box.value || win.archiveHeader + '\n' || ''
 //append archived text
 if (archivePageDir == -1 && (i=txt.indexOf('\n=='))!=-1) //just after intro
   txt = txt.substring(0,i) + '\n' + win.archiveText + txt.substring(i)
 else //at bottom
   txt += win.archiveText
 //get sections
 //var 
 secs = getTextSections(txt)
 //get intro section, then sort and append all other sections
 txt = txt.substring(0, secs[0].begin)
 secs.sort(function(a,b){return (a.firstDate-b.firstDate) * archivePageDir})
 for (i=0; i<secs.length; i++) txt += secs[i].content
 txt = txt.replace(/\n+==/g, '\n\n==') //make sure ther is exactly one line before each ==header==
 box.value = txt
 win.document.editform.wpSummary.value = '+'
 if (window.archiverAutoDiff) win.document.getElementById('wpDiff').click()

}


function zz(a){ a=a.toString(); return (a.length==1) ? '0'+a : a } // 6 -> '06'

var jsMsgHTML = ''
function msg(t){
 jsMsgHTML += '<br />' + t.replace(/\n/g,'<br />') 
 jsMsg(jsMsgHTML)
}
function err(t){
 msg('<br /><font color=red><b>Error:</b></font><br />' + t)
 throw 'Error in' + scriptName + ' script: ' + t
}


function countBytes(text){
 var bytes = 0, bb
 for (var i=0; i<text.length; i++){
   bb = text.charCodeAt(i)
   if (bb < 128) bytes++
   else if (bb < 2048) bytes += 2
   else if (bb < 65535) bytes += 3
   else bytes += 4 
 }
 return bytes
}


function plural(num, v1, v2, v3){
 num = num % 100
 if (5<=num && num<=20) return v3
 num = num % 10
 if (num == 1) return v1
 else if (2<=num && num<=4) return v2
 else return v3
}


function parseSigStamp(sig){ // '05:53, 7 марта 2007' -> date
 var s = sig.split(' ')
 var sigTime = new Date()
 sigTime.setYear(s[3])
 var month_name = s[2].substring(0,3).toLowerCase()
 var month = 'янвфевмарапрмаяиюниюлавгсеноктноядек'.indexOf(month_name)
 if (month == -1) month = 'janfebmaraprmayjunjulaugsepoctnovdec'.indexOf(month_name)
 if (month == -1) return null
 sigTime.setMonth(month/3)
 sigTime.setDate(s[1])
 sigTime.setHours(s[0].substring(0,2))
 sigTime.setMinutes(s[0].substring(3,5))
 sigTime.setSeconds(0)
 return sigTime
}

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
}

}//archiver




//if the script is imported directly
if (!doneOnloadHook && (mw.config.get('wgAction') == 'edit' || mw.config.get('wgAction') == 'sumbit'))
$(function(){
  if (document.URL.indexOf('&section=') == -1 && document.editform && document.editform.wpSection.value == '')
    mw.util.addPortletLink('p-cactions', 'javascript:archiver.start();', '(a)', 't-archive', 'Archive this page with Archiver')
})