Utilisateur:Darkoneko/nekotb fc 1.1.js
Apparence
Note : après avoir enregistré la page, vous devrez forcer le rechargement complet du cache de votre navigateur pour voir les changements.
Mozilla / Firefox / Konqueror / Safari : maintenez la touche Majuscule (Shift) en cliquant sur le bouton Actualiser (Reload) ou pressez Maj-Ctrl-R (Cmd-R sur Apple Mac) ;
Firefox (sur GNU/Linux) / Chrome / Internet Explorer / Opera : maintenez la touche Ctrl en cliquant sur le bouton Actualiser ou pressez Ctrl-F5.//une extension de Date pour qu'il comprenne le format ISO8601 utilisé par l'API (ya une fonction native mais seulement sur firefox 3.5+)
// http://dansnetwork.com/2008/11/01/javascript-iso8601rfc3339-date-parser/
Date.prototype.setISO8601 = function(dString){
var regexp = /(\d\d\d\d)(-)?(\d\d)(-)?(\d\d)(T)?(\d\d)(:)?(\d\d)(:)?(\d\d)?(Z)/;
if (dString.toString().match(new RegExp(regexp))) {
var d = dString.match(new RegExp(regexp));
var offset = 0;
this.setUTCDate(1);
this.setUTCFullYear(parseInt(d[1],10));
this.setUTCMonth(parseInt(d[3],10) - 1);
this.setUTCDate(parseInt(d[5],10));
this.setUTCHours(parseInt(d[7],10));
this.setUTCMinutes(parseInt(d[9],10));
this.setUTCSeconds(parseInt(d[11],10));
}
else {
this.setTime(Date.parse(dString));
}
return this;
};
Date.prototype.getYYYYMMDD = function(){
var month = this.getMonth()+1
if(month < 10) month = "0"+month
var day = this.getDate()
if(day < 10) day = "0"+day
return ( this.getFullYear() +"-"+ month +"-"+ day );
};
//essai d'extension du DOM Element. ça rend de facto le script incompatible avec IE7- et Safari 2
Element.prototype.createAndAppendTextNode = function(p_txt) {
return this.appendChild( document.createTextNode( p_txt ) )
}
//creation d'un element + attributs + éventuels node texte dedans ; et ajout au node parent
Element.prototype.createAndAppendElement = function(p_el, p_attributs, p_textnode) {
var xx = this.appendChild( document.createElement( p_el ) )
for(key in p_attributs) {
xx.setAttribute( key , p_attributs[key] )
}
if(p_textnode) {
xx.createAndAppendTextNode( p_textnode )
}
return xx
}
//supprime tous les sous nodes d'un node (pas besoin d'être récursif).
Element.prototype.removeChildNodes = function() {
while (this.hasChildNodes() ) {
this.removeChild(this.firstChild);
}
}
/*
Auteur : user:Darkoneko
tous les id css sont préfixées de nekotb_fc (pour "NEKO ToolBox - Fusionneur de Contribs") pour eviter les conflits avec d'autres scripts
*/
var nekotb_fc = new Object()
nekotb_fc.bgcolor_list = new Array( //utilisés pour la coloration des lignes de chaque user
"lightblue",
"yellow",
"lightgreen",
"white",
"#FCC",
"#CCC",
"lightyellow",
"green",
"orange",
"#88F"
) //note : pas de virgule après le dernier terme ou ça plante !
nekotb_fc.user_list = new Array() //stocke la liste des utilisateurs fusionnés
nekotb_fc.contrib_limit_per_user = 7500; //nb max de contribs récupérées par compte
nekotb_fc.edit_clash_limit = 5; //(en minutes) espacement sous lequel des edits de 2 comptes différents sont considérés comme en collision
nekotb_fc.timeline_nb_day_per_line = 5 //nombre de jours affichés par ligne (en + du nom des comptes) en mode timeline
nekotb_fc.oldest_timestamp = false;
nekotb_fc.newest_timestamp = false;
nekotb_fc.user_contribs = new Array()
nekotb_fc.every_contrib_ordered = new Array()
nekotb_fc.addLinkToLeftBar = function() {
var ul = document.getElementById("p-navigation").getElementsByTagName("ul")[0]
var li = ul.createAndAppendElement("li", {'class':"mw-list-item"})
li.createAndAppendElement( "a", {href:'#', 'onclick':"return nekotb_fc.init()"}, '@Fusion contribs 1.1' )
}
$(nekotb_fc.addLinkToLeftBar)
nekotb_fc.init = function() {
document.getElementById("firstHeading").firstChild.nodeValue = "Neko toolbox : fusionneur de contribs, v1.1.2 du 06/09/2015"
var content = document.getElementById("bodyContent")
content.removeChildNodes() //supprimer le contenu initial de la zone 'article".
var root = content.createAndAppendElement("div", {'id':"nekotb_fc_formulaire"} )
root.createAndAppendElement("h2", {}, "Zone temporelle à inspecter")
//on laisse à l'user le choix dans la date
curYear = new Date().getFullYear();
root.createAndAppendTextNode("Fusionner entre le ")
root.createAndAppendElement("input", {'id':"nekotb_fc_date_debut", 'value':curYear+"-01-01"} )
root.createAndAppendTextNode(" et le ")
root.createAndAppendElement("input", {'id':"nekotb_fc_date_fin", 'value':curYear+"-12-31"})
root.createAndAppendElement("h2", {}, "Comptes à fusionner")
//ajout des users
var form = root.createAndAppendElement("form", {"onsubmit":"return nekotb_fc.add_user();"} )
form.createAndAppendElement("input", {'id':"nekotb_fc_user_input"} )
form.createAndAppendTextNode(" (appuyez sur entrée pour ajouter)")
root.createAndAppendElement("ul", {'id':"nekotbfc_liste_users"} ) //la liste des users ajoutés sera affichée dans cet element
root.createAndAppendElement("hr")
root.createAndAppendElement( "a", { href:"#", "onclick":"return nekotb_fc.show_fusion()" }, "=> lancer la fusion <=" )
//réglages divers
root.createAndAppendElement("h2", {}, "Réglages optionnels divers")
root.createAndAppendElement("h3", {}, "Nombre max de contributions récupérées par utilisateur")
root.createAndAppendElement("input", { 'id' : "nekotb_fc_contrib_limit_per_user", "value": nekotb_fc.contrib_limit_per_user } )
root.createAndAppendTextNode(" (sera arrondi au multiple de 500 supérieur)")
root.createAndAppendElement("h3", {}, "indicateur de collisions")
root.createAndAppendTextNode("Ecart maximal de temps pour que des éditions de 2 users différents soient considérées comme entrant en 'collision' : ")
root.createAndAppendElement("br")
root.createAndAppendElement("input", { 'id' : "nekotb_fc_edit_clash_limit", "value":nekotb_fc.edit_clash_limit })
root.createAndAppendTextNode(" minutes")
root.createAndAppendElement("h3", {}, "timeline")
root.createAndAppendTextNode("Nombre de jours par ligne")
root.createAndAppendElement("br")
root.createAndAppendElement("input", { 'id' : "nekotb_fc_timeline_nb_day_per_line", "value":nekotb_fc.timeline_nb_day_per_line })
return false
}
nekotb_fc.add_user = function() {
var input = document.getElementById("nekotb_fc_user_input")
var name = input.value
input.value = '' //on vide le champ
if(name.length < 1 ) return false //si champ vide, on ignore silencieusement
var ul = document.getElementById("nekotbfc_liste_users")
var randomNumber = Math.floor(Math.random()*100001) //generation d'un id [a priori] unique, comme le nom peut contenir des chars non valides
var li = ul.createAndAppendElement("li", {'id':"nekotb_fc_"+randomNumber, "onclick":"nekotb_fc.remove_user('nekotb_fc_"+randomNumber +"')"} )
li.createAndAppendTextNode(name)
var img = li.createAndAppendElement("img", {'src':"/media/wikipedia/commons/thumb/4/46/Pictogram_voting_delete.svg/15px-Pictogram_voting_delete.svg.png"} )
nekotb_fc.user_list.push( name.replace(/ /g, "_") )
return false
}
nekotb_fc.remove_user = function(idnode) {
var node = document.getElementById(idnode)
var name = node.firstChild.nodeValue.replace(/ /g, "_")
node.parentNode.removeChild(node)
for(var a=0 ; a < nekotb_fc.user_list.length ; a++) {
if(nekotb_fc.user_list[a] == name) {
nekotb_fc.user_list.splice(a, 1);
break;
}
}
return false
}
nekotb_fc.back_to_form = function() {
var div2 = document.getElementById("nekotb_fc_fusion")
div2.parentNode.removeChild(div2) //supprimer le div de resultat complement
document.getElementById("nekotb_fc_formulaire").style.display = "block"
return false
}
nekotb_fc.fetch_and_verify_parameters = function() {
if( nekotb_fc.user_list.length < 1 ) {
alert("aucun utilisateur selectionné")
return false
}
var reg_int = new RegExp(/^[0-9]+$/)
//recuperer les params
nekotb_fc.contrib_limit_per_user = document.getElementById("nekotb_fc_contrib_limit_per_user").value
if( ! reg_int.test(nekotb_fc.contrib_limit_per_user) ) {
alert("la limite de contributions récupérées par compte doit être un nombre entier")
return false
}
nekotb_fc.edit_clash_limit = document.getElementById("nekotb_fc_edit_clash_limit").value
if( ! reg_int.test(nekotb_fc.edit_clash_limit ) ) {
alert("l'écart pour l'indicateur de clash doit etre un nombre entier")
return false
}
nekotb_fc.timeline_nb_day_per_line = document.getElementById("nekotb_fc_timeline_nb_day_per_line").value
if( ! reg_int.test(nekotb_fc.timeline_nb_day_per_line ) ) {
alert("le nombre de jour par ligne de la timeline doit etre un nombre entier")
return false
}
var reg_date = new RegExp(/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/)
var debut = document.getElementById("nekotb_fc_date_debut").value
if( ! reg_date.test(debut) ) {
alert("date de début est incorrect")
return false
}
nekotb_fc.oldest_timestamp = debut + "T00:00:00Z"
var fin = document.getElementById("nekotb_fc_date_fin").value
if( ! reg_date.test(fin) ) {
alert("date de fin est incorrect")
return false
}
nekotb_fc.newest_timestamp = fin + "T23:59:59Z"
return true
}
nekotb_fc.show_fusion = function() {
if( ! nekotb_fc.fetch_and_verify_parameters() ) return false;
nekotb_fc.user_count = 0 //mise ou remise à 0 du compteur utilisé pour la colorisation
document.getElementById("nekotb_fc_formulaire").style.display = "none" //masquer le div du formulaire (sans le supprimer)
//creer le div qui contiendra les resultats
var div2 = document.getElementById("bodyContent").createAndAppendElement( "div", {'id':"nekotb_fc_fusion"} )
div2.createAndAppendElement( "a", {'href':"#", 'onclick':"return nekotb_fc.back_to_form()"}, "revenir au formulaire" )
div2.createAndAppendElement( "h2", {}, "Récupération des contributions" )
div2.createAndAppendElement("ul", { 'id' : "nekotb_fc_fusion_user_list" }) //utilisé pour jouer avec l'affichage en temps réel de la recup de contribs
div2.createAndAppendElement("h2", {}, "Liste des contributions fusionnées")
div2.createAndAppendTextNode("Note : les heures indiquées sont en UTC")
//recuperation des contributions par user
for(var a=0, len = nekotb_fc.user_list.length ; a < len ; a++) {
nekotb_fc.user_contribs[ nekotb_fc.user_list[a] ] = nekotb_fc.get_contribution_list( nekotb_fc.user_list[a])
nekotb_fc.every_contrib_ordered = nekotb_fc.every_contrib_ordered.concat( nekotb_fc.user_contribs[ nekotb_fc.user_list[a] ] );
nekotb_fc.user_contribs[ nekotb_fc.user_list[a] ].sort( function(a, b){ return a['revid'] - b['revid'] } ) //tri chronologique ascendant
}
nekotb_fc.every_contrib_ordered.sort( function(a, b){ return b['revid'] - a['revid'] } ) //tri chronologique descendant
var len = nekotb_fc.every_contrib_ordered.length
var dat = new Date()
if( len > 0 ) {
//test de creation de ligne
var first_timestamp = dat.setISO8601(nekotb_fc.oldest_timestamp).getTime() //(car antéchronologique)
var last_timestamp = dat.setISO8601(nekotb_fc.newest_timestamp).getTime()
nekotb_fc.show_timeline(first_timestamp, last_timestamp)
}
return false; //TEST timeline
var ul = div2.createAndAppendElement("ul")
if( len > 0 ) {
ul.appendChild( nekotb_fc.create_contrib_line(nekotb_fc.every_contrib_ordered[0]), false ) //car on ne peut pas comparer cette premiere ligne avec "celle d'avant"
for(var a = 1; a < len ; a++ ) {
var editClash = false //detecteur de clash
if( nekotb_fc.every_contrib_ordered[a]['bgcolor'] != nekotb_fc.every_contrib_ordered[a-1]['bgcolor'] ) {
var ecart = dat.setISO8601(nekotb_fc.every_contrib_ordered[a-1]['timestamp']).getTime() - dat.setISO8601(nekotb_fc.every_contrib_ordered[a]['timestamp']).getTime() / 1000
if( ecart < (nekotb_fc.edit_clash_limit * 60) ) {
editClash = true
}
}
ul.appendChild( nekotb_fc.create_contrib_line(nekotb_fc.every_contrib_ordered[a], editClash ) )
}
}
return false
}
nekotb_fc.show_timeline = function(first_timestamp, last_timestamp) {
var div2 = document.getElementById("nekotb_fc_fusion");
var dat = new Date();
var final = new Array()
var nb_users = nekotb_fc.user_list.length
div2.createAndAppendElement("br")
div2.createAndAppendTextNode("/!\\ Note : à cause des passages à l'heure d'été, le dernier dimanche de mars comporte 23 heures et le dernier dimanche d'octobre 25 heures")
//partie recuperation
for( var a = 0 ; a < nb_users ; a++) {
var cur_day = new Date(first_timestamp).getYYYYMMDD()
var user = nekotb_fc.user_list[a]
final[user] = new Array()
var ligne = ''
var length_user_list = nekotb_fc.user_contribs[user].length
for(var b=0, next_timestamp=false, cur_timestamp=first_timestamp ; cur_timestamp <= last_timestamp ; cur_timestamp=next_timestamp) {
next_timestamp = cur_timestamp + 1000 * 60 * 60 //1H plus tard
var edit = 0
while( b < length_user_list && dat.setISO8601(nekotb_fc.user_contribs[user][b]['timestamp']).getTime() < next_timestamp ) {
edit++
b++
}
//caractere a afficher pour cette heure
if(edit == 0) { ligne += "_" }
else if (edit < 10) { ligne += edit }
else { ligne += "X" }
var new_day = new Date(cur_timestamp).getYYYYMMDD() //test changement de jour pour remplir une case
if(cur_day != new_day) {
final[user][cur_day] = new Array()
final[user][cur_day] = ligne
ligne = ''
cur_day = new_day
}
} //boucle d'un user
final[user][cur_day] = new Array()
final[user][cur_day] = ligne //mise du dernier jour
} //boucle users
//partie affichage
var compteur_jours = 0;
for(var jour = first_timestamp ; jour < last_timestamp ; jour += (1000 * 60 * 60 * 24) ) {
if( compteur_jours % nekotb_fc.timeline_nb_day_per_line == 0 ) {
var table = div2.createAndAppendElement("table", {"class":"nekotb_fc_timeline", "style":"font-family: monospace; border:1px solid black; margin-top:5px;"})
var tr = new Array();
for(var a=0 ; a < nb_users+1 ; a++) {
tr[a] = table.createAndAppendElement("tr")
}
//premiere colonne : la liste des users
tr[0].createAndAppendElement("td", {}, " ")
for(var a=0 ; a < nb_users ; a++) {
tr[a+1].createAndAppendElement("td", {}, nekotb_fc.user_list[a])
}
}
var cur_day = new Date(jour)
tr[0].createAndAppendElement("td", {}, cur_day.getYYYYMMDD() )
for(var b = 0 ; b < nekotb_fc.user_list.length ; b++) {
tr[b+1].createAndAppendElement("td", {}, final[ nekotb_fc.user_list[b] ][ cur_day.getYYYYMMDD() ] )
}
compteur_jours++
}
}
nekotb_fc.create_contrib_line = function(tab, editClash) {
var li = document.createElement("li")
if( editClash == true) { //ligne rouge entre les 2 contribs incriminées
li.setAttribute('style', "border-top: 5px solid red; background-color:"+tab['bgcolor']+";" );
} else {
li.setAttribute('style', "background-color:"+tab['bgcolor']+";" )
}
var timestamp = tab['timestamp'].replace("T", " à ").replace("Z", "") //mise en page du timestamp
li.createAndAppendElement( "a", {'href':"/w/index.php?title="+tab['title']+"&oldid="+tab['revid']}, timestamp )
li.createAndAppendTextNode(" (")
li.createAndAppendElement( "a", {'href':"/w/index.php?title="+tab['title']+"&oldid="+tab['revid']+"&diff=prev"}, "diff" )
li.createAndAppendTextNode(" | ")
li.createAndAppendElement( "a", {'href':"/w/index.php?title="+tab['title']+"&action=history"}, "hist" )
li.createAndAppendTextNode(") ")
if( tab['new'] != null ) {
li.createAndAppendElement("span", {'class':"newpage"}, "N" )
}
if( tab['minor'] != null ) {
li.createAndAppendElement("span", {'class':"minor"}, "m" )
}
li.createAndAppendTextNode(" ")
li.createAndAppendElement( "a", {'href':"/wiki/"+tab['title']}, tab['title'] )
if( tab['comment'] != null && tab['comment'] != "" ) {
li.createAndAppendTextNode(" (")
li.createAndAppendElement("span", {'class':"comment"}, tab['comment'] )
li.createAndAppendTextNode(")")
}
if( tab['top'] != null ) {
li.createAndAppendTextNode(" ")
li.createAndAppendElement("span", {'class':"mw-uctop"}, "(dernière)" )
}
return li
}
/*
retourne un tableau a partir d'une (ou plusieurs) requetes API
*/
nekotb_fc.get_contribution_list = function(p_user) {
var bgcolor = nekotb_fc.bgcolor_list[nekotb_fc.user_count++] // choix de la couleur de fond
var ul = document.getElementById('nekotb_fc_fusion_user_list') // pour faire joujou avec l'affichage
var li = ul.createAndAppendElement( "li", {'style':"background-color:"+bgcolor+";"}, " ")
var http_request = new XMLHttpRequest()
http_request.overrideMimeType('text/xml');
var tableau = new Array()
var compteur_tableau = 0
var newest_timestamp = nekotb_fc.newest_timestamp //variable locale car changé à chaque boucle
var uccontinue = false
do {
if( tableau.length >= nekotb_fc.contrib_limit_per_user ) {
li.firstChild.nodeValue += " Limite atteinte - les contributions les plus anciennes n'apparaitrons pas"
break;
}
var continue_do_while = false
var address = "/w/api.php?format=xml&action=query&rawcontinue=&list=usercontribs&uclimit=500&ucuser=" + p_user + "&ucend=" + nekotb_fc.oldest_timestamp + "&ucstart=" + newest_timestamp
if( uccontinue ) {
address += "&uccontinue="+uccontinue
}
http_request.open('GET', address , false)
http_request.send(null)
var lignes = http_request.responseXML.documentElement.getElementsByTagName("item")
for (var a = 0, len = lignes.length ; a < len ; a++) {
tableau[compteur_tableau] = new Array()
for (var b = 0, len2 = lignes[a].attributes.length ; b < len2; b++) {
tableau[compteur_tableau][ lignes[a].attributes.item(b).name ] = lignes[a].attributes.item(b).value
}
tableau[compteur_tableau]['bgcolor'] = bgcolor //affectée ici pour ne pas avoir à reparser le tableau plus tard
compteur_tableau ++
}
li.firstChild.nodeValue = p_user + " : " + tableau.length + " contributions récupérées."
query_continue = http_request.responseXML.documentElement.getElementsByTagName("query-continue")
if( query_continue && query_continue.length > 0 ) {
uccontinue = query_continue[0].getElementsByTagName("usercontribs")[0].getAttribute('uccontinue')
if( uccontinue && uccontinue != 'undefined' ) { //protection
continue_do_while = true
}
}
} while( continue_do_while );
return tableau
}