Skip to content

Instantly share code, notes, and snippets.

@ternera
Last active October 16, 2025 21:39
Show Gist options
  • Select an option

  • Save ternera/659afdb63a705d77450e28cbccf8e47a to your computer and use it in GitHub Desktop.

Select an option

Save ternera/659afdb63a705d77450e28cbccf8e47a to your computer and use it in GitHub Desktop.
Quickly vote on requests for deletion on Wikidata. Install by adding importScript("User:Ternera/RFDVoteHelper.js"); to your common.js file.
(function() {
'use strict';
if (window.RFDVoteHelper) {
return;
}
if (mw.config.get('wgPageName') !== 'Wikidata:Requests_for_deletions') {
return;
}
const VoteDialog = function(config) {
VoteDialog.super.call(this, config);
this.sectionNumber = config.sectionNumber;
};
OO.inheritClass(VoteDialog, OO.ui.ProcessDialog);
VoteDialog.static.name = 'WikidataRFDVote';
VoteDialog.static.title = $('<span>').html('RfD Quick Vote <span style="font-size:80%; color:#666;">v1.0</span>');
VoteDialog.static.size = 'medium';
VoteDialog.static.actions = [
{ action: 'cancel', icon: 'close', label: 'Cancel', flags: 'safe' },
{ action: 'submit', icon: 'check', label: 'Submit Vote', flags: ['primary', 'progressive'] }
];
VoteDialog.prototype.initialize = function() {
VoteDialog.super.prototype.initialize.apply(this, arguments);
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.voteTypeInput = new OO.ui.DropdownInputWidget({
options: [
{ data: 'delete', label: 'Delete' },
{ data: 'keep', label: 'Keep' },
{ data: 'comment', label: 'Comment' }
]
});
this.reasonInput = new OO.ui.MultilineTextInputWidget({
placeholder: 'Optional reason or comment...',
rows: 4,
autosize: true
});
const voteTypeField = new OO.ui.FieldLayout(this.voteTypeInput, {
label: 'Vote type',
align: 'top'
});
const reasonField = new OO.ui.FieldLayout(this.reasonInput, {
label: 'Reason/Comment',
align: 'top'
});
this.panel.$element.append(
voteTypeField.$element,
reasonField.$element
);
this.$body.append(this.panel.$element);
};
VoteDialog.prototype.getBodyHeight = function() {
return this.panel.$element.outerHeight(true) + 20;
};
VoteDialog.prototype.getActionProcess = function(action) {
if (action === 'submit') {
return new OO.ui.Process(async () => {
const submitButton = this.actions.get({ actions: 'submit' })[0];
submitButton.setDisabled(true);
try {
await this.submitVote();
} catch (error) {
submitButton.setDisabled(false);
const errorMsg = error.error ? error.error.info : (error.message || 'An unknown error occurred.');
mw.notify('Error: ' + errorMsg, { type: 'error' });
}
});
}
if (action === 'cancel') {
return new OO.ui.Process(() => { this.close(); });
}
return VoteDialog.super.prototype.getActionProcess.call(this, action);
};
VoteDialog.prototype.submitVote = async function() {
const voteType = this.voteTypeInput.getValue();
const reason = this.reasonInput.getValue().trim();
let voteText = ':';
if (voteType === 'delete') {
voteText += '{{Vote delete}}';
} else if (voteType === 'keep') {
voteText += '{{Keep}}';
} else if (voteType === 'comment') {
voteText += '{{comment}}';
}
if (reason) {
voteText += ' ' + reason;
}
voteText += ' ~~' + '~~';
const api = new mw.Api();
const data = await api.get({
action: 'parse',
page: 'Wikidata:Requests_for_deletions',
prop: 'wikitext',
section: this.sectionNumber
});
if (!data || !data.parse || !data.parse.wikitext) {
throw new Error('Could not fetch section content');
}
const currentText = data.parse.wikitext['*'];
const newText = currentText.trimEnd() + '\n' + voteText;
await api.postWithEditToken({
action: 'edit',
title: 'Wikidata:Requests_for_deletions',
section: this.sectionNumber,
text: newText,
summary: 'Voting on RfD: ' + voteType + ' (via [[User:Ternera/RFDVoteHelper.js|RFDVoteHelper]])'
});
mw.notify('Vote submitted successfully!', { type: 'success' });
this.close().then(() => location.reload());
};
const RFDVoteHelper = {
windowManager: null,
init: function() {
$('.mw-editsection').each(function() {
const $editSection = $(this);
const $editLink = $editSection.find('a[href*="&action=edit"]').first();
if (!$editLink.length) return;
const sectionMatch = $editLink.attr('href').match(/[?&]section=([^&]+)/);
if (!sectionMatch) return;
const section = sectionMatch[1];
if (section === 'T' || section === '0') return;
const $voteLink = $('<a>')
.attr('href', '#')
.text('vote')
.css('margin-left', '0.3em')
.click(function(e) {
e.preventDefault();
RFDVoteHelper.showVoteDialog(section);
});
$editSection.append(
$('<span>').addClass('mw-editsection-divider').text(' | '),
$('<span>').addClass('mw-editsection-bracket').text('['),
$voteLink,
$('<span>').addClass('mw-editsection-bracket').text(']')
);
});
},
showVoteDialog: function(sectionNumber) {
if (!this.windowManager) {
this.windowManager = new OO.ui.WindowManager();
document.body.appendChild(this.windowManager.$element[0]);
}
const dialog = new VoteDialog({ sectionNumber: sectionNumber });
this.windowManager.addWindows([dialog]);
this.windowManager.openWindow(dialog);
}
};
window.RFDVoteHelper = RFDVoteHelper;
$(function() {
mw.loader.using(['oojs-ui-core', 'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.api'], function() {
RFDVoteHelper.init();
});
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment