MediaWiki:Gadget-ondemand-arbcomVoting.js: различия между версиями

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
Содержимое удалено Содержимое добавлено
ну вот
 
поддержка тёмной темы
 
(не показаны 163 промежуточные версии 14 участников)
Строка 1: Строка 1:
/*
* Скрипт для выборов в Арбитражный комитет (ВП:АК)
*
* Авторы: Kalan, Serhio Magpie, stjn
*/


(function(){
$(function () {
var containerEl = document.getElementById( 'voting-container' );
function en(e) { return encodeURIComponent(e) }
if ( containerEl === null ) {
function el(e) { return document.createElement(e) }
mw.log.warn( 'arbcomVoting: Aborted because no voting container was detected.' );
function tstamp(t) { return !t.getUTCFullYear() ? null : // Safari + Chrome
return;
(t.getUTCFullYear()+":0"+(t.getUTCMonth()+1)+":0"+t.getUTCDate()+":0"+
}
t.getUTCHours() +":0"+ t.getUTCMinutes() +":0"+t.getUTCSeconds())
.replace(/:0?(\d\d)/g, '$1') }
var wgUserName = mw.config.get( 'wgUserName' );
function ch(o) { for (var i in o) { return o[i] } }
if ( wgUserName === null ) {
function wf(s) { return s/*.replace('/wiki/', document.URL.match(/.*\/wiki\//) || document.URL.match(/.*w\//).replace(/\/w\//, '/wiki'))*/ }
mw.log.warn( 'arbcomVoting: Not logged in.' );
return;
var conf = {
}
'criteria': {
'count': 500,
/******* COMMON ******* */
'registration': '2011-08-04T00:00:00Z'
function tstamp(t) {
},
return !t.getUTCFullYear() ? null : // Safari + Chrome
'pagepath': 'Википедия:Выборы арбитров/Осень 2011/Голосование',
(t.getUTCFullYear()+":0"+(t.getUTCMonth()+1)+":0"+t.getUTCDate()+":0"+
'talkpath': 'Википедия:Выборы арбитров/Осень 2011/Обсуждение/',
t.getUTCHours() +":0"+ t.getUTCMinutes() +":0"+t.getUTCSeconds())
'votepath': 'Википедия:Выборы арбитров/Осень 2011/Голосование/',
.replace(/:0?(\d\d)/g, '$1');
'start': new Date('Nov 22 2011 00:00 +0000'),
}
'end': new Date('Nov 29 2011 00:00 +0000')
function ch(o) { for (var i in o) { return o[i] } }
}

var ico = {
/******* VARIABLES *******/
'up': 'http:/upwiki/wikipedia/commons/thumb/',

'supp' : '2/2d/Support-gray.svg/39px-Support-gray.svg.png',
var data = require( './ondemand-arbcomVoting.json' );
'suppinact' : '8/8d/Support-colored.svg/39px-Support-colored.svg.png',
var wgNamespaceNumber = mw.config.get( 'wgNamespaceNumber' );
'suppact' : '5/5b/Support-filled.svg/39px-Support-filled.svg.png',
var votes = {};
'opp' : 'e/e7/Oppose-gray.svg/39px-Oppose-gray.svg.png',
var saving = null;
'oppinact' : '0/06/Oppose-colored.svg/39px-Oppose-colored.svg.png',
var api;
'oppact' : '7/7d/Oppose-filled.svg/39px-Oppose-filled.svg.png'
var statusEl;
}
var loc = {
var btnEl;

'votebutton': 'Проголосовать',
/******* MAIN *******/
'statuscriteria': 'Проверка критериев…',
function votingLength( date, days ) {
'criteriafail': '<h3 class="voting-error">Вы не соответствуете критериям.</h3><p>Ваши голоса на этих выборах <b>не будут засчитаны</b>.</p><p>Впрочем, если вам просто любопытно, как работает скрипт, вы можете посмотреть на него без сохранения голосов.</p>',
var result = new Date( date );
'criteriafailbutton': 'Посмотреть',
result.setDate( result.getDate() + days );
'loadingvotes': 'Проверка имеющихся голосов…',
return result;
'draghere': 'Перетащите первого кандидата сюда',
}
'votinghelp': '<div class="voting-help"><h4>Основная часть</h4>\

<p>Расставьте голоса «за» и «против» рядом с именами тех кандидатов, относительно которых у вас сформировано мнение. Вы сможете дополнить или изменить выбор позже.</p>\
function votingStart() {
</div>',
$( '.voting-container' ).addClass( 'active' );
'schulzehelp': wf('<div class="voting-help"><h4>Дополнительная часть</h4>\
statusEl.textContent = data.strings.statuscriteria;
<div style="float: left; margin: 3em 0; background:#FFFFE0;border:1px solid #FD4;font-size:8pt;padding:0.5em 0.7em;line-height:1.4em">Это ещё не вся дополнительная часть. Не забудьте вернуться сюда вечером среды.</div>\
btnEl.setDisabled(true);
<p>В качестве эксперимента проводится сбор данных для подсчёта голосов по <a href="/ruwiki/wiki/Метод_Шульце">методу Шульце</a>. <b>Пожалуйста, примите участие.</b> Если вам очень хочется, вы можете пропустить этот раздел, но тогда бот вышлет вам под конец персональное приглашение подумать.</p>\
btnEl.$element.css( 'display', 'none' );
<p>Краткая инструкция:</p>\

<ol>\
api.get({
<li>Перетащите какого-нибудь подходящего, на ваш взгляд, кандидата на первый уровень.</li>\
'action': 'query',
<li>Перетаскивайте остальных кандидатов в этом же направлении. Кандидатов можно оставлять на существующих уровнях (отмечены цифрами) или на новых (места для их создания обозначены пунктиром). Во время перетаскивания пунктир показывает относительное расположение кандидата в списке.</li>\
'list': 'users',
<li>Перед сохранением убедитесь в соответствии расстановки вашему мнению: более хорошие кандидаты выше, менее хорошие ниже, равные на одном уровне, совсем неподходящие в разделе «нет предпочтения». Если вы являетесь одним из кандидатов, <em>не оставляйте</em> себя в нижней секции.</li>\
'usprop': ['registration', 'editcount'],
</ol>\
'prop': 'revisions',
<p>До конца выборов вы сможете изменить своё мнение.</p>\
'rvprop': 'content',
<p style="font-size:smaller">Если что-то осталось непонятным, можно <a target="_blank" href="/ruwiki/wiki/Обсуждение_Википедии:Выборы_арбитров/Весна_2011/Голосование">задать вопрос</a>.</p>\
'ususers': wgUserName,
</div>'),
'titles': data.config.voteexceptionspath
'nopreference': 'нет предпочтения',
}).done(votingStartContinue);
'newlevel': 'новый уровень',
}
'justbeforesave': wf('Смело направляйте любые отзывы, предложения и сообщения&nbsp;об&nbsp;ошибках на&nbsp;<a href="/ruwiki/wiki/MediaWiki_talk:Voting12.js">страницу&nbsp;обсуждения</a>.'),

'savebutton': 'Сохранить',
function votingStartContinue(d) {
'saveprog': '<b>Не закрывайте страницу</b> до завершения сохранения.',
query = d.query;
'summary': '\u200Bг\u200Bо\u200Bл\u200Bо\u200Bс\u200B',
pages = query.pages;
'thankyou': wf('<h3>Спасибо за участие в выборах!</h3><p>Вы сможете изменить ваши голоса до конца голосования.</p><p>Только что отданные голоса можно посмотреть на <a href="/ruwiki/wiki/Special:Mycontributions">странице вклада</a>.</p>')
userinfo = query.users[0];
}
if ((// a valid voter must be a non-anonymous user
var cand = 'Alogrin ! AndyVolykhov ! Blacklake ! D.bratchuk ! Dima io ! Drbug ! INSAR ! Levg ! Makakaaaa ! Nikitin.ilya ! ShinePhantom ! VasilievVV ! Зелев Андрей ! Юрий Педаченко'.split(' ! ')
typeof userinfo === 'undefined' || typeof userinfo.missing !== 'undefined' ||
var wgAPIPath = wgServer + wgScriptPath + '/api.php?format=json&'
// who is not blocked
userinfo.blockedby ||
var votes = {}
// whose editcount is at least data.config.criteria.count
var criteriaMatch
userinfo.editcount < data.config.criteria.count ||
var saving = null
// and who is registered no later than data.config.criteria.registration
var aj = sajax_init_object()
// "null" means "before 2005-12-29", user creation was not logged before then
var aj2 = sajax_init_object()
(userinfo.registration !== null && userinfo.registration > data.config.criteria.registration)) &&
var token
// exemptions
('\n' + ch(pages).revisions[0]['*'] + '\n').indexOf('\n* %\n'.replace('%', wgUserName)) == -1
// drag-n-drop
) {
var coo
votingCriteriaFail();
var cooD
} else {
var drag, dragGhost, dragItem, dragOriginal
votingContinue();
var dragHere
}
}
function votingStart() {

importStylesheetURI('http://ru.wikipedia.org/ruwiki/w/index.php?title=MediaWiki:Voting7.css&action=raw&ctype=text/css')
function votingCriteriaFail() {
btn.disabled = true
statusEl.textContent = '';
btn.style.display = 'none'
$( '#voting-msg-criteriafail' ).show();
status.innerHTML = loc.statuscriteria
}
aj2.onreadystatechange = votingStartContinue

aj2.open('GET', wgAPIPath + 'action=query&list=users&usprop=registration|editcount&prop=revisions&rvprop=content&ususers=' +
function votingContinue() {
en(wgUserName) + '&titles=' + en(conf.votepath + '^'), true)
statusEl.textContent = data.strings.loadingvotes;
aj2.send('')
// latest contributions in ns:4
}
api.get({
function votingStartContinue() {
'action': 'query',
if (aj2.readyState != 4) return
'list': 'usercontribs',
if (aj2.status != 200) { // temporary problems?
'ucnamespace': wgNamespaceNumber,
votingStart()
'uclimit': 500,
return
'ucuser': wgUserName,
}
'ucend': tstamp( data.config.start ),
'ucdir': 'older'
query = eval('(' + aj2.responseText + ')').query
}).done(votingDraw);
userinfo = query.users[0]
}
if ((// a valid voter must be a non-anonymous user
userinfo.missing !== undefined ||
function onClick( e ) {
// who is not blocked
e.preventDefault();
userinfo.blockedby ||
var button = e.currentTarget;
// whose editcount is at least conf.criteria.count
var span = button.querySelector( 'span' );
userinfo.editcount < conf.criteria.count ||
var tr = button.parentNode.parentNode;
// and who is registered no later than conf.criteria.registration
var candidate = button.dataset.candidate;
// "null" means "before 2005-12-29", user creation was not logged before then
var type = button.dataset.type;
(userinfo.registration !== null && userinfo.registration > conf.criteria.registration)) &&
var active = Boolean( button.getAttribute( 'aria-checked' ) );
// exemptions
var option = data.options[ type ];
(ch(query.pages).revisions[0]['*'] + '\n').indexOf('\n* %\n'.replace('%', wgUserName)) == -1)
votingCriteriaFail()
// Update other button first
else {
var otherButton = tr.querySelector( `.vote-button[aria-checked="true"]` );
criteriaMatch = 1
var otherType = otherButton && otherButton.dataset.type;
votingContinue()
if ( otherButton ) {
}
var otherVote = otherType === 'support' ? 1 : -1;
}
otherButton.setAttribute( 'aria-checked', false );
function votingCriteriaFail() {
}
status.innerHTML = loc.criteriafail
btn.style.display = ''
// Change vote
btn.disabled = false
var vote = type === 'support' ? 1 : -1;
btn.value = loc.criteriafailbutton
var sameVote = votes[ candidate ].value === vote;
btn.onclick = function(){criteriaMatch = 0; votingContinue()}
votes[ candidate ].value = ( sameVote ? 0 : vote );
}
button.setAttribute( 'aria-checked', !sameVote );
function votingContinue() {
status.innerHTML = loc.loadingvotes
// Update table row
btn.style.display = ''
tr.className = !sameVote ? 'vote-' + type : '';
btn.onclick = votingToken
}
btn.value = loc.savebutton
btn.disabled = true
function addVoteButton( candidate, type, previousVote ) {
// latest contributions in ns:4 + Schulze method page
var option = data.options[ type ];
aj.open('GET', wgAPIPath + 'action=query' +
var didVote = previousVote === ( type === 'support' ? 1 : -1 );
'&list=usercontribs&ucnamespace=4&uclimit=500' +
var votedOpposite = previousVote === ( type === 'support' ? -1 : 1 );
'&ucuser=' + en(wgUserName) +
var voteType = votedOpposite ? 'changevote' : 'novote';
(tstamp(conf.start) ? '&ucend=' + tstamp(conf.start) : '') +

'&ucdir=older' +
var button = document.createElement( 'button' );
'&fake=1&prop=revisions&rvprop=content' +
button.className = 'vote-button skin-invert';
'&titles=' + en(conf.votepath+'$') ,
button.setAttribute( 'role', 'switch' );
true)
button.setAttribute( 'type', 'button' );
aj.onreadystatechange = votingDraw
button.setAttribute( 'data-candidate', candidate );
aj.send('')
button.setAttribute( 'data-type', type );
}
if ( didVote ) {
function votingDraw() {
button.setAttribute( 'data-voted', true );
if (aj.readyState != 4) return
}
if (aj.status != 200) { // temporary problems?
button.setAttribute( 'aria-checked', didVote );
votingContinue()
button.addEventListener( 'click', onClick );
return
}
var span = document.createElement( 'span' );
btn.disabled = !criteriaMatch
span.textContent = option[ voteType ];
status.innerHTML = ''
if ( votedOpposite ) {
span.textContent = span.textContent.replace( '%candidate%', candidate );
var query = eval('(' + aj.responseText + ')')
}
query = query.query
button.title = span.textContent;
var co = query.usercontribs
var sc = ch(query.pages).revisions[0]['*'].split('\n')
button.appendChild( span );
return button;
// retrieving "traditional" votes, according to contributions, only latest ones are valid
}
for (i=0; i<cand.length; i++) votes[cand[i]] = {'orig':0, 'value':0}

for (var i=co.length-1; i>=0; i--) {
function votingDraw(d) {
var m = co[i].title.indexOf(conf.votepath) == 0
statusEl.textContent = '';
if (m) m = co[i].title.match(/\/([+-])\/(.*?)$/)
btnEl.setLabel(data.strings.savebutton);
if (m) votes[m[2]] = { 'orig' : m[1]=='+' ? +1 : -1,
btnEl.setDisabled(false);
'value': 0 }
btnEl.off('click').on('click', votingSave);
}
btnEl.$element.css( 'display', '' );

// retrieving Schulze method page
var co = ( d.query && d.query.usercontribs ) || [];
// we assume that the bot will immediately revert not-well-formed edits

for (i=0; i<sc.length; i++) {
// retrieving votes, according to contributions, only latest ones are valid
if (sc[i].indexOf('* ' + wgUserName + ' | ') == 0) {
for ( var i = 0; i < data.candidates.length; i++ ) {
votes._ = {'reading': true}
votes[ data.candidates [ i ] ] = { 'orig': 0, 'value': 0 };
var j = 0
}
continue
}
for ( var i = co.length - 1; i >= 0; i-- ) {
var match = false;
if (!votes._) continue
if ( co[ i ].title.startsWith( data.config.votepath ) ) {
if (sc[i].indexOf('* ') == 0) votes._.reading = undefined
match = co[ i ].title.match( /\/([+-])\/(.*?)$/ );
if (votes._.reading) {
}
j++
if ( match ) {
var m = sc[i].match(/^\*(.) (.*)/)
var hasSummary = co[ i ].comment === data.strings.summary;
m[2] = m[2].split(' | ')
if ( hasSummary ) {
votes._[m[1]=='#'?j:0] = m[2]
votes[ match[ 2 ] ] = {
}
'orig': match[ 1 ] === '+' ? +1 : -1,
}
'value': 0
if (!votes._) {
};
votes._ = {0: cand}
}
}
}
if (!votes._[1]) votes._[1] = []
}
if (!votes._[0]) votes._[0] = []

var div1 = document.createElement( 'div' );
// drawing
var div1 = el('div')
div1.id = 'voting-standard';
var help = document.createElement( 'p' );
div1.id = 'voting-standard'
div1.innerHTML = loc.votinghelp
help.textContent = data.strings.votinghelp;
div1.appendChild( help );
var tab = el('table')
var tab = document.createElement( 'table' );
tab.className = 'voting-table';
var tr, img, td1, td2, td3, lin

for (i in votes) {
var caption = document.createElement( 'caption' );
if (i != '_') {
caption.textContent = data.strings.votebutton;
tr = el('tr')
tab.appendChild( caption );
td1 = el('td')

img = el('img')
for ( var candidate in votes ) {
img.alt = '+'
if ( candidate.match( /^_[srl]$/ ) ) {
img.width = img.height = 39
continue;
img.src = ico.up + (votes[i].orig == 1 ? ico.suppinact : ico.supp)
}
lin = el('a')
var tr = document.createElement( 'tr' );
lin.href = '#'
lin.title = '+'
var th = document.createElement( 'th' );
lin.appendChild(img)
th.setAttribute( 'scope', 'row' );
td1.appendChild(lin)
var lin = document.createElement('a');
lin.href = mw.util.getUrl( data.config.talkpath + candidate );
td2 = el('td')
lin.target = '_blank';
img = el('img')
lin.textContent = candidate;
img.alt = '−'
lin.title = data.strings.questionsTooltip.replace( '%candidate%', candidate );
img.width = img.height = 39
th.appendChild( lin );
img.src = ico.up + (votes[i].orig == -1 ? ico.oppinact : ico.opp)
tr.appendChild( th );
lin = el('a')
lin.href = '#'
// Support button
lin.title = '-'
var td1 = document.createElement( 'td' );
lin.appendChild(img)
td1.appendChild( addVoteButton( candidate, 'support', votes[ candidate ].orig ) );
td2.appendChild(lin)
tr.appendChild( td1 );
td3 = el('td')
// Oppose button
td3.className = 'talklink'
var td2 = document.createElement( 'td' );
lin = el('a')
td2.appendChild( addVoteButton( candidate, 'oppose', votes[ candidate ].orig ) );
lin.href = wgServer + wgScript + '?title=' + encodeURI(conf.talkpath + i)
tr.appendChild( td2 );
lin.target = '_blank'
lin.innerHTML = i
tab.appendChild( tr );
}
td3.appendChild(lin)
tr.appendChild(td1)
div1.appendChild(tab);

tr.appendChild(td2)
statusEl.textContent = '';
tr.appendChild(td3)
$( '#voting-msg-justbeforesave' ).show();
tab.appendChild(tr)
statusEl.parentNode.insertBefore(div1, statusEl);
}
}
}
div1.appendChild(tab)
function showThankYou() {
// IE has problems inserting the table
div1.innerHTML += ''
containerEl.innerHTML = '';
$( '.voting-msg' ).hide();
var imgs = div1.getElementsByTagName('img')
$( '#voting-msg-thankyou' ).show();
for (i=0; i<imgs.length; i++)
}
imgs[i].parentNode.onclick = oncl
function votingSave() {
dragHere = el('span')
if (!saving) {
dragHere.id = 'voting-draghere'
var div = document.createElement('div');
dragHere.innerHTML = loc.draghere
div.className = 'voting-saving';
div.textContent = data.strings.saveprog;
var div2 = el('div')
div.insertAdjacentHTML( 'afterbegin', '<div class="voting-progress"><div class="voting-progress-progress" style="width:0%">&nbsp;</div></div>' );
div2.id = 'voting-schulze'
statusEl.parentNode.appendChild( div );
div2.innerHTML = loc.schulzehelp
btnEl.setDisabled( true );
var ul = el('ul')
$( '#voting-standard' ).hide();
ul.id = 'schulze-list'

var gw, theid, can, first, level, spc
saving = { 'cursor': 0, 'pages': [] };
var c

for (i=1; i!=null; i==0 ? i=null : i++) {
for (var i in votes) {
gw = el('li')
if (votes[i].value != 0 && votes[i].orig != votes[i].value) {
gw.className = 'groundwork'
saving.pages[saving.cursor++] = {
theid = el('div')
'text': '\n# [[user:' + wgUserName + '|' + wgUserName + ']] ~' + '~~' + '~~\n',
theid.className = 'li-id'
theid.innerHTML = !votes._[i] ? i : (i==1 ? '' : i-1) + '' + i
'page': data.config.votepath + (votes[i].value==1?'+':'-') + '/' + i
};
gw.appendChild(theid)
}
}
if (!votes._[i]) i=0

saving.cursor = -5;
level = el('li')
if (saving.pages.length) {
level.className = 'level'
votingSave();
if (i==0) level.id = 'voting-no-preference'
} else {
if (i==1 && votes._[i].join('!') == '') {
showThankYou();
level.appendChild(dragHere)
}
}
} else {
if (saving.cursor === -5) {
theid = el('div')
saving.cursor = 0;
theid.className = 'li-id'
}
theid.innerHTML = i==0 ? loc.nopreference : i
if (saving.cursor === -1) {
level.appendChild(theid)
return;
}
for (c=0; c<votes._[i].length; c++) {

can = el('a')
document.querySelector( '.voting-progress-progress' ).style.width = 100*(saving.cursor+1)/saving.pages.length + '%';
can.onclick = function() { return false }

can.onmousedown = boxMouseDown
api.postWithToken('edit', {
can.innerHTML = votes._[i][c]
'action': 'edit',
can.className = 'cand'
'notminor': 1,
'unwatch': 1,
level.appendChild(can)
'assert': 'user',
}
'summary': data.strings.summary,
level.appendChild(spacer())
'title': saving.pages[saving.cursor].page,
'appendtext': saving.pages[saving.cursor].text
ul.appendChild(gw)
}).done(votingSave);
ul.appendChild(level)
saving.cursor++;
}
if (!saving.pages[saving.cursor]) {
div2.appendChild(ul)
saving.cursor = -1;
showThankYou();
if (navigator.appName=='Microsoft Internet Explorer') {
}
// troubles with scopes
}
document.onmousemove = mouseMove
}
document.onmouseup = mouseUp

} else {
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs', 'oojs-ui']).done(function () {
hookEvent('mousemove', mouseMove)
api = new mw.Api();
hookEvent('mouseup', mouseUp)
}
// Prepare dates
data.config.start = new Date(data.config.start);
status.innerHTML = loc.justbeforesave
if ( typeof data.config.votelength !== 'undefined' ) {
status.parentNode.insertBefore(div1, status)
data.config.end = votingLength( data.config.start, data.config.votelength );
status.parentNode.insertBefore(div2, status)
} else {
}
data.config.end = new Date( data.config.end );
function votingToken() {
}
aj = sajax_init_object()
// Prepare pages paths
aj.onreadystatechange = votingTokenContinue
data.config.pagepath = data.config.pagepath.replace('%config.path%', data.config.path);
aj.open('GET', wgAPIPath + 'action=query&prop=info&intoken=edit&titles=42', true)
data.config.talkpagepath = mw.util.getUrl( data.config.talkpagepath.replace('%config.path%', data.config.path) );
aj.send('')
data.config.talkpath = data.config.talkpath.replace('%config.path%', data.config.path);
}
data.config.votepath = data.config.votepath.replace('%config.path%', data.config.path);
function votingTokenContinue() {
data.config.voteexceptionspath = data.config.voteexceptionspath.replace('%config.path%', data.config.path);
if (aj.readyState != 4) return
// Init
if (aj.readyState == 4 && aj.status != 200) {
if ( containerEl ) {
votingToken()
containerEl.innerHTML = '';
return

}
statusEl = document.createElement('div');
token = eval('(' + aj.responseText + ')')
statusEl.className = 'voting-status voting-msg';
token = ch(token.query.pages).edittoken
containerEl.appendChild( statusEl );
votingSave()
}
$( '.voting-msg a' ).attr( 'target', '_blank' );
function votingSave() {

if (saving == null) {
btnEl = new OO.ui.ButtonWidget( {
var div = el('div')
label: data.strings.votebutton,
div.id = 'voting-saving'
flags: ['primary', 'progressive'],
div.innerHTML = '<div id="voting-progress"><div id="voting-progress-progress" style="width:0%">&nbsp;</div></div>' + loc.saveprog
id: 'voting-button'
status.parentNode.appendChild(div)
} );
btn.disabled = true
btnEl.on('click', votingStart);
document.getElementById('voting-schulze' ).style.visibility = 'hidden'
containerEl.appendChild(btnEl.$element.get(0));
document.getElementById('voting-standard').style.visibility = 'hidden'
}
} );
saving = {'cursor': 0, 'pages': []}
});
for (var i in votes) {
if (votes[i].value != 0 && votes[i].orig != votes[i].value) {
saving.pages[saving.cursor] = {'text': '\n# [[user:' + wgUserName + '|' + wgUserName + ']] ~' + '~~' + '~~\n',
'page': conf.votepath + (votes[i].value==1?'+':'-') + '/' + i}
saving.cursor++
}
}
var text, text2
var ms, mi
var li = document.getElementById('schulze-list').getElementsByTagName('li')
text = text2 = '\n* ' + wgUserName + ' | ~' + '~~' + '~~\n'
for (i=0; i<li.length; i++) {
ms = li[i].getElementsByTagName('a')
if (ms.length) {
mi = []
for (var j=0; j<ms.length; j++) mi[mi.length] = ms[j].innerHTML
text += (li[i].id == 'voting-no-preference' ? '*: ' : '*# ') + mi.join(' | ') + '\n'
}
}
for (i=1; i!=null; i==0 ? i=null : i++) {
if (!votes._[i]) i=0
if (votes._[i].join('.')) text2 += (i==0 ? '*: ' : '*# ') + votes._[i].join(' | ') + '\n'
}
if (text != text2) {
saving.pages[saving.cursor] = {'page': conf.votepath + '$',
'text': text}
}
saving.cursor = -5
if (saving.pages.length)
votingSave()
else
document.getElementById('voting-container').innerHTML = loc.thankyou
} else {
if (saving.cursor != -5 && aj.readyState != 4) return
if (aj.readyState == 4 && aj.status != 200) {
votingSave()
return
}
if (saving.cursor == -5) saving.cursor = 0
if (saving.cursor == -1) return
document.getElementById('voting-progress-progress').style.width = 100*(saving.cursor+1)/saving.pages.length + '%'
aj = sajax_init_object()
aj.open('POST', wgAPIPath + 'action=edit&notminor=1&unwatch=1&token=' + en(token) + '&summary=' + en(loc.summary) +
'&title=' + en(saving.pages[saving.cursor].page) +
'&appendtext=' + en(saving.pages[saving.cursor].text), true)
aj.onreadystatechange = votingSave
aj.send('')
saving.cursor++
if (!saving.pages[saving.cursor]) {
saving.cursor = -1
document.getElementById('voting-container').innerHTML = loc.thankyou
}
}
}
// onclick() for round buttons
function oncl() {
var imgs = this.parentNode.parentNode.getElementsByTagName('img')
var link = this.parentNode.parentNode.getElementsByTagName('a')[2]
var ca = link.innerHTML
var ti = this.title
var vo = ti=='+'?1:-1
votes[ca].value = (votes[ca].value==vo) ? 0 : vo
imgs[0].src = ico.up + (votes[ca].value== 1 ?ico.suppact:(votes[ca].orig== 1 ?ico.suppinact:ico.supp))
imgs[1].src = ico.up + (votes[ca].value==-1 ?ico.oppact :(votes[ca].orig==-1 ?ico.oppinact :ico.opp))
link.className = ['opp', '', 'supp'][votes[ca].value + 1]
return false
}
// used by drag-n-drop
function disableSelection(el, g){
// doesn't work for Opera, thus onmousemove drops selection ranges
el.onselectstart = g ? function(){ return false } : null
el.unselectable = g ? "on" : "off"
el.style.MozUserSelect = g ? "none" : ""
}
function absolutePosition(el) {
q = el
co = {'x': 0, 'y': 0}
while (q.offsetParent){
co.x += q.offsetLeft
co.y += q.offsetTop
q = q.offsetParent
}
return co
}
function coords(e) {
if (e.pageX !== undefined)
return {'x': e.pageX, 'y': e.pageY}
else // IE (buggy for v7 while zoomed, but who cares?)
return {
'x': e.clientX + document.documentElement.scrollLeft,
'y': e.clientY + document.documentElement.scrollTop
}
}
function boxMouseDown() {
if (dragHere && dragHere.parentNode) dragHere.parentNode.removeChild(dragHere)
dragGhost = this
dragGhost.id = 'voting-ghost'
dragOriginal = dragItem = this.parentNode
dragItem.className += ' dragitem'
drag = document.createElement('a')
drag.innerHTML = this.innerHTML
drag.id = 'voting-drag'
drag.className = 'cand'
cooD = absolutePosition(dragGhost)
cooD.x -= coo.x
cooD.y -= coo.y
mouseMove({'pageX': coo.x, 'pageY': coo.y})
document.body.appendChild(drag)
disableSelection(document.body, true)
disableSelection(drag, true)
}
function mouseMove(e) {
coo = coords(e || window.event)
if (!drag) return
if (document.defaultView) // Opera: removing selection
document.defaultView.getSelection().removeAllRanges()
drag.style.left = (coo.x + cooD.x) + "px"
drag.style.top = (coo.y + cooD.y) + "px"
var lis = document.getElementById('schulze-list').getElementsByTagName('li')
var dragCurrent
for (i=0; i<lis.length; i++) {
pos = absolutePosition(lis[i])
if (pos.x <= coo.x &&
pos.y <= coo.y &&
pos.x + lis[i].offsetWidth > coo.x &&
pos.y + lis[i].offsetHeight > coo.y) {
dragCurrent = lis[i]
}
}
if (!dragCurrent) { // pointer outside the whole list, returning ghost to its original location
dragCurrent = dragOriginal
}
if (dragCurrent != dragItem) { // changing target level
dragItem.className = dragItem.className.replace(' dragitem', '')
d = dragItem.getElementsByTagName('div')[0]
d.innerHTML = d.innerHTML.replace(/^([^:]+).*/, '$1')
dragItem.removeChild(dragGhost)
dragItem = dragCurrent
dragItem.className += ' dragitem'
if (dragItem.className.match(/groundwork/)) {
dragItem.getElementsByTagName('div')[0].innerHTML += ': ' + loc.newlevel
dragItem.appendChild(dragGhost)
}
else { // not an empty level, so inserting dragGhost in an appropriate place
var bef
var ch = dragItem.getElementsByTagName('a')
var spacer = dragItem.getElementsByTagName('span')[0]
if (ch.length == 0) {
bef = spacer
} else {
bef = ch[0]
for (i=0; i<ch.length; i++)
if (ch[i].innerHTML < dragGhost.innerHTML)
bef = ch[i+1] || spacer
}
dragItem.insertBefore(dragGhost, bef)
}
}
}
function spacer() {
var el = document.createElement('span')
el.className = 'spacer'
el.innerHTML = '&nbsp;'
return el
}
function mouseUp() {
if (!dragGhost || !drag || !dragItem) return
dragItem.className = dragItem.className.replace(' dragitem', '')
dragItem = null
dragGhost.id = ''
dragGhost = null
disableSelection(document.body, false)
disableSelection(drag, false)
document.body.removeChild(drag)
drag = null
// arranging structures
function groundwork() {
var el = document.createElement('li')
el.className = 'groundwork'
el.innerHTML = '<div class="li-id">!</div>'
return el
}
// creating groundworks around any newly created level
var lis = document.getElementById('schulze-list').getElementsByTagName('li')
for (i=0; i<lis.length-1; i++)
if (lis[i].className == 'groundwork' && lis[i].getElementsByTagName('a').length > 0) {
lis[i].className = 'level'
lis[i].appendChild(spacer())
lis[i].parentNode.insertBefore(groundwork(), lis[i].nextSibling)
lis[i].parentNode.insertBefore(groundwork(), lis[i])
}
// destroying empty levels, except when there's only one
var lis = document.getElementById('schulze-list').getElementsByTagName('li')
if (lis.length > 4)
for (i=0; i<lis.length-1; i++)
if (lis[i].className == 'level' && lis[i].getElementsByTagName('a').length == 0)
lis[i].parentNode.removeChild(lis[i])
// blowing up inevitable duplicate groundworks
var lis = document.getElementById('schulze-list').getElementsByTagName('li')
for (i=1; i<lis.length; i++) {
if (lis[i].className == 'groundwork' && lis[i-1].className == 'groundwork')
lis[i].parentNode.removeChild(lis[i])
}
var lis = document.getElementById('schulze-list').getElementsByTagName('li')
// "drag first candidate here" label
if (lis[1].getElementsByTagName('a').length == 0)
lis[1].insertBefore(dragHere, lis[1].getElementsByTagName('span')[0])
// renumbering
var j = 0
for (i=0; i<lis.length-1; i++) {
lis[i].getElementsByTagName('div')[0].innerHTML =
lis[i].className == 'groundwork'
? (j==0 ? '…'+(++j) : (i==lis.length-2 ? ++j : j+'…'+(++j))) : j
}
}
document.getElementById('voting-container').innerHTML = ''
var btn = el('input')
btn.type = 'button'
btn.id = 'voting-button'
btn.value = loc.votebutton
btn.onclick = votingStart
var status = el('div')
status.id = 'voting-status'
document.getElementById('voting-container').appendChild(status)
document.getElementById('voting-container').appendChild(btn)
})()

Текущая версия от 13:25, 1 августа 2024

/* 
 * Скрипт для выборов в Арбитражный комитет (ВП:АК)
 * 
 * Авторы: Kalan, Serhio Magpie, stjn
 */

$(function () {
	var containerEl = document.getElementById( 'voting-container' );
	if ( containerEl === null ) {
		mw.log.warn( 'arbcomVoting: Aborted because no voting container was detected.' );
		return;
	}
	
	var wgUserName = mw.config.get( 'wgUserName' );
	if ( wgUserName === null ) {
		mw.log.warn( 'arbcomVoting: Not logged in.' );
		return;
	}
	
	/******* COMMON ******* */
	function tstamp(t) {
		return !t.getUTCFullYear() ? null : // Safari + Chrome
			(t.getUTCFullYear()+":0"+(t.getUTCMonth()+1)+":0"+t.getUTCDate()+":0"+
			t.getUTCHours()   +":0"+ t.getUTCMinutes() +":0"+t.getUTCSeconds())
			.replace(/:0?(\d\d)/g, '$1');
	}
	function ch(o) { for (var i in o) { return o[i] } }

	/******* VARIABLES *******/

	var data = require( './ondemand-arbcomVoting.json' );
	var wgNamespaceNumber = mw.config.get( 'wgNamespaceNumber' );
	var votes = {};
	var saving = null;
	var api;
	var statusEl;
	var btnEl;

	/******* MAIN *******/
	function votingLength( date, days ) {
		var result = new Date( date );
		result.setDate( result.getDate() + days );
		return result;
	}

	function votingStart() {
		$( '.voting-container' ).addClass( 'active' );
		statusEl.textContent = data.strings.statuscriteria;
		btnEl.setDisabled(true);
		btnEl.$element.css( 'display', 'none' );

		api.get({
			'action': 'query',
			'list': 'users',
			'usprop': ['registration', 'editcount'],
			'prop': 'revisions',
			'rvprop': 'content',
			'ususers': wgUserName,
			'titles': data.config.voteexceptionspath
		}).done(votingStartContinue);
	}

	function votingStartContinue(d) {
		query = d.query;
		pages = query.pages;
		userinfo = query.users[0];
		if ((// a valid voter must be a non-anonymous user
			typeof userinfo === 'undefined' || typeof userinfo.missing !== 'undefined' ||
			// who is not blocked
			userinfo.blockedby ||
			// whose editcount is at least data.config.criteria.count
			userinfo.editcount < data.config.criteria.count ||
			// and who is registered no later than data.config.criteria.registration
			// "null" means "before 2005-12-29", user creation was not logged before then
			(userinfo.registration !== null && userinfo.registration > data.config.criteria.registration)) &&
			// exemptions
			('\n' + ch(pages).revisions[0]['*'] + '\n').indexOf('\n* %\n'.replace('%', wgUserName)) == -1
		) {
			votingCriteriaFail();
		} else {
			votingContinue();
		}
	}

	function votingCriteriaFail() {
		statusEl.textContent = '';
		$( '#voting-msg-criteriafail' ).show();
	}

	function votingContinue() {
		statusEl.textContent = data.strings.loadingvotes;
		// latest contributions in ns:4
		api.get({
			'action': 'query',
			'list': 'usercontribs',
			'ucnamespace': wgNamespaceNumber,
			'uclimit': 500,
			'ucuser': wgUserName,
			'ucend': tstamp( data.config.start ),
			'ucdir': 'older'
		}).done(votingDraw);
	}
	
	function onClick( e ) {
		e.preventDefault();
		var button = e.currentTarget;
		var span = button.querySelector( 'span' );
		var tr = button.parentNode.parentNode;
		var candidate = button.dataset.candidate;
		var type = button.dataset.type;
		var active = Boolean( button.getAttribute( 'aria-checked' ) );
		var option = data.options[ type ];
		
		// Update other button first
		var otherButton = tr.querySelector( `.vote-button[aria-checked="true"]` );
		var otherType = otherButton && otherButton.dataset.type;
		if ( otherButton ) {
			var otherVote = otherType === 'support' ? 1 : -1;
			otherButton.setAttribute( 'aria-checked', false );
		}
		
		// Change vote
		var vote = type === 'support' ? 1 : -1;
		var sameVote = votes[ candidate ].value === vote;
		votes[ candidate ].value = ( sameVote ? 0 : vote );
		button.setAttribute( 'aria-checked', !sameVote );
		
		// Update table row
		tr.className = !sameVote ? 'vote-' + type : '';
	}
	
	function addVoteButton( candidate, type, previousVote ) {
		var option = data.options[ type ];
		var didVote = previousVote === ( type === 'support' ? 1 : -1 );
		var votedOpposite = previousVote === ( type === 'support' ? -1 : 1 );
		var voteType = votedOpposite ? 'changevote' : 'novote';

		var button = document.createElement( 'button' );
		button.className = 'vote-button skin-invert';
		button.setAttribute( 'role', 'switch' );
		button.setAttribute( 'type', 'button' );
		button.setAttribute( 'data-candidate', candidate );
		button.setAttribute( 'data-type', type );
		if ( didVote ) {
			button.setAttribute( 'data-voted', true );
		}
		button.setAttribute( 'aria-checked', didVote );
		button.addEventListener( 'click', onClick );
		
		var span = document.createElement( 'span' );
		span.textContent = option[ voteType ];
		if ( votedOpposite ) {
			span.textContent = span.textContent.replace( '%candidate%', candidate );
		}
		button.title = span.textContent;
		
		button.appendChild( span );
		return button;
	}

	function votingDraw(d) {
		statusEl.textContent = '';
		btnEl.setLabel(data.strings.savebutton);
		btnEl.setDisabled(false);
		btnEl.off('click').on('click', votingSave);
		btnEl.$element.css( 'display', '' );

		var co = ( d.query && d.query.usercontribs ) || [];

		// retrieving votes, according to contributions, only latest ones are valid
		for ( var i = 0; i < data.candidates.length; i++ ) {
			votes[ data.candidates [ i ] ] = { 'orig': 0, 'value': 0 };
		}
		for ( var i = co.length - 1; i >= 0; i-- ) {
			var match = false;
			if ( co[ i ].title.startsWith( data.config.votepath ) ) {
				match = co[ i ].title.match( /\/([+-])\/(.*?)$/ );
			}
			if ( match ) {
				var hasSummary = co[ i ].comment === data.strings.summary;
				if ( hasSummary ) {
					votes[ match[ 2 ] ] = {
						'orig': match[ 1 ] === '+' ? +1 : -1,
						'value': 0
					};
				}
			}
		}

		var div1 = document.createElement( 'div' );
		div1.id = 'voting-standard';
		var help = document.createElement( 'p' );
		help.textContent = data.strings.votinghelp;
		div1.appendChild( help );
		var tab = document.createElement( 'table' );
		tab.className = 'voting-table';

		var caption = document.createElement( 'caption' );
		caption.textContent = data.strings.votebutton;
		tab.appendChild( caption );

		for ( var candidate in votes ) {
			if ( candidate.match( /^_[srl]$/ ) ) {
				continue;
			}
			var tr = document.createElement( 'tr' );
			
			var th = document.createElement( 'th' );
			th.setAttribute( 'scope', 'row' );
			var lin = document.createElement('a');
			lin.href = mw.util.getUrl( data.config.talkpath + candidate );
			lin.target = '_blank';
			lin.textContent = candidate;
			lin.title = data.strings.questionsTooltip.replace( '%candidate%', candidate );
			th.appendChild( lin );
			tr.appendChild( th );
			
			// Support button
			var td1 = document.createElement( 'td' );
			td1.appendChild( addVoteButton( candidate, 'support', votes[ candidate ].orig ) );
			tr.appendChild( td1 );
			
			// Oppose button
			var td2 = document.createElement( 'td' );
			td2.appendChild( addVoteButton( candidate, 'oppose', votes[ candidate ].orig ) );
			tr.appendChild( td2 );
			
			tab.appendChild( tr );
		}
		
		div1.appendChild(tab);

		statusEl.textContent = '';
		$( '#voting-msg-justbeforesave' ).show();
		statusEl.parentNode.insertBefore(div1, statusEl);
	}
	
	function showThankYou() {
		containerEl.innerHTML = '';
		$( '.voting-msg' ).hide();
		$( '#voting-msg-thankyou' ).show();
	}
	
	function votingSave() {
		if (!saving) {
			var div = document.createElement('div');
			div.className = 'voting-saving';
			div.textContent = data.strings.saveprog;
			div.insertAdjacentHTML( 'afterbegin', '<div class="voting-progress"><div class="voting-progress-progress" style="width:0%">&nbsp;</div></div>' );
			statusEl.parentNode.appendChild( div );
			btnEl.setDisabled( true );
			$( '#voting-standard' ).hide();

			saving = { 'cursor': 0, 'pages': [] };

			for (var i in votes) {
				if (votes[i].value != 0 && votes[i].orig != votes[i].value) {
					saving.pages[saving.cursor++] = {
						'text': '\n# [[user:' + wgUserName + '|' + wgUserName + ']] ~' + '~~' + '~~\n',
						'page': data.config.votepath + (votes[i].value==1?'+':'-') + '/' + i
					};
				}
			}

			saving.cursor = -5;
			if (saving.pages.length) {
				votingSave();
			} else {
				showThankYou();
			}
		} else {
			if (saving.cursor === -5) {
				saving.cursor = 0;
			}
			if (saving.cursor === -1) {
				return;
			}

			document.querySelector( '.voting-progress-progress' ).style.width = 100*(saving.cursor+1)/saving.pages.length + '%';

			api.postWithToken('edit', {
				'action': 'edit',
				'notminor': 1,
				'unwatch': 1,
				'assert': 'user',
				'summary': data.strings.summary,
				'title': saving.pages[saving.cursor].page,
				'appendtext': saving.pages[saving.cursor].text
			}).done(votingSave);
			saving.cursor++;
			if (!saving.pages[saving.cursor]) {
				saving.cursor = -1;
				showThankYou();
			}
		}
	}

	mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs', 'oojs-ui']).done(function () {
		api = new mw.Api();
		
		// Prepare dates
		data.config.start = new Date(data.config.start);
		if ( typeof data.config.votelength !== 'undefined' ) {
			data.config.end = votingLength( data.config.start, data.config.votelength );
		} else {
			data.config.end = new Date( data.config.end );
		}
		// Prepare pages paths
		data.config.pagepath = data.config.pagepath.replace('%config.path%', data.config.path);
		data.config.talkpagepath = mw.util.getUrl( data.config.talkpagepath.replace('%config.path%', data.config.path) );
		data.config.talkpath = data.config.talkpath.replace('%config.path%', data.config.path);
		data.config.votepath = data.config.votepath.replace('%config.path%', data.config.path);
		data.config.voteexceptionspath = data.config.voteexceptionspath.replace('%config.path%', data.config.path);
		// Init
		if ( containerEl ) {
			containerEl.innerHTML = '';

			statusEl = document.createElement('div');
			statusEl.className = 'voting-status voting-msg';
			containerEl.appendChild( statusEl );
			
			$( '.voting-msg a' ).attr( 'target', '_blank' );

			btnEl = new OO.ui.ButtonWidget( {
				label: data.strings.votebutton,
				flags: ['primary', 'progressive'],
				id: 'voting-button'
			} );
			btnEl.on('click', votingStart);
			containerEl.appendChild(btnEl.$element.get(0));
		}
	} );
});