Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
$(function(){
    var aj
    var token
    var wgAPIPath = mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/api.php?format=json&'
    var e = function(x){return encodeURIComponent(x)}
    function ch(o) { for (var i in o) { return o[i] } }
 
    var i18n = {
        'en': {
            'comment': 'Comment',
            'vote': 'Vote for this image',
            'saved': 'Vote saved',
            'votes': 'Votes list',
            'alreadyvoted': 'You have already voted for this image',
            'alreadyvoted2': 'You have edited the corresponding page. Perhaps your edit was a vote.'
        },
        'de': { // by Saibo
            'comment': 'Kommentar',
            'vote': 'Dafür stimmen',
            'saved': 'Stimme gespeichert',
            'votes': 'Stimmenliste',
            'alreadyvoted': 'Du hast schon für dieses Bild gestimmt',
            'alreadyvoted2': 'Du hast die zugehörige Seite dieses Bildes schon bearbeitet; vielleicht war dies eine Stimmabgabe.'
        },
        'el': { // by Badseed
            'comment': 'Σχόλιο',
            'vote': 'Υπέρ',
            'saved': 'Η ψήφος αποθηκεύτηκε',
            'votes': 'Κατάλογος ψήφων',
            'alreadyvoted': 'Έχετε ήδη ψηφίσει αυτή την εικόνα',
            'alreadyvoted2': 'Επεξεργαστήκατε την αντίστοιχη σελίδα. Ίσως η επεξεργασία σας ήταν ψήφος.'
        },
        'fr': { // by Delhovlyn
            'comment': 'Commentaire',
            'vote': 'Soutenir',
            'saved': 'Vote enregistré',
            'votes': 'Liste des votes',
            'alreadyvoted': 'Vous avez déjà voté pour cette image',
            'alreadyvoted2': 'Vous avez édité la page correspondante. Peut-être que votre modification était un vote.'
        },
        'ru': {
            'comment': 'Комментарий',
            'vote': 'Проголосовать',
            'saved': 'Голос принят',
            'votes': 'Список голосов',
            'alreadyvoted': 'Вы уже проголосовали за это изображение',
            'alreadyvoted2': 'Вы редактировали соответствующую страницу. Возможно, эта правка была голосом.'
        },
        'uk': { // by Ahonc
            'comment': 'Коментар',
            'vote': 'Проголосувати',
            'saved': 'Голос прийнятий',
            'votes': 'Список голосів',
            'alreadyvoted': 'Ви вже голосували за це зображення',
            'alreadyvoted2': 'Ви редагували відповідну сторінку. Можливо, це редагування було голосом.'
        }
    }
    i18n = i18n[mw.config.get('wgUserLanguage')] || i18n.en
 
    appendCSS('                           \
        .purgeyourcache {                 \
            display: none;                \
        }                                 \
        form.potyvote {                   \
            text-align: center;           \
        }                                 \
        form.potyvote input {             \
            width: 95%;                   \
        }                                 \
        form.potyvote input.go {          \
            width: auto;                  \
        }                                 \
        form.potyvote input.gr {          \
            color: #BBB;                  \
        }                                 \
        .poty-saved {                     \
            font-weight: bold;            \
            color: #4B0;                  \
            padding: 0.5em;               \
            text-align: center;           \
            font-size: smaller;           \
        }                                 \
        .poty-saving {                    \
            text-align: center;           \
        }                                 \
        .poty-edited {                    \
            padding: 0.2em;               \
            color: #D27F37;               \
            font-size: smaller;           \
            line-height: 1.3em;           \
        }                                 \
        .poty-saved-img img {             \
            opacity: 0.16;                \
        }                                 \
        .poty-votes {                     \
            padding: 0.2em;               \
            font-size: smaller;           \
            color: #666 !important;       \
            text-align: center;           \
            line-height: 2em;             \
            display: block;               \
        }                                 \
    ')
 
    galleries = function(data) {
        token = ch(data.query.pages).edittoken
         
        contribs = data.query.usercontribs
        previous = {}
        var match
        for (var i=0; i<contribs.length; i++) {
            if (match = contribs[i].title.match(POTYregexp))
                previous[match[1]] = contribs[i].comment.match(/^\+1\u200b/) ? 2 : 1
        }
 
        var gallery = document.getElementsByClassName('dshuf')[0].getElementsByClassName('gallerybox')
        if (!gallery) return
        
        
        var gallerytext
        for (var i=0; i<gallery.length; i++) { // i=0 contains the whole table
            gallerytext = getElementsByClassName(gallery[i], 'div', 'gallerytext')[0]
 
            if (!gallerytext) continue
            title = gallerytext.getElementsByTagName('a')[0].title
            href  = gallerytext.getElementsByTagName('a')[0].href
            if (previous[title.match(POTYregexp)[1]] == 2) {
                gallerytext.innerHTML = '<p class="poty-saved">%</p>'.replace(/%/, i18n.alreadyvoted)
                gallerytext.parentNode.className += ' poty-saved-img'
            } else {
                gallerytext.innerHTML = '<p><form class="potyvote" action="javascript:void(0)">\
                <div><input style="display:none" type="text" class="gr" value="%co" \
                onfocus="if(this.className==\'gr\'){this.value=\'\';this.className=\'\'}" \
                onblur=" if(this.value==\'\'){this.className=\'gr\';this.value=\'%co\'}" /></div>\
                <input class="go" title="%ti" type="submit" onclick="POTYsave(this)" value="↑ %su ↑" /></form>\
                <div class="poty-edited">%da</div></p>'
                .replace(/%co/g, i18n.comment)
                .replace(/%su/g, i18n.vote)
                .replace(/%ti/g, title.replace(/&/g, '&amp;')
                                      .replace(/\u0022/g, '&quot;')
                                      .replace(/\u0027/g, '&#39;'))
                .replace(/%da/g, previous[title.match(POTYregexp)[1]] == 1 ? i18n.alreadyvoted2 : '')
            }
            gallerytext.parentNode.innerHTML +=
                '<a class="poty-votes" href="%">$</a>'.replace(/%/, href).replace(/\$/, i18n.votes)
        }
    }


    window.POTYsave = function(button) {
        var co = button.parentNode.getElementsByTagName('input')[0].value
        if (button.parentNode.getElementsByTagName('input')[0].className) co = ''
 
        text = '\n# {{2010POTY/Vote|' + mw.config.get('wgUserName') + '}} ~~' + '~~'
    
        var p = button.parentNode.parentNode
        p.innerHTML = '<p class="poty-saving">...</p>'
        voteSuccess = function() {
            p.className = 'poty-saved'
            p.parentNode.parentNode.className += ' poty-saved-img'
            p.innerHTML = i18n.saved
        }
    
        $.post(wgAPIPath + 'action=edit&watchlist=nochange&section=2&token=' + e(token) +
                           '&summary=' + e('+1\u200B' + (co?': ':'') + co) +
                           '&title=' + e(button.title) +
                           '&appendtext=' + e(text), {}, voteSuccess)
    }
 
    init = function() {
        $.get(wgAPIPath + 'action=query&list=usercontribs&ucnamespace=4&uclimit=500' +
                          '&ucdir=older&prop=info&intoken=edit&titles=1' +
                          '&ucend=20110428000000&ucuser=' + e(mw.config.get('wgUserName')), {}, galleries)
    }
 
    init()
})