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

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
Содержимое удалено Содержимое добавлено
ещё больше кода
поддержка тёмной темы
 
(не показано 157 промежуточных версий 14 участников)
Строка 1: Строка 1:
/*
(function(){
* Скрипт для выборов в Арбитражный комитет (ВП:АК)
appendCSS('.cand-list { width: 25em } #voting-container ul { margin: 1.5em 0 } #voting-container ul li { width: 19em !important } #voting-help-final { margin: 3em 0 0; border-top: 1px solid #888} .cand-selected { background: #97B6E2 !important } #voting-limited-count { float: right; font-size: 300%; text-align: right; width: 1.5em; line-height: 100%; color: #ff5555 } #voting-limited-count.ok { color: #71C837; font-weight: bold }')
*
* Авторы: Kalan, Serhio Magpie, stjn
function en(e) { return encodeURIComponent(e) }
*/
function el(e) { return document.createElement(e) }
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] } }
var conf = {
'criteria': {
'count': 500,
'registration': '2011-08-04T23:59:60Z'
},
'pagepath': 'Википедия:Выборы арбитров/Осень 2011/Голосование',
'talkpath': 'Википедия:Выборы арбитров/Осень 2011/Обсуждение/',
'votepath': 'Википедия:Выборы арбитров/Осень 2011/Голосование/',
'start': new Date('Nov 22 2011 00:00 +0000'),
'end': new Date('Nov 29 2011 00:00 +0000')
}
var ico = {
'up': '/upwiki/wikipedia/commons/thumb/',
'supp' : '2/2d/Support-gray.svg/39px-Support-gray.svg.png',
'suppinact' : '8/8d/Support-colored.svg/39px-Support-colored.svg.png',
'suppact' : '5/5b/Support-filled.svg/39px-Support-filled.svg.png',
'opp' : 'e/e7/Oppose-gray.svg/39px-Oppose-gray.svg.png',
'oppinact' : '0/06/Oppose-colored.svg/39px-Oppose-colored.svg.png',
'oppact' : '7/7d/Oppose-filled.svg/39px-Oppose-filled.svg.png'
}
var loc = {
'votebutton': 'Проголосовать',
'statuscriteria': 'Проверка критериев…',
'criteriafail': '<h3 class="voting-error">Вы не соответствуете критериям.</h3><p>Ваши голоса на этих выборах <b>не будут засчитаны</b>.</p><p>Впрочем, если вам просто любопытно, как работает скрипт, вы можете посмотреть на него без сохранения голосов.</p>',
'criteriafailbutton': 'Посмотреть',
'loadingvotes': 'Проверка имеющихся голосов…',
'draghere': 'Перетащите первого кандидата сюда',
'votinghelp': '<div class="voting-help"><h4>Основная часть</h4>\
<p>Расставьте голоса «за» и «против» рядом с именами тех кандидатов, относительно которых у вас сформировано мнение. Вы сможете дополнить или изменить выбор позже.</p>\
</div>',
'etchelp1': '<div class="voting-help"><h4>Дополнительная часть</h4>\
<p>В этот раз дополнительная часть состоит из нескольких опросников. Вы можете принять участие не во всех или вовсе проигнорировать этот блок, но лучше всего заполните все три. Это поможет улучшить процедуру выборов.</p></div>\
<div class="voting-help"><h5>Метод Шульце</h5>\
<ol>\
<li>Перетащите какого-нибудь подходящего, на ваш взгляд, кандидата на первый уровень.</li>\
<li>Перетаскивайте остальных кандидатов в этом же направлении. Кандидатов можно оставлять на существующих уровнях (отмечены цифрами) или на новых (места для их создания обозначены пунктиром). Во время перетаскивания пунктир показывает относительное расположение кандидата в списке.</li>\
<li>Перед сохранением убедитесь в соответствии расстановки вашему мнению: более хорошие кандидаты выше, менее хорошие ниже, равные на одном уровне, совсем неподходящие в разделе «нет предпочтения». Если вы являетесь одним из кандидатов, <em>не оставляйте</em> себя в нижней секции.</li>\
</ol>\
</div>',
'etchelp2': '<div id="voting-etc-schulze"></div>\
<div class="voting-help"><h5>Целевой набор резервных арбитров</h5>\
<p>Оцените, насколько каждый кандидат пригоден на роль <strong>именно резервного арбитра</strong>. Если, по вашему мнению, кандидат будет хорош только как основой арбитр, <strong>не голосуйте за</strong>.</p>\
</div>',
'etchelp3': '<div class="voting-help"><h5>Идеальный состав</h5>\
<p>Выберите <strong>ровно пять</strong> арбитров из списка. Голос за большее или меньшее количество арбитров будет сохранён, но проигнорирован при подсчёте.</p>\
</div>',
'etchelp4': '<div class="voting-help" id="voting-help-final"><p>До конца выборов вы сможете изменить своё мнение.</p>\
<p>Если что-то осталось непонятным, можно <a target="_blank" href="/ruwiki/wiki/Обсуждение_Википедии:Выборы_арбитров/Осень_2011/Голосование">задать вопрос</a>.</p></div>',
'nopreference': 'нет предпочтения',
'newlevel': 'новый уровень',
'justbeforesave': 'Смело направляйте любые отзывы, предложения и сообщения&nbsp;об&nbsp;ошибках на&nbsp;<a href="/ruwiki/wiki/MediaWiki_talk:Script/Voting.js">страницу&nbsp;обсуждения</a>.',
'savebutton': 'Сохранить',
'saveprog': '<b>Не закрывайте страницу</b> до завершения сохранения.',
'summary': '\u200Bг\u200Bол\u200Bос\u200B',
'thankyou': '<h3>Спасибо за участие в выборах!</h3><p>Вы сможете изменить ваши голоса до конца голосования.</p><p>Только что отданные голоса можно посмотреть на <a href="/ruwiki/wiki/Special:Mycontributions">странице вклада</a>.</p>'
}
var cand = 'Alogrin ! AndyVolykhov ! Blacklake ! D.bratchuk ! Dima io ! Drbug ! INSAR ! Levg ! Makakaaaa ! Nikitin.ilya ! ShinePhantom ! VasilievVV ! Зелев Андрей ! Юрий Педаченко'.split(' ! ')
var wgAPIPath = wgServer + wgScriptPath + '/api.php?format=json&'
var votes = {}
var criteriaMatch
var saving = null
var aj = sajax_init_object()
var aj2 = sajax_init_object()
var token
// drag-n-drop
var coo
var cooD
var drag, dragGhost, dragItem, dragOriginal
var dragHere
function votingStart() {
importStylesheetURI('//ru.wikipedia.org/ruwiki/w/index.php?title=MediaWiki:Voting7.css&action=raw&ctype=text/css')
btn.disabled = true
btn.style.display = 'none'
status.innerHTML = loc.statuscriteria
aj2.onreadystatechange = votingStartContinue
aj2.open('GET', wgAPIPath + 'action=query&list=users&usprop=registration|editcount&prop=revisions&rvprop=content&ususers=' +
en(wgUserName) + '&titles=' + en(conf.votepath + '^'), true)
aj2.send('')
}
function votingStartContinue() {
if (aj2.readyState != 4) return
if (aj2.status != 200) { // temporary problems?
votingStart()
return
}
query = eval('(' + aj2.responseText + ')').query
userinfo = query.users[0]
if ((// a valid voter must be a non-anonymous user
userinfo.missing !== undefined ||
// who is not blocked
userinfo.blockedby ||
// whose editcount is at least conf.criteria.count
userinfo.editcount < conf.criteria.count ||
// and who is registered no later than conf.criteria.registration
// "null" means "before 2005-12-29", user creation was not logged before then
(userinfo.registration !== null && userinfo.registration > conf.criteria.registration)) &&
// exemptions
('\n' + ch(query.pages).revisions[0]['*'] + '\n').indexOf('\n* %\n'.replace('%', wgUserName)) == -1)
votingCriteriaFail()
else {
criteriaMatch = 1
votingContinue()
}
}
function votingCriteriaFail() {
status.innerHTML = loc.criteriafail
btn.style.display = ''
btn.disabled = false
btn.value = loc.criteriafailbutton
btn.onclick = function(){criteriaMatch = 0; votingContinue()}
}
function votingContinue() {
status.innerHTML = loc.loadingvotes
btn.style.display = ''
btn.onclick = votingToken
btn.value = loc.savebutton
btn.disabled = true
// latest contributions in ns:4 + Schulze method page
aj.open('GET', wgAPIPath + 'action=query' +
'&list=usercontribs&ucnamespace=4&uclimit=500' +
'&ucuser=' + en(wgUserName) +
(tstamp(conf.start) ? '&ucend=' + tstamp(conf.start) : '') +
'&ucdir=older' +
'&fake=1&prop=revisions&rvprop=content' +
'&titles=' + en(conf.votepath+'$|')
+ en(conf.votepath+'%|')
+ en(conf.votepath+'=') ,
true)
aj.onreadystatechange = votingDraw
aj.send('')
}
function votingDraw() {
if (aj.readyState != 4) return
if (aj.status != 200) { // temporary problems?
votingContinue()
return
}
btn.disabled = !criteriaMatch
status.innerHTML = ''
var query = eval('(' + aj.responseText + ')')
query = query.query
var co = query.usercontribs
for (var k in query.pages) {
spl = query.pages[k].revisions[0]['*'].split('\n')
switch (query.pages[k].title.match(/(.)$/)[1]) {
case '$':
var sc = spl
break
case '%':
var reserve = spl
break
case '=':
var limited = spl
break
}
}
// 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--) {
var m = co[i].title.indexOf(conf.votepath) == 0
if (m) m = co[i].title.match(/\/([+-])\/(.*?)$/)
if (m) votes[m[2]] = { 'orig' : m[1]=='+' ? +1 : -1,
'value': 0 }
}
// retrieving Schulze method page
// we assume that the bot will immediately revert not-well-formed edits
for (i=0; i<sc.length; i++) {
if (sc[i].indexOf('* ' + wgUserName + ' | ') == 0) {
votes._s = {'reading': true}
var j = 0
continue
}
if (!votes._s) continue
if (sc[i].indexOf('* ') == 0) votes._s.reading = undefined
if (votes._s.reading) {
j++
var m = sc[i].match(/^\*(.) (.*)/)
m[2] = m[2].split(' | ')
votes._s[m[1]=='#'?j:0] = m[2]
}
}
if (!votes._s) {
votes._s = {0: cand}
}
if (!votes._s[1]) votes._[1] = []
if (!votes._s[0]) votes._[0] = []
if (votes._s.reading) votes._s.reading = undefined
// retrieving "reserve" voting
votes._r = {}
for (i = 0; i < cand.length; ++i)
votes._r[cand[i]] = { 'orig' : 0, 'value': 0 }
reading = false
for (i=0; i<reserve.length; i++) {
if (reserve[i].indexOf('* ' + wgUserName + ' | ') == 0) {
reading = true
continue
}
if (!votes._r) continue
if (reserve[i].indexOf('* ') == 0) reading = false
if (reading) {
m = reserve[i].match(/^\*: (.) (.*)$/)
votes._r[m[2]].orig = votes._r[m[2]].value = ((m[1] == '.') ? 0 : (m[1] == '+' ? 1 : -1))
}
}
// retrieving "limited" voting
votes._l = {}
for (i = 0; i < cand.length; ++i)
votes._l[cand[i]] = { 'orig' : 0, 'value': 0 }
reading = false
for (i=0; i<limited.length; i++) {
if (limited[i].indexOf('* ' + wgUserName + ' | ') == 0) {
reading = true
continue
}
if (!votes._l) continue
if (limited[i].indexOf('* ') == 0) reading = false
if (reading) {
m = limited[i].match(/^\*: (.) (.*)$/)
votes._l[m[2]].orig = votes._l[m[2]].value = ((m[1] == '.') ? 0 : 1)
}
}


$(function () {
var containerEl = document.getElementById( 'voting-container' );
// drawing
if ( containerEl === null ) {
var div1 = el('div')
mw.log.warn( 'arbcomVoting: Aborted because no voting container was detected.' );
div1.id = 'voting-standard'
return;
div1.innerHTML = loc.votinghelp
}
var tab = el('table')
var wgUserName = mw.config.get( 'wgUserName' );
var tr, img, td1, td2, td3, lin
if ( wgUserName === null ) {
for (i in votes) {
mw.log.warn( 'arbcomVoting: Not logged in.' );
if (!i.match(/^_[srl]$/)) {
return;
tr = el('tr')
}
td1 = el('td')
img = el('img')
/******* COMMON ******* */
img.alt = '+'
function tstamp(t) {
img.width = img.height = 39
return !t.getUTCFullYear() ? null : // Safari + Chrome
img.src = ico.up + (votes[i].orig == 1 ? ico.suppinact : ico.supp)
(t.getUTCFullYear()+":0"+(t.getUTCMonth()+1)+":0"+t.getUTCDate()+":0"+
lin = el('a')
t.getUTCHours() +":0"+ t.getUTCMinutes() +":0"+t.getUTCSeconds())
lin.href = '#'
.replace(/:0?(\d\d)/g, '$1');
lin.title = '+'
}
lin.appendChild(img)
function ch(o) { for (var i in o) { return o[i] } }
td1.appendChild(lin)
td2 = el('td')
img = el('img')
img.alt = '−'
img.width = img.height = 39
img.src = ico.up + (votes[i].orig == -1 ? ico.oppinact : ico.opp)
lin = el('a')
lin.href = '#'
lin.title = '-'
lin.appendChild(img)
td2.appendChild(lin)
td3 = el('td')
td3.className = 'talklink'
lin = el('a')
lin.href = wgServer + wgScript + '?title=' + encodeURI(conf.talkpath + i)
lin.target = '_blank'
lin.innerHTML = i
td3.appendChild(lin)
tr.appendChild(td1)
tr.appendChild(td2)
tr.appendChild(td3)
tab.appendChild(tr)
}
}
div1.appendChild(tab)
// IE has problems inserting the table
div1.innerHTML += ''
var imgs = div1.getElementsByTagName('img')
for (i=0; i<imgs.length; i++)
imgs[i].parentNode.onclick = oncl
dragHere = el('span')
dragHere.id = 'voting-draghere'
dragHere.innerHTML = loc.draghere
var divetc = el('div')
divetc.id = 'voting-schulze'
var div2 = el('div')
div2.id = 'voting-etc-schulze'
div2.innerHTML = loc.etchelp1
var ul = el('ul')
ul.id = 'schulze-list'
var gw, theid, can, first, level, spc
var c
for (i=1; i!=null; i==0 ? i=null : i++) {
gw = el('li')
gw.className = 'groundwork'
theid = el('div')
theid.className = 'li-id'
theid.innerHTML = !votes._s[i] ? i : (i==1 ? '' : i-1) + '…' + i
gw.appendChild(theid)
if (!votes._s[i]) i=0
level = el('li')
level.className = 'level'
if (i==0) level.id = 'voting-no-preference'
if (i==1 && votes._s[i].join('!') == '') {
level.appendChild(dragHere)
}
theid = el('div')
theid.className = 'li-id'
theid.innerHTML = i==0 ? loc.nopreference : i
level.appendChild(theid)
for (c=0; c<votes._s[i].length; c++) {
can = el('a')
can.onclick = function() { return false }
can.onmousedown = boxMouseDown
can.innerHTML = votes._s[i][c]
can.className = 'cand'
level.appendChild(can)
}
level.appendChild(spacer())
ul.appendChild(gw)
ul.appendChild(level)
}
div2.appendChild(ul)
divetc.appendChild(div2)
var div2 = el('div')
div2.id = 'voting-etc-reserve'
div2.innerHTML = loc.etchelp2
var tab = el('table')
var tr, img, td1, td2, td3, lin
for (i in votes._r) {
tr = el('tr')
td1 = el('td')
img = el('img')
img.alt = '+'
img.width = img.height = 19
img.src = ico.up + (votes._r[i].orig == 1 ? ico.suppinact : ico.supp)
lin = el('a')
lin.href = '#'
lin.title = '+'
lin.appendChild(img)
td1.appendChild(lin)


/******* VARIABLES *******/
td2 = el('td')
img = el('img')
img.alt = '−'
img.width = img.height = 19
img.src = ico.up + (votes._r[i].orig == -1 ? ico.oppinact : ico.opp)
lin = el('a')
lin.href = '#'
lin.title = '-'
lin.appendChild(img)
td2.appendChild(lin)


var data = require( './ondemand-arbcomVoting.json' );
td3 = el('td')
var wgNamespaceNumber = mw.config.get( 'wgNamespaceNumber' );
td3.className = 'talklink'
var votes = {};
lin = el('a')
var saving = null;
lin.href = wgServer + wgScript + '?title=' + encodeURI(conf.talkpath + i)
var api;
lin.target = '_blank'
var statusEl;
lin.innerHTML = i
var btnEl;


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


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

div2.appendChild(tab)
api.get({
var imgs = div2.getElementsByTagName('img')
'action': 'query',
for (i=0; i<imgs.length; i++) {
'list': 'users',
imgs[i].src = imgs[i].src.replace(/39px/, '19px')
'usprop': ['registration', 'editcount'],
imgs[i].parentNode.onclick = onclr
'prop': 'revisions',
}
'rvprop': 'content',
divetc.appendChild(div2)
'ususers': wgUserName,
'titles': data.config.voteexceptionspath
}).done(votingStartContinue);
var div2 = el('div')
}
div2.id = 'voting-etc-limited'

div2.innerHTML = loc.etchelp3
function votingStartContinue(d) {
var count = 0
query = d.query;
var hol = el('div')
pages = query.pages;
hol.className = 'cand-list'
userinfo = query.users[0];
console.log(votes._l)
if ((// a valid voter must be a non-anonymous user
for (i in votes._l) {
typeof userinfo === 'undefined' || typeof userinfo.missing !== 'undefined' ||
one = el('a')
// who is not blocked
one.innerHTML = i
userinfo.blockedby ||
one.className = votes._l[i].orig ? 'cand cand-selected' : 'cand'
// whose editcount is at least data.config.criteria.count
count += votes._l[i].orig
userinfo.editcount < data.config.criteria.count ||
one.href = '#'
// and who is registered no later than data.config.criteria.registration
one.onclick = oncll
// "null" means "before 2005-12-29", user creation was not logged before then
hol.appendChild(one)
(userinfo.registration !== null && userinfo.registration > data.config.criteria.registration)) &&
}
// exemptions
var cnt = el('div')
('\n' + ch(pages).revisions[0]['*'] + '\n').indexOf('\n* %\n'.replace('%', wgUserName)) == -1
cnt.id = 'voting-limited-count'
) {
cnt.innerHTML = count
votingCriteriaFail();
if (count == 5) cnt.className = 'ok'
} else {
div2.appendChild(cnt)
votingContinue();
div2.appendChild(hol)
}
divetc.appendChild(div2)
}

function votingCriteriaFail() {
var div2 = el('div')
statusEl.textContent = '';
div2.innerHTML = loc.etchelp4
$( '#voting-msg-criteriafail' ).show();
}
divetc.appendChild(div2)

function votingContinue() {
if (navigator.appName=='Microsoft Internet Explorer') {
statusEl.textContent = data.strings.loadingvotes;
// troubles with scopes
// latest contributions in ns:4
document.onmousemove = mouseMove
api.get({
document.onmouseup = mouseUp
'action': 'query',
} else {
'list': 'usercontribs',
hookEvent('mousemove', mouseMove)
'ucnamespace': wgNamespaceNumber,
hookEvent('mouseup', mouseUp)
'uclimit': 500,
}
'ucuser': wgUserName,
'ucend': tstamp( data.config.start ),
status.innerHTML = loc.justbeforesave
'ucdir': 'older'
status.parentNode.insertBefore(div1, status)
}).done(votingDraw);
status.parentNode.insertBefore(divetc, status)
}
}
function votingToken() {
function onClick( e ) {
aj = sajax_init_object()
e.preventDefault();
aj.onreadystatechange = votingTokenContinue
var button = e.currentTarget;
aj.open('GET', wgAPIPath + 'action=query&prop=info&intoken=edit&titles=42', true)
var span = button.querySelector( 'span' );
aj.send('')
var tr = button.parentNode.parentNode;
}
var candidate = button.dataset.candidate;
function votingTokenContinue() {
var type = button.dataset.type;
if (aj.readyState != 4) return
var active = Boolean( button.getAttribute( 'aria-checked' ) );
if (aj.readyState == 4 && aj.status != 200) {
var option = data.options[ type ];
votingToken()
return
// Update other button first
}
var otherButton = tr.querySelector( `.vote-button[aria-checked="true"]` );
token = eval('(' + aj.responseText + ')')
var otherType = otherButton && otherButton.dataset.type;
token = ch(token.query.pages).edittoken
if ( otherButton ) {
votingSave()
var otherVote = otherType === 'support' ? 1 : -1;
}
otherButton.setAttribute( 'aria-checked', false );
function votingSave() {
}
if (saving == null) {
var div = el('div')
// Change vote
div.id = 'voting-saving'
var vote = type === 'support' ? 1 : -1;
div.innerHTML = '<div id="voting-progress"><div id="voting-progress-progress" style="width:0%">&nbsp;</div></div>' + loc.saveprog
var sameVote = votes[ candidate ].value === vote;
status.parentNode.appendChild(div)
votes[ candidate ].value = ( sameVote ? 0 : vote );
btn.disabled = true
button.setAttribute( 'aria-checked', !sameVote );
document.getElementById('voting-schulze' ).style.visibility = 'hidden'
document.getElementById('voting-standard').style.visibility = 'hidden'
// Update table row
tr.className = !sameVote ? 'vote-' + type : '';
saving = {'cursor': 0, 'pages': []}
}
for (var i in votes) {
function addVoteButton( candidate, type, previousVote ) {
if (votes[i].value != 0 && votes[i].orig != votes[i].value) {
var option = data.options[ type ];
saving.pages[saving.cursor++] = {'text': '\n# [[user:' + wgUserName + '|' + wgUserName + ']] ~' + '~~' + '~~\n',
var didVote = previousVote === ( type === 'support' ? 1 : -1 );
'page': conf.votepath + (votes[i].value==1?'+':'-') + '/' + i}
var votedOpposite = previousVote === ( type === 'support' ? -1 : 1 );
}
var voteType = votedOpposite ? 'changevote' : 'novote';
}

var text, text2, changed
var button = document.createElement( 'button' );
var ms, mi
button.className = 'vote-button skin-invert';
var li = document.getElementById('schulze-list').getElementsByTagName('li')
button.setAttribute( 'role', 'switch' );
button.setAttribute( 'type', 'button' );
text = text2 = '\n* ' + wgUserName + ' | ~' + '~~' + '~~\n'
button.setAttribute( 'data-candidate', candidate );
for (i=0; i<li.length; i++) {
button.setAttribute( 'data-type', type );
ms = li[i].getElementsByTagName('a')
if ( didVote ) {
if (ms.length) {
button.setAttribute( 'data-voted', true );
mi = []
}
for (var j=0; j<ms.length; j++) mi[mi.length] = ms[j].innerHTML
button.setAttribute( 'aria-checked', didVote );
text += (li[i].id == 'voting-no-preference' ? '*: ' : '*# ') + mi.join(' | ') + '\n'
button.addEventListener( 'click', onClick );
}
}
var span = document.createElement( 'span' );
span.textContent = option[ voteType ];
for (i=1; i!=null; i==0 ? i=null : i++) {
if ( votedOpposite ) {
if (!votes._s[i]) i=0
span.textContent = span.textContent.replace( '%candidate%', candidate );
if (votes._s[i].join('.')) text2 += (i==0 ? '*: ' : '*# ') + votes._s[i].join(' | ') + '\n'
}
}
button.title = span.textContent;
if (text != text2) {
button.appendChild( span );
saving.pages[saving.cursor++] = {'page': conf.votepath + '$',
return button;
'text': text}
}
}

function votingDraw(d) {
text = '\n* ' + wgUserName + ' | ~' + '~~' + '~~\n'
statusEl.textContent = '';
changed = false
btnEl.setLabel(data.strings.savebutton);
for (var i in votes._l) {
btnEl.setDisabled(false);
text += '*: ' + (votes._l[i].value ? '+' : '.') + ' ' + i + '\n'
btnEl.off('click').on('click', votingSave);
if (votes._l[i].orig != votes._l[i].value)
btnEl.$element.css( 'display', '' );
changed = true

}
var co = ( d.query && d.query.usercontribs ) || [];
if (changed) {

console.log(text)
// retrieving votes, according to contributions, only latest ones are valid
saving.pages[saving.cursor++] = {'page': conf.votepath + '=',
for ( var i = 0; i < data.candidates.length; i++ ) {
'text': text}
}
votes[ data.candidates [ i ] ] = { 'orig': 0, 'value': 0 };
}
for ( var i = co.length - 1; i >= 0; i-- ) {
text = '\n* ' + wgUserName + ' | ~' + '~~' + '~~\n'
changed = false
var match = false;
if ( co[ i ].title.startsWith( data.config.votepath ) ) {
var vd = {99: '-', 100: '.', 101: '+'}
match = co[ i ].title.match( /\/([+-])\/(.*?)$/ );
for (var i in votes._r) {
}
text += '*: ' + vd[votes._r[i].value + 100] + ' ' + i + '\n'
if ( match ) {
if (votes._l[i].orig != votes._l[i].value)
var hasSummary = co[ i ].comment === data.strings.summary;
changed = true
if ( hasSummary ) {
}
votes[ match[ 2 ] ] = {
if (changed) {
'orig': match[ 1 ] === '+' ? +1 : -1,
console.log(text)
'value': 0
saving.pages[saving.cursor++] = {'page': conf.votepath + '%',
};
'text': text}
}
}
}
}
saving.cursor = -5

if (saving.pages.length)
var div1 = document.createElement( 'div' );
votingSave()
div1.id = 'voting-standard';
else
var help = document.createElement( 'p' );
document.getElementById('voting-container').innerHTML = loc.thankyou
help.textContent = data.strings.votinghelp;
} else {
div1.appendChild( help );
if (saving.cursor != -5 && aj.readyState != 4) return
var tab = document.createElement( 'table' );
if (aj.readyState == 4 && aj.status != 200) {
tab.className = 'voting-table';
votingSave()

return
var caption = document.createElement( 'caption' );
}
caption.textContent = data.strings.votebutton;
if (saving.cursor == -5) saving.cursor = 0
tab.appendChild( caption );
if (saving.cursor == -1) return

for ( var candidate in votes ) {
document.getElementById('voting-progress-progress').style.width = 100*(saving.cursor+1)/saving.pages.length + '%'
if ( candidate.match( /^_[srl]$/ ) ) {
aj = sajax_init_object()
continue;
aj.open('POST', wgAPIPath + 'action=edit&notminor=1&unwatch=1&token=' + en(token) + '&summary=' + en(loc.summary) +
}
'&title=' + en(saving.pages[saving.cursor].page) +
var tr = document.createElement( 'tr' );
'&appendtext=' + en(saving.pages[saving.cursor].text), true)
aj.onreadystatechange = votingSave
var th = document.createElement( 'th' );
aj.send('')
th.setAttribute( 'scope', 'row' );
saving.cursor++
var lin = document.createElement('a');
if (!saving.pages[saving.cursor]) {
lin.href = mw.util.getUrl( data.config.talkpath + candidate );
saving.cursor = -1
lin.target = '_blank';
document.getElementById('voting-container').innerHTML = loc.thankyou
lin.textContent = candidate;
}
lin.title = data.strings.questionsTooltip.replace( '%candidate%', candidate );
}
th.appendChild( lin );
}
tr.appendChild( th );
// onclick() for round buttons
// Support button
function oncl() {
var td1 = document.createElement( 'td' );
var imgs = this.parentNode.parentNode.getElementsByTagName('img')
td1.appendChild( addVoteButton( candidate, 'support', votes[ candidate ].orig ) );
var link = this.parentNode.parentNode.getElementsByTagName('a')[2]
tr.appendChild( td1 );
var ca = link.innerHTML
var ti = this.title
// Oppose button
var vo = ti=='+'?1:-1
var td2 = document.createElement( 'td' );
votes[ca].value = (votes[ca].value==vo) ? 0 : vo
td2.appendChild( addVoteButton( candidate, 'oppose', votes[ candidate ].orig ) );
tr.appendChild( td2 );
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))
tab.appendChild( tr );
}
link.className = ['opp', '', 'supp'][votes[ca].value + 1]
div1.appendChild(tab);
return false

}
statusEl.textContent = '';
$( '#voting-msg-justbeforesave' ).show();
function onclr() {
statusEl.parentNode.insertBefore(div1, statusEl);
var imgs = this.parentNode.parentNode.getElementsByTagName('img')
}
var link = this.parentNode.parentNode.getElementsByTagName('a')[2]
var ca = link.innerHTML
function showThankYou() {
var ti = this.title
containerEl.innerHTML = '';
var vo = ti=='+'?1:-1
$( '.voting-msg' ).hide();
votes._r[ca].value = (votes._r[ca].value==vo) ? 0 : vo
$( '#voting-msg-thankyou' ).show();
}
imgs[0].src = ico.up + (votes._r[ca].value== 1 ?ico.suppact:(votes._r[ca].orig== 1 ?ico.suppinact:ico.supp)).replace(/39px/, '19px')
imgs[1].src = ico.up + (votes._r[ca].value==-1 ?ico.oppact :(votes._r[ca].orig==-1 ?ico.oppinact :ico.opp)).replace(/39px/, '19px')
function votingSave() {
if (!saving) {
link.className = ['opp', '', 'supp'][votes._r[ca].value + 1]
var div = document.createElement('div');
div.className = 'voting-saving';
return false
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 );
function oncll() {
btnEl.setDisabled( true );
if (this.className == 'cand')
$( '#voting-standard' ).hide();
this.className = 'cand cand-selected'

else
saving = { 'cursor': 0, 'pages': [] };
this.className = 'cand'

for (var i in votes) {
var ca = this.innerHTML
votes._l[ca].value = (this.className == 'cand')?0:1
if (votes[i].value != 0 && votes[i].orig != votes[i].value) {
saving.pages[saving.cursor++] = {
'text': '\n# [[user:' + wgUserName + '|' + wgUserName + ']] ~' + '~~' + '~~\n',
var count = 0
'page': data.config.votepath + (votes[i].value==1?'+':'-') + '/' + i
for (var i in votes._l)
};
count += votes._l[i].value
}
}
document.getElementById('voting-limited-count').innerHTML = count

document.getElementById('voting-limited-count').className = (count==5)?'ok':''
saving.cursor = -5;
if (saving.pages.length) {
return false
votingSave();
}
} else {
showThankYou();
}
// used by drag-n-drop
} else {
if (saving.cursor === -5) {
function disableSelection(el, g){
saving.cursor = 0;
// doesn't work for Opera, thus onmousemove drops selection ranges
}
el.onselectstart = g ? function(){ return false } : null
if (saving.cursor === -1) {
el.unselectable = g ? "on" : "off"
return;
el.style.MozUserSelect = g ? "none" : ""
}
}

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

co = {'x': 0, 'y': 0}
api.postWithToken('edit', {
while (q.offsetParent){
'action': 'edit',
co.x += q.offsetLeft
'notminor': 1,
co.y += q.offsetTop
'unwatch': 1,
q = q.offsetParent
'assert': 'user',
}
'summary': data.strings.summary,
return co
'title': saving.pages[saving.cursor].page,
}
'appendtext': saving.pages[saving.cursor].text
function coords(e) {
}).done(votingSave);
if (e.pageX !== undefined)
saving.cursor++;
return {'x': e.pageX, 'y': e.pageY}
if (!saving.pages[saving.cursor]) {
else // IE (buggy for v7 while zoomed, but who cares?)
saving.cursor = -1;
return {
showThankYou();
'x': e.clientX + document.documentElement.scrollLeft,
}
'y': e.clientY + document.documentElement.scrollTop
}
}
}
}

function boxMouseDown() {
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'oojs', 'oojs-ui']).done(function () {
if (dragHere && dragHere.parentNode) dragHere.parentNode.removeChild(dragHere)
api = new mw.Api();
dragGhost = this
// Prepare dates
dragGhost.id = 'voting-ghost'
data.config.start = new Date(data.config.start);
dragOriginal = dragItem = this.parentNode
if ( typeof data.config.votelength !== 'undefined' ) {
dragItem.className += ' dragitem'
data.config.end = votingLength( data.config.start, data.config.votelength );
drag = document.createElement('a')
} else {
drag.innerHTML = this.innerHTML
data.config.end = new Date( data.config.end );
drag.id = 'voting-drag'
}
drag.className = 'cand'
// Prepare pages paths
data.config.pagepath = data.config.pagepath.replace('%config.path%', data.config.path);
cooD = absolutePosition(dragGhost)
data.config.talkpagepath = mw.util.getUrl( data.config.talkpagepath.replace('%config.path%', data.config.path) );
cooD.x -= coo.x
data.config.talkpath = data.config.talkpath.replace('%config.path%', data.config.path);
cooD.y -= coo.y
data.config.votepath = data.config.votepath.replace('%config.path%', data.config.path);
mouseMove({'pageX': coo.x, 'pageY': coo.y})
data.config.voteexceptionspath = data.config.voteexceptionspath.replace('%config.path%', data.config.path);
// Init
document.body.appendChild(drag)
if ( containerEl ) {
containerEl.innerHTML = '';
disableSelection(document.body, true)

disableSelection(drag, true)
statusEl = document.createElement('div');
}
statusEl.className = 'voting-status voting-msg';
function mouseMove(e) {
containerEl.appendChild( statusEl );
coo = coords(e || window.event)
if (!drag) return
$( '.voting-msg a' ).attr( 'target', '_blank' );
if (document.defaultView) // Opera: removing selection

document.defaultView.getSelection().removeAllRanges()
btnEl = new OO.ui.ButtonWidget( {
label: data.strings.votebutton,
drag.style.left = (coo.x + cooD.x) + "px"
flags: ['primary', 'progressive'],
drag.style.top = (coo.y + cooD.y) + "px"
id: 'voting-button'
} );
var lis = document.getElementById('schulze-list').getElementsByTagName('li')
btnEl.on('click', votingStart);
var dragCurrent
containerEl.appendChild(btnEl.$element.get(0));
}
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));
		}
	} );
});