User:BZPN/MassRollback2.js: Difference between revisions
Jump to navigation
Jump to search
Content deleted Content added
No edit summary |
No edit summary |
||
| Line 15: | Line 15: | ||
createUI: function() { |
createUI: function() { |
||
const $container = $(` |
const $container = $(` |
||
<div id=\"mass-rollback-container\" class=\"mw-portlet\" style=\"display:none;\">\n <h3 class=\"mw-headline\" style=\"margin-top:0;\">Mass rollback tool</h3>\n\n <div id=\"stats-section\" class=\"mw-message-box mw-message-box-notice\" style=\"margin-bottom:1em;\">\n <h4 class=\"mw-ui-headline\" style=\"margin:0 0 .5em 0;\">User statistics</h4>\n <p>Edits: <strong id=\"total-edits\">-</strong></p>\n <p>First edit: <span id=\"first-edit\">-</span></p>\n <p>Recent edit: <span id=\"last-edit\">-</span> <button id=\"refresh-stats\" class=\"mw-ui-button mw-ui-progressive\">Refresh</button></p>\n </div>\n\n <div id=\"filters-section\" class=\"mw-portlet-body\" style=\"margin-bottom:1em;\">\n <h4 class=\"mw-ui-headline\">Filter edits</h4>\n <div class=\"mw-input\" style=\"margin-bottom:.5em;\">\n <label class=\"mw-label\" style=\"margin-right:.5em;\">Date from:</label>\n <input type=\"date\" id=\"start-date\" class=\"mw-ui-input\">\n <label class=\"mw-label\" style=\"margin:0 .5em;\">to:</label>\n <input type=\"date\" id=\"end-date\" class=\"mw-ui-input\">\n </div>\n <div class=\"mw-input\" style=\"margin-bottom:.5em;\">\n <label class=\"mw-label\" style=\"margin-right:.5em;\">Namespace:</label>\n <select id=\"namespace-filter\" multiple class=\"mw-ui-input\" style=\"min-width:150px;\">\n <option value=\"all\">All</option>\n <option value=\"0\">Main</option>\n <option value=\"1\">Talk</option>\n <option value=\"2\">User</option>\n <option value=\"3\">User talk</option>\n <option value=\"4\">Project</option>\n <option value=\"5\">Project talk</option>\n <option value=\"6\">File</option>\n <option value=\"7\">File talk</option>\n <option value=\"8\">MediaWiki</option>\n <option value=\"9\">MediaWiki talk</option>\n <option value=\"10\">Template</option>\n <option value=\"11\">Template talk</option>\n <option value=\"12\">Help</option>\n <option value=\"13\">Help talk</option>\n <option value=\"14\">Category</option>\n <option value=\"15\">Category talk</option>\n <option value=\"100\">Portal</option>\n <option value=\"101\">Portal talk</option>\n </select>\n </div>\n <div class=\"mw-input\" style=\"margin-bottom:.5em;\">\n <label class=\"mw-label\" style=\"margin-right:.5em;\">Edit size:</label>\n <select id=\"size-filter\" class=\"mw-ui-input\">\n <option value=\"\">Any</option>\n <option value=\"small\">Small (< 50 bytes)</option>\n <option value=\"medium\">Medium (50-500 bytes)</option>\n <option value=\"large\">Large (> 500 bytes)</option>\n </select>\n </div>\n <div class=\"mw-input\" style=\"margin-bottom:.5em;\">\n <label class=\"mw-label\" style=\"margin-right:.5em;\">Sort order:</label>\n <select id=\"sort-order\" class=\"mw-ui-input\">\n <option value=\"desc\">Latest first</option>\n <option value=\"asc\">Oldest first</option>\n </select>\n </div>\n <div class=\"mw-input\" style=\"margin-bottom:.5em;\">\n <button id=\"clear-filters\" class=\"mw-ui-button\">Clear filters</button>\n <button id=\"show-filtered\" class=\"mw-ui-button mw-ui-progressive\" style=\"margin-left:.5em;\">Show filtered edits</button>\n </div>\n </div>\n\n <div id=\"reason-section\" class=\"mw-portlet-body\" style=\"margin-bottom:1em;\">\n <h4 class=\"mw-ui-headline\">Rollback reason</h4>\n <select id=\"rollback-reason\" class=\"mw-ui-input\">\n <option value=\"\"></option>\n <option value=\"vandalism\">Vandalism</option>\n <option value=\"spam\">Spam</option>\n <option value=\"test\">Test rollback</option>\n <option value=\"teste\">Test edit</option>\n <option value=\"rules-violation\">Violation</option>\n <option value=\"other\">Other</option>\n </select>\n <input type=\"text\" id=\"custom-reason\" placeholder=\"Enter custom reason\" class=\"mw-ui-input\" style=\"display:none; margin-top:.5em; width:100%;\">\n </div>\n\n <div id=\"actions-section\" class=\"mw-portlet-body\" style=\"text-align:center;\">\n <button id=\"rollback-selected\" class=\"mw-ui-button mw-ui-progressive\" style=\"margin-right:.5em;\">Rollback selected</button>\n <button id=\"rollback-all\" class=\"mw-ui-button mw-ui-destructive\">Rollback all</button>\n </div>\n </div>\n\n <div id=\"filtered-edits-container\" class=\"mw-portlet\" style=\"display:none;\">\n <h4 class=\"mw-ui-headline\">Filtered edits</h4>\n <div class=\"mw-input\" style=\"margin-bottom:.5em;\">\n <input type=\"checkbox\" id=\"select-all\"> <label for=\"select-all\">Select all</label>\n </div>\n <table id=\"filtered-edits-table\" class=\"wikitable\" style=\"width:100%;\">\n <thead>\n <tr>\n <th>Revid</th>\n <th>Title</th>\n <th>Timestamp</th>\n <th>Namespace</th>\n <th>Size</th>\n <th>Select</th>\n </tr>\n </thead>\n <tbody>\n <!-- Wstawiane dynamicznie -->\n </tbody>\n </table>\n <div style=\"text-align:center; margin-top:.5em;\">\n <button id=\"rollback-selected-filtered\" class=\"mw-ui-button mw-ui-progressive\">Rollback selected filtered edits</button>\n </div>\n </div>\n `); |
|||
<div id="mass-rollback-container" class="mw-portlet" style="display:none;"> |
|||
<h3 class="mw-headline" style="margin-top:0;">Mass rollback tool</h3> |
|||
<div id="stats-section" class="mw-message-box mw-message-box-notice" style="margin-bottom:1em;"> |
|||
<h4 class="oo-ui-labelElement-label" style="margin:0 0 .5em 0;">User statistics</h4> |
|||
<p>Edits: <strong id="total-edits">-</strong></p> |
|||
<p>First edit: <span id="first-edit">-</span></p> |
|||
<p>Recent edit: <span id="last-edit">-</span> <button id="refresh-stats" class="oo-ui-buttonElement-button oo-ui-buttonElement-framed oo-ui-flaggedElement-progressive">Refresh</button></p> |
|||
</div> |
|||
<div id="filters-section" class="mw-portlet-body" style="margin-bottom:1em;"> |
|||
<h4 class="oo-ui-labelElement-label">Filter edits</h4> |
|||
<div class="mw-input" style="margin-bottom:.5em;"> |
|||
<label class="mw-label" style="margin-right:.5em;">Date from:</label> |
|||
<input type="date" id="start-date" class="oo-ui-inputWidget-input"> |
|||
<label class="mw-label" style="margin:0 .5em;">to:</label> |
|||
<input type="date" id="end-date" class="oo-ui-inputWidget-input"> |
|||
</div> |
|||
<div class="mw-input" style="margin-bottom:.5em;"> |
|||
<label class="mw-label" style="margin-right:.5em;">Namespace:</label> |
|||
<select id="namespace-filter" multiple class="oo-ui-inputWidget-input" style="min-width:150px;"> |
|||
<option value="all">All</option> |
|||
<option value="0">Main</option> |
|||
<option value="1">Talk</option> |
|||
<option value="2">User</option> |
|||
<option value="3">User talk</option> |
|||
<option value="4">Project</option> |
|||
<option value="5">Project talk</option> |
|||
<option value="6">File</option> |
|||
<option value="7">File talk</option> |
|||
<option value="8">MediaWiki</option> |
|||
<option value="9">MediaWiki talk</option> |
|||
<option value="10">Template</option> |
|||
<option value="11">Template talk</option> |
|||
<option value="12">Help</option> |
|||
<option value="13">Help talk</option> |
|||
<option value="14">Category</option> |
|||
<option value="15">Category talk</option> |
|||
<option value="100">Portal</option> |
|||
<option value="101">Portal talk</option> |
|||
</select> |
|||
</div> |
|||
<div class="mw-input" style="margin-bottom:.5em;"> |
|||
<label class="mw-label" style="margin-right:.5em;">Edit size:</label> |
|||
<select id="size-filter" class="oo-ui-inputWidget-input"> |
|||
<option value="">Any</option> |
|||
<option value="small">Small (< 50 bytes)</option> |
|||
<option value="medium">Medium (50-500 bytes)</option> |
|||
<option value="large">Large (> 500 bytes)</option> |
|||
</select> |
|||
</div> |
|||
<div class="mw-input" style="margin-bottom:.5em;"> |
|||
<label class="mw-label" style="margin-right:.5em;">Sort order:</label> |
|||
<select id="sort-order" class="oo-ui-inputWidget-input"> |
|||
<option value="desc">Latest first</option> |
|||
<option value="asc">Oldest first</option> |
|||
</select> |
|||
</div> |
|||
<div class="mw-input" style="margin-bottom:.5em;"> |
|||
<button id="clear-filters" class="oo-ui-buttonElement-button oo-ui-buttonElement-framed">Clear filters</button> |
|||
<button id="show-filtered" class="oo-ui-buttonElement-button oo-ui-buttonElement-framed oo-ui-flaggedElement-progressive" style="margin-left:.5em;">Show filtered edits</button> |
|||
</div> |
|||
</div> |
|||
<div id="reason-section" class="mw-portlet-body" style="margin-bottom:1em;"> |
|||
<h4 class="oo-ui-labelElement-label">Rollback reason</h4> |
|||
<select id="rollback-reason" class="oo-ui-inputWidget-input"> |
|||
<option value=""></option> |
|||
<option value="vandalism">Vandalism</option> |
|||
<option value="spam">Spam</option> |
|||
<option value="test">Test rollback</option> |
|||
<option value="teste">Test edit</option> |
|||
<option value="rules-violation">Violation</option> |
|||
<option value="other">Other</option> |
|||
</select> |
|||
<input type="text" id="custom-reason" placeholder="Enter custom reason" class="oo-ui-inputWidget-input" style="display:none; margin-top:.5em; width:100%;"> |
|||
</div> |
|||
<div id="actions-section" class="mw-portlet-body" style="text-align:center;"> |
|||
<button id="rollback-selected" class="oo-ui-buttonElement-button oo-ui-buttonElement-framed oo-ui-flaggedElement-progressive" style="margin-right:.5em;">Rollback selected</button> |
|||
<button id="rollback-all" class="oo-ui-buttonElement-button oo-ui-buttonElement-framed oo-ui-flaggedElement-destructive">Rollback all</button> |
|||
</div> |
|||
</div> |
|||
<div id="filtered-edits-container" class="mw-portlet" style="display:none;"> |
|||
<h4 class="oo-ui-labelElement-label">Filtered edits</h4> |
|||
<div class="mw-input" style="margin-bottom:.5em;"> |
|||
<input type="checkbox" id="select-all"> <label for="select-all">Select all</label> |
|||
</div> |
|||
<table id="filtered-edits-table" class="wikitable" style="width:100%;"> |
|||
<thead> |
|||
<tr> |
|||
<th>Revid</th> |
|||
<th>Title</th> |
|||
<th>Timestamp</th> |
|||
<th>Namespace</th> |
|||
<th>Size</th> |
|||
<th>Select</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody> |
|||
<!-- Wstawiane dynamicznie --> |
|||
</tbody> |
|||
</table> |
|||
<div style="text-align:center; margin-top:.5em;"> |
|||
<button id="rollback-selected-filtered" class="oo-ui-buttonElement-button oo-ui-buttonElement-framed oo-ui-flaggedElement-progressive">Rollback selected filtered edits</button> |
|||
</div> |
|||
</div> |
|||
`); |
|||
const $toggleButton = $(` |
const $toggleButton = $(` |
||
<button id="mass-rollback-toggle" class=" |
<button id=\"mass-rollback-toggle\" class=\"mw-ui-button mw-ui-progressive\" style=\"margin-bottom:.75em;\">\n Show/hide MassRollback\n </button>\n `); |
||
Show/hide MassRollback |
|||
</button> |
|||
`); |
|||
$toggleButton.on('click', function() { |
$toggleButton.on('click', function() { |
||
| Line 492: | Line 381: | ||
}; |
}; |
||
// Ładujemy |
// Ładujemy stabilne moduły MW; bez OOUI, aby uniknąć problemów z loaderem |
||
mw.loader.using([ |
mw.loader.using([ |
||
'mediawiki.api', |
'mediawiki.api', |
||
'mediawiki.util', |
'mediawiki.util', |
||
'mediawiki.notify', |
'mediawiki.notify', |
||
'mediawiki.ui', |
|||
// OOUI – zbiorcze moduły kompatybilne na większości wiki |
|||
' |
'mediawiki.ui.button' |
||
'oojs-ui.styles' |
|||
]).then(function () { |
]).then(function () { |
||
$(function () { |
$(function () { |
||
| Line 505: | Line 393: | ||
}); |
}); |
||
}).catch(function () { |
}).catch(function () { |
||
// Fallback: jeśli OOUI nie jest dostępne, nadal inicjalizuj narzędzie bez stylów OOUI |
|||
$(function () { |
$(function () { |
||
try { MassRollback.init(); } catch (e) { console.error(e); } |
try { MassRollback.init(); } catch (e) { console.error(e); } |
||
Revision as of 20:05, 12 September 2025
(function($, mw) {
'use strict';
const MassRollback = {
init: function() {
if (mw.config.get('wgCanonicalSpecialPageName') !== 'Contributions') {
return;
}
this.createUI();
this.bindEvents();
this.fetchUserStats();
},
createUI: function() {
const $container = $(`
<div id=\"mass-rollback-container\" class=\"mw-portlet\" style=\"display:none;\">\n <h3 class=\"mw-headline\" style=\"margin-top:0;\">Mass rollback tool</h3>\n\n <div id=\"stats-section\" class=\"mw-message-box mw-message-box-notice\" style=\"margin-bottom:1em;\">\n <h4 class=\"mw-ui-headline\" style=\"margin:0 0 .5em 0;\">User statistics</h4>\n <p>Edits: <strong id=\"total-edits\">-</strong></p>\n <p>First edit: <span id=\"first-edit\">-</span></p>\n <p>Recent edit: <span id=\"last-edit\">-</span> <button id=\"refresh-stats\" class=\"mw-ui-button mw-ui-progressive\">Refresh</button></p>\n </div>\n\n <div id=\"filters-section\" class=\"mw-portlet-body\" style=\"margin-bottom:1em;\">\n <h4 class=\"mw-ui-headline\">Filter edits</h4>\n <div class=\"mw-input\" style=\"margin-bottom:.5em;\">\n <label class=\"mw-label\" style=\"margin-right:.5em;\">Date from:</label>\n <input type=\"date\" id=\"start-date\" class=\"mw-ui-input\">\n <label class=\"mw-label\" style=\"margin:0 .5em;\">to:</label>\n <input type=\"date\" id=\"end-date\" class=\"mw-ui-input\">\n </div>\n <div class=\"mw-input\" style=\"margin-bottom:.5em;\">\n <label class=\"mw-label\" style=\"margin-right:.5em;\">Namespace:</label>\n <select id=\"namespace-filter\" multiple class=\"mw-ui-input\" style=\"min-width:150px;\">\n <option value=\"all\">All</option>\n <option value=\"0\">Main</option>\n <option value=\"1\">Talk</option>\n <option value=\"2\">User</option>\n <option value=\"3\">User talk</option>\n <option value=\"4\">Project</option>\n <option value=\"5\">Project talk</option>\n <option value=\"6\">File</option>\n <option value=\"7\">File talk</option>\n <option value=\"8\">MediaWiki</option>\n <option value=\"9\">MediaWiki talk</option>\n <option value=\"10\">Template</option>\n <option value=\"11\">Template talk</option>\n <option value=\"12\">Help</option>\n <option value=\"13\">Help talk</option>\n <option value=\"14\">Category</option>\n <option value=\"15\">Category talk</option>\n <option value=\"100\">Portal</option>\n <option value=\"101\">Portal talk</option>\n </select>\n </div>\n <div class=\"mw-input\" style=\"margin-bottom:.5em;\">\n <label class=\"mw-label\" style=\"margin-right:.5em;\">Edit size:</label>\n <select id=\"size-filter\" class=\"mw-ui-input\">\n <option value=\"\">Any</option>\n <option value=\"small\">Small (< 50 bytes)</option>\n <option value=\"medium\">Medium (50-500 bytes)</option>\n <option value=\"large\">Large (> 500 bytes)</option>\n </select>\n </div>\n <div class=\"mw-input\" style=\"margin-bottom:.5em;\">\n <label class=\"mw-label\" style=\"margin-right:.5em;\">Sort order:</label>\n <select id=\"sort-order\" class=\"mw-ui-input\">\n <option value=\"desc\">Latest first</option>\n <option value=\"asc\">Oldest first</option>\n </select>\n </div>\n <div class=\"mw-input\" style=\"margin-bottom:.5em;\">\n <button id=\"clear-filters\" class=\"mw-ui-button\">Clear filters</button>\n <button id=\"show-filtered\" class=\"mw-ui-button mw-ui-progressive\" style=\"margin-left:.5em;\">Show filtered edits</button>\n </div>\n </div>\n\n <div id=\"reason-section\" class=\"mw-portlet-body\" style=\"margin-bottom:1em;\">\n <h4 class=\"mw-ui-headline\">Rollback reason</h4>\n <select id=\"rollback-reason\" class=\"mw-ui-input\">\n <option value=\"\"></option>\n <option value=\"vandalism\">Vandalism</option>\n <option value=\"spam\">Spam</option>\n <option value=\"test\">Test rollback</option>\n <option value=\"teste\">Test edit</option>\n <option value=\"rules-violation\">Violation</option>\n <option value=\"other\">Other</option>\n </select>\n <input type=\"text\" id=\"custom-reason\" placeholder=\"Enter custom reason\" class=\"mw-ui-input\" style=\"display:none; margin-top:.5em; width:100%;\">\n </div>\n\n <div id=\"actions-section\" class=\"mw-portlet-body\" style=\"text-align:center;\">\n <button id=\"rollback-selected\" class=\"mw-ui-button mw-ui-progressive\" style=\"margin-right:.5em;\">Rollback selected</button>\n <button id=\"rollback-all\" class=\"mw-ui-button mw-ui-destructive\">Rollback all</button>\n </div>\n </div>\n\n <div id=\"filtered-edits-container\" class=\"mw-portlet\" style=\"display:none;\">\n <h4 class=\"mw-ui-headline\">Filtered edits</h4>\n <div class=\"mw-input\" style=\"margin-bottom:.5em;\">\n <input type=\"checkbox\" id=\"select-all\"> <label for=\"select-all\">Select all</label>\n </div>\n <table id=\"filtered-edits-table\" class=\"wikitable\" style=\"width:100%;\">\n <thead>\n <tr>\n <th>Revid</th>\n <th>Title</th>\n <th>Timestamp</th>\n <th>Namespace</th>\n <th>Size</th>\n <th>Select</th>\n </tr>\n </thead>\n <tbody>\n <!-- Wstawiane dynamicznie -->\n </tbody>\n </table>\n <div style=\"text-align:center; margin-top:.5em;\">\n <button id=\"rollback-selected-filtered\" class=\"mw-ui-button mw-ui-progressive\">Rollback selected filtered edits</button>\n </div>\n </div>\n `);
const $toggleButton = $(`
<button id=\"mass-rollback-toggle\" class=\"mw-ui-button mw-ui-progressive\" style=\"margin-bottom:.75em;\">\n Show/hide MassRollback\n </button>\n `);
$toggleButton.on('click', function() {
$('#mass-rollback-container').slideToggle();
});
$('#mw-content-text').prepend($toggleButton).prepend($container);
},
bindEvents: function() {
$('#rollback-reason').on('change', function() {
$('#custom-reason').toggle($(this).val() === 'other');
});
$('#clear-filters').on('click', function() {
$('#start-date').val('');
$('#end-date').val('');
$('#namespace-filter').val(['all']);
$('#size-filter').val('');
$('#sort-order').val('desc');
});
$('#refresh-stats').on('click', this.fetchUserStats.bind(this));
$('#show-filtered').on('click', this.displayFilteredEdits.bind(this));
$('#rollback-selected-filtered').on('click', this.rollbackSelectedFromFiltered.bind(this));
$('#rollback-selected').on('click', this.rollbackSelected.bind(this));
$('#rollback-all').on('click', this.rollbackAll.bind(this));
$(document).on('change', '#select-all', function() {
const isChecked = $(this).is(':checked');
$('#filtered-edits-table tbody input[type="checkbox"]').prop('checked', isChecked);
});
// Dodaj checkbox przy edycjach w widoku kontrybucji
$('li[data-mw-revid]').each(function() {
const $li = $(this);
const revid = $li.data('mw-revid');
const parentid = $li.data('mw-prev-revid');
const title = $li.find('.mw-contributions-title').text().trim();
const $checkbox = $('<input>', {
type: 'checkbox',
class: 'rollback-checkbox',
'data-revid': revid,
'data-parentid': parentid,
'data-title': title,
style: 'margin-right: 5px;'
});
$li.prepend($checkbox);
});
},
fetchUserStats: function() {
const userName = mw.config.get('wgRelevantUserName');
const api = new mw.Api();
// Poprawne pobieranie liczby edycji i pierwszej/ostatniej edycji
const editCountReq = api.get({
action: 'query',
list: 'users',
ususers: userName,
usprop: 'editcount'
});
const firstEditReq = api.get({
action: 'query',
list: 'usercontribs',
ucuser: userName,
uclimit: 1,
ucdir: 'newer',
ucprop: 'timestamp'
});
const lastEditReq = api.get({
action: 'query',
list: 'usercontribs',
ucuser: userName,
uclimit: 1,
ucdir: 'older',
ucprop: 'timestamp'
});
$.when(editCountReq, firstEditReq, lastEditReq).done(function(editCountData, firstData, lastData) {
try {
const editcount = editCountData[0].query.users[0].editcount;
$('#total-edits').text(editcount != null ? editcount : '-');
} catch (e) {
$('#total-edits').text('-');
}
try {
const firstList = firstData[0].query.usercontribs || [];
$('#first-edit').text(firstList.length ? new Date(firstList[0].timestamp).toLocaleDateString() : '-');
} catch (e) {
$('#first-edit').text('-');
}
try {
const lastList = lastData[0].query.usercontribs || [];
$('#last-edit').text(lastList.length ? new Date(lastList[0].timestamp).toLocaleDateString() : '-');
} catch (e) {
$('#last-edit').text('-');
}
}).fail(function() {
$('#total-edits').text('-');
$('#first-edit').text('-');
$('#last-edit').text('-');
});
},
getNamespaceName: function(ns) {
const nsMapping = {
0: 'Main',
1: 'Talk',
2: 'User',
3: 'User talk',
4: 'Project',
5: 'Project talk',
6: 'File',
7: 'File talk',
8: 'MediaWiki',
9: 'MediaWiki talk',
10: 'Template',
11: 'Template talk',
12: 'Help',
13: 'Help talk',
14: 'Category',
15: 'Category talk',
100: 'Portal',
101: 'Portal talk'
};
return nsMapping[ns] || ns;
},
applyFilters: function(contributions) {
const startDate = $('#start-date').val() ? new Date($('#start-date').val()) : null;
const endDate = $('#end-date').val() ? new Date($('#end-date').val()) : null;
let namespaceFilter = $('#namespace-filter').val() || [];
const sizeFilter = $('#size-filter').val();
const sortOrder = $('#sort-order').val();
if (namespaceFilter.includes('all')) {
namespaceFilter = [];
}
let filtered = contributions.filter(contrib => {
const contribDate = new Date(contrib.timestamp);
if (startDate && contribDate < startDate) return false;
if (endDate && contribDate > endDate) return false;
if (namespaceFilter.length > 0 && !namespaceFilter.includes(contrib.ns.toString())) return false;
if (sizeFilter) {
const size = contrib.size || 0;
switch (sizeFilter) {
case 'small': return size < 50;
case 'medium': return size >= 50 && size <= 500;
case 'large': return size > 500;
}
}
return true;
});
filtered.sort((a, b) => {
return sortOrder === 'asc'
? new Date(a.timestamp) - new Date(b.timestamp)
: new Date(b.timestamp) - new Date(a.timestamp);
});
return filtered;
},
displayFilteredEdits: function() {
const userName = mw.config.get('wgRelevantUserName');
const api = new mw.Api();
api.get({
action: 'query',
list: 'usercontribs',
ucuser: userName,
uclimit: 'max',
ucprop: 'ids|title|timestamp|size|ns|parentid'
}).done((data) => {
const allContributions = data.query.usercontribs;
const filtered = this.applyFilters(allContributions);
const $tbody = $('#filtered-edits-table tbody');
$tbody.empty();
if (filtered.length === 0) {
$tbody.append('<tr><td colspan="6" style="text-align:center; padding:5px;">No edits match the selected filters.</td></tr>');
} else {
filtered.forEach(contrib => {
const row = `
<tr>
<td style="padding: 5px; border: 1px solid #ccc;">${contrib.revid}</td>
<td style="padding: 5px; border: 1px solid #ccc;">${contrib.title}</td>
<td style="padding: 5px; border: 1px solid #ccc;">${new Date(contrib.timestamp).toLocaleString()}</td>
<td style="padding: 5px; border: 1px solid #ccc;">${this.getNamespaceName(contrib.ns)}</td>
<td style="padding: 5px; border: 1px solid #ccc;">${contrib.size || 0}</td>
<td style="padding: 5px; border: 1px solid #ccc; text-align: center;">
<input type="checkbox" class="filtered-rollback-checkbox" data-revid="${contrib.revid}" data-parentid="${contrib.parentid}" data-title="${contrib.title}">
</td>
</tr>
`;
$tbody.append(row);
});
}
$('#filtered-edits-container').css("display", "block").slideDown();
});
},
confirmAction: function(message, count) {
return new Promise((resolve) => {
const $modal = $(`
<div style="position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 9999;">
<div style="background: #fff; padding: 20px; border-radius: 4px; max-width: 400px; width: 90%;">
<h4 style="margin-top:0;">Confirm Rollback</h4>
<p>${message}</p>
<p>Number of edits: ${count}</p>
<div style="text-align: right; margin-top: 15px;">
<button id="confirm-action" style="padding: 5px 10px; background-color: #007bff; color: #fff; border: none; border-radius: 3px; cursor: pointer; margin-right: 5px;">Confirm</button>
<button id="cancel-action" style="padding: 5px 10px; background-color: #aaa; color: #fff; border: none; border-radius: 3px; cursor: pointer;">Cancel</button>
</div>
</div>
</div>
`);
$('body').append($modal);
$modal.find('#confirm-action').on('click', function() {
$modal.remove();
resolve(true);
});
$modal.find('#cancel-action').on('click', function() {
$modal.remove();
resolve(false);
});
});
},
// Nowa wersja: grupujemy edycje wg tytułu i dla każdej grupy wykrywamy ciąg kolejnych edycji.
performRollback: function(contributions) {
const userName = mw.config.get('wgRelevantUserName');
const reason = $('#rollback-reason').val() === 'other'
? $('#custom-reason').val()
: $('#rollback-reason option:selected').text();
const summary = `Reverted edit(s) by [[User:${userName}|${userName}]]: ${reason}`;
const api = new mw.Api();
// Grupujemy edycje wg tytułu strony
const groups = {};
contributions.forEach(contrib => {
if (!groups[contrib.title]) {
groups[contrib.title] = [];
}
groups[contrib.title].push(contrib);
});
const promises = [];
// Dla każdej strony – sortuj wg daty malejąco i wykryj ciąg edycji
for (let title in groups) {
let group = groups[title].sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
let latest = group[0];
// Rozpoczynamy ciąg od najnowszej edycji
let chain = [latest];
let currentParent = latest.parentid;
// Przeglądamy kolejne edycje dla tego samego tytułu
for (let i = 1; i < group.length; i++) {
const contrib = group[i];
// Jeżeli kolejna edycja jest dokładnie poprzednikiem poprzednio znalezionego elementu, dodajemy do ciągu
if (parseInt(contrib.revid, 10) === parseInt(currentParent, 10)) {
chain.push(contrib);
currentParent = contrib.parentid;
} else {
break;
}
}
// Wykonaj rollback tylko, jeśli mamy przynajmniej jedną edycję (zawsze prawda)
promises.push(api.postWithToken('csrf', {
action: 'edit',
undo: latest.revid,
undoafter: currentParent,
title: title,
summary: summary
}));
}
return Promise.all(promises);
},
rollbackSelected: async function() {
// Pobieramy zaznaczone edycje z listy kontrybucji
const selected = $('.rollback-checkbox:checked').map(function() {
return {
title: $(this).data('title'),
revid: $(this).data('revid'),
parentid: $(this).data('parentid'),
// Wartość timestamp może nie być dostępna w tym widoku – można opcjonalnie dodać dodatkowy pobór danych
timestamp: $(this).closest('li').find('.mw-contributions-timestamp').text() || new Date().toISOString()
};
}).get();
if (selected.length === 0) {
mw.notify('No edits selected.', { type: 'warn' });
return;
}
const confirmed = await this.confirmAction('Are you sure you want to rollback the selected edits?', selected.length);
if (!confirmed) return;
try {
await this.performRollback(selected);
mw.notify(`Successfully rolled back ${selected.length} edits.`, { type: 'success' });
location.reload();
} catch (error) {
mw.notify('Error during rollback.', { type: 'error' });
console.error(error);
}
},
rollbackSelectedFromFiltered: async function() {
const selected = $('.filtered-rollback-checkbox:checked').map(function() {
return {
title: $(this).data('title'),
revid: $(this).data('revid'),
parentid: $(this).data('parentid'),
timestamp: new Date().toISOString() // Jeśli API już zwraca timestamp, można go tutaj użyć
};
}).get();
if (selected.length === 0) {
mw.notify('No filtered edits selected.', { type: 'warn' });
return;
}
const confirmed = await this.confirmAction('Are you sure you want to rollback the selected filtered edits?', selected.length);
if (!confirmed) return;
try {
await this.performRollback(selected);
mw.notify(`Successfully rolled back ${selected.length} filtered edits.`, { type: 'success' });
location.reload();
} catch (error) {
mw.notify('Error during rollback.', { type: 'error' });
console.error(error);
}
},
rollbackAll: async function() {
const userName = mw.config.get('wgRelevantUserName');
const api = new mw.Api();
try {
// Dodajemy "timestamp" by móc grupować edycje
const data = await api.get({
action: 'query',
list: 'usercontribs',
ucuser: userName,
uclimit: 'max',
ucprop: 'ids|title|parentid|timestamp'
});
const contributions = data.query.usercontribs;
if (contributions.length === 0) {
mw.notify('No edits to rollback.', { type: 'warn' });
return;
}
const confirmed = await this.confirmAction('Are you sure you want to rollback ALL edits?', contributions.length);
if (!confirmed) return;
await this.performRollback(contributions);
mw.notify(`Successfully rolled back all ${contributions.length} edits.`, { type: 'success' });
location.reload();
} catch (error) {
mw.notify('Error during rollback.', { type: 'error' });
console.error(error);
}
}
};
// Ładujemy stabilne moduły MW; bez OOUI, aby uniknąć problemów z loaderem
mw.loader.using([
'mediawiki.api',
'mediawiki.util',
'mediawiki.notify',
'mediawiki.ui',
'mediawiki.ui.button'
]).then(function () {
$(function () {
try { MassRollback.init(); } catch (e) { console.error(e); }
});
}).catch(function () {
$(function () {
try { MassRollback.init(); } catch (e) { console.error(e); }
});
});
})(jQuery, mediaWiki);