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

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
Содержимое удалено Содержимое добавлено
открываем основное голосование
поддержка тёмной темы
 
(не показаны 152 промежуточные версии 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': '2012-02-05T23:59:60Z'
},
'pagepath': 'Википедия:Выборы арбитров/Весна 2012/Голосование',
'talkpath': 'Википедия:Выборы арбитров/Весна 2012/Обсуждение/',
'votepath': 'Википедия:Выборы арбитров/Весна 2012/Голосование/',
'start': new Date('May 23 2012 00:00 +0000'),
'end': new Date('May 30 2012 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>\
<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/Обсуждение_Википедии:Выборы_арбитров/Весна_2012/Голосование">задать вопрос</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 = 'Biathlon ! Cemenarist ! DR ! Generous ! Michgrig ! Mstislavl ! RussianSpy ! VasilievVV ! Vladimir Solovjev ! Vlsergey ! Wanderer ! Дядя Фред ! Рулин'.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._s[1] = []
if (!votes._s[0]) votes._s[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];
for (i in votes._l) {
if ((// a valid voter must be a non-anonymous user
one = el('a')
typeof userinfo === 'undefined' || typeof userinfo.missing !== 'undefined' ||
one.innerHTML = i
// who is not blocked
one.className = votes._l[i].orig ? 'cand cand-selected' : 'cand'
userinfo.blockedby ||
count += votes._l[i].orig
// whose editcount is at least data.config.criteria.count
one.href = '#'
userinfo.editcount < data.config.criteria.count ||
one.onclick = oncll
// and who is registered no later than data.config.criteria.registration
hol.appendChild(one)
// "null" means "before 2005-12-29", user creation was not logged before then
}
(userinfo.registration !== null && userinfo.registration > data.config.criteria.registration)) &&
var cnt = el('div')
// exemptions
cnt.id = 'voting-limited-count'
('\n' + ch(pages).revisions[0]['*'] + '\n').indexOf('\n* %\n'.replace('%', wgUserName)) == -1
cnt.innerHTML = count
) {
if (count == 5) cnt.className = 'ok'
votingCriteriaFail();
div2.appendChild(cnt)
} else {
div2.appendChild(hol)
votingContinue();
divetc.appendChild(div2)
}
}

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

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

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

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

if (changed) {
var co = ( d.query && d.query.usercontribs ) || [];
saving.pages[saving.cursor++] = {'page': conf.votepath + '=',

'text': text}
// 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 };
text = '\n* ' + wgUserName + ' | ~' + '~~' + '~~\n'
}
changed = false
for ( var i = co.length - 1; i >= 0; i-- ) {
var vd = {99: '-', 100: '.', 101: '+'}
var match = false;
for (var i in votes._r) {
if ( co[ i ].title.startsWith( data.config.votepath ) ) {
text += '*: ' + vd[votes._r[i].value + 100] + ' ' + i + '\n'
match = co[ i ].title.match( /\/([+-])\/(.*?)$/ );
if (votes._l[i].orig != votes._l[i].value)
}
changed = true
if ( match ) {
}
var hasSummary = co[ i ].comment === data.strings.summary;
if (changed) {
if ( hasSummary ) {
saving.pages[saving.cursor++] = {'page': conf.votepath + '%',
votes[ match[ 2 ] ] = {
'text': text}
}
'orig': match[ 1 ] === '+' ? +1 : -1,
'value': 0
};
saving.cursor = -5
}
if (saving.pages.length)
}
votingSave()
}
else

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

if (saving.cursor == -1) return
var caption = document.createElement( 'caption' );
caption.textContent = data.strings.votebutton;
document.getElementById('voting-progress-progress').style.width = 100*(saving.cursor+1)/saving.pages.length + '%'
tab.appendChild( caption );
aj = sajax_init_object()

aj.open('POST', wgAPIPath + 'action=edit&notminor=1&unwatch=1&token=' + en(token) + '&summary=' + en(loc.summary) +
for ( var candidate in votes ) {
'&title=' + en(saving.pages[saving.cursor].page) +
if ( candidate.match( /^_[srl]$/ ) ) {
'&appendtext=' + en(saving.pages[saving.cursor].text), true)
continue;
aj.onreadystatechange = votingSave
}
aj.send('')
var tr = document.createElement( 'tr' );
saving.cursor++
if (!saving.pages[saving.cursor]) {
var th = document.createElement( 'th' );
saving.cursor = -1
th.setAttribute( 'scope', 'row' );
document.getElementById('voting-container').innerHTML = loc.thankyou
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 );
// onclick() for round buttons
th.appendChild( lin );
function oncl() {
tr.appendChild( th );
var imgs = this.parentNode.parentNode.getElementsByTagName('img')
var link = this.parentNode.parentNode.getElementsByTagName('a')[2]
// Support button
var ca = link.innerHTML
var td1 = document.createElement( 'td' );
var ti = this.title
td1.appendChild( addVoteButton( candidate, 'support', votes[ candidate ].orig ) );
var vo = ti=='+'?1:-1
tr.appendChild( td1 );
votes[ca].value = (votes[ca].value==vo) ? 0 : vo
// Oppose button
imgs[0].src = ico.up + (votes[ca].value== 1 ?ico.suppact:(votes[ca].orig== 1 ?ico.suppinact:ico.supp))
var td2 = document.createElement( 'td' );
imgs[1].src = ico.up + (votes[ca].value==-1 ?ico.oppact :(votes[ca].orig==-1 ?ico.oppinact :ico.opp))
td2.appendChild( addVoteButton( candidate, 'oppose', votes[ candidate ].orig ) );
tr.appendChild( td2 );
link.className = ['opp', '', 'supp'][votes[ca].value + 1]
tab.appendChild( tr );
return false
}
}
div1.appendChild(tab);
function onclr() {

var imgs = this.parentNode.parentNode.getElementsByTagName('img')
statusEl.textContent = '';
var link = this.parentNode.parentNode.getElementsByTagName('a')[2]
$( '#voting-msg-justbeforesave' ).show();
var ca = link.innerHTML
statusEl.parentNode.insertBefore(div1, statusEl);
var ti = this.title
}
var vo = ti=='+'?1:-1
votes._r[ca].value = (votes._r[ca].value==vo) ? 0 : vo
function showThankYou() {
containerEl.innerHTML = '';
imgs[0].src = ico.up + (votes._r[ca].value== 1 ?ico.suppact:(votes._r[ca].orig== 1 ?ico.suppinact:ico.supp)).replace(/39px/, '19px')
$( '.voting-msg' ).hide();
imgs[1].src = ico.up + (votes._r[ca].value==-1 ?ico.oppact :(votes._r[ca].orig==-1 ?ico.oppinact :ico.opp)).replace(/39px/, '19px')
$( '#voting-msg-thankyou' ).show();
}
link.className = ['opp', '', 'supp'][votes._r[ca].value + 1]
function votingSave() {
return false
if (!saving) {
}
var div = document.createElement('div');
div.className = 'voting-saving';
function oncll() {
div.textContent = data.strings.saveprog;
if (this.className == 'cand')
div.insertAdjacentHTML( 'afterbegin', '<div class="voting-progress"><div class="voting-progress-progress" style="width:0%">&nbsp;</div></div>' );
this.className = 'cand cand-selected'
statusEl.parentNode.appendChild( div );
else
btnEl.setDisabled( true );
this.className = 'cand'
$( '#voting-standard' ).hide();

var ca = this.innerHTML
saving = { 'cursor': 0, 'pages': [] };
votes._l[ca].value = (this.className == 'cand')?0:1

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

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

while (q.offsetParent){
document.querySelector( '.voting-progress-progress' ).style.width = 100*(saving.cursor+1)/saving.pages.length + '%';
co.x += q.offsetLeft

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

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

coo = coords(e || window.event)
statusEl = document.createElement('div');
if (!drag) return
statusEl.className = 'voting-status voting-msg';
if (document.defaultView) // Opera: removing selection
containerEl.appendChild( statusEl );
document.defaultView.getSelection().removeAllRanges()
$( '.voting-msg a' ).attr( 'target', '_blank' );
drag.style.left = (coo.x + cooD.x) + "px"

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