User:Nardog/CatChangesViewer-core.js
mw.loader.using([
'mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter',
'oojs-ui-widgets', 'mediawiki.widgets', 'mediawiki.widgets.UserInputWidget',
'mediawiki.widgets.datetime', 'oojs-ui.styles.icons-interactions',
'oojs-ui.styles.icons-movement', 'mediawiki.interface.helpers.styles',
'user.options'
], function catChangesViewer(require) {
mw.loader.addStyleTag('.catchangesviewer .oo-ui-numberInputWidget{width:4em} .catchangesviewer .oo-ui-numberInputWidget input{text-align:center} .catchangesviewer .oo-ui-menuSelectWidget, .catchangesviewer .mw-widgets-datetime-dateTimeInputWidget{width:min-content} .catchangesviewer .mw-widget-userInputWidget{width:8em} .catchangesviewer .oo-ui-fieldLayout-align-inline{vertical-align:top} .catchangesviewer-table{white-space:nowrap} .catchangesviewer-addition{background:var(--background-color-success-subtle,#d5fdf4)} .catchangesviewer-removal{background:var(--background-color-error-subtle,#fee7e6)} .catchangesviewer-table td:empty::after{content:"\\a0"}');
let api = new mw.Api({
ajax: { headers: { 'Api-User-Agent': 'CatChangesViewer (https://en.wikipedia.org/wiki/User:Nardog/CatChangesViewer)' } }
});
let { formatTimeAndDate } = require('mediawiki.DateFormatter');
let msgKeys = mw.config.get('wgContentLanguage') === 'en' ? [] : [
'recentchanges-page-added-to-category',
'recentchanges-page-added-to-category-bundled',
'recentchanges-page-removed-from-category',
'recentchanges-page-removed-from-category-bundled'
];
let addedKeys = msgKeys.slice(0, 2), removedKeys = msgKeys.slice(2);
class CatChangesSearch {
constructor() {
this.options = getOptions();
this.params = Object.assign({
action: 'query',
list: 'recentchanges',
rctype: 'categorize',
rctitle: mw.config.get('wgPageName'),
rcprop: 'ids|timestamp|comment|user|flags',
formatversion: 2
}, this.options);
this.rcs = [];
this.latest = {};
this.curPage = 0;
this.titles = {};
this.newRcs = [];
}
load(isRefresh) {
isRefresh = isRefresh && !!this.rcs.length;
if (isRefresh) {
this.params.rcdir = 'newer';
this.params.rclimit = Math.min(limitInput.getNumericValue() + 1, 500);
this.params.rccontinue = this.rcs[0].timestamp.replace(/\D/g, '') + '|' + this.rcs[0].revid;
} else {
delete this.params.rcdir;
this.params.rclimit = limitInput.getNumericValue();
this.params.rccontinue = this.rccontinue;
}
this.setDisabledAll(true);
$error.empty();
let msgPromise = api.loadMessagesIfMissing(msgKeys);
api.get(this.params).then(response => {
if (!isRefresh) {
this.rccontinue = (response.continue || {}).rccontinue;
this.complete = !this.rccontinue && response.batchcomplete;
}
return msgPromise.then(() => {
this.processChanges(isRefresh, response.query.recentchanges);
});
}).catch(e => {
$error.text(((e || {}).error || {}).info || e);
}).always(() => {
this.setDisabledAll(false);
this.resetNavButtons();
this.updateButton();
refreshButton.toggle(true);
});
}
updateButton() {
button.setLabel(
this.rcs.length
? this.complete ? 'No more results' : 'Load more'
: this.complete ? 'No results' : 'Search'
).setDisabled(this.complete);
}
processChanges(isRefresh, rcs = []) {
if (isRefresh && (rcs[0] || {}).revid === this.rcs[0].revid) {
rcs.shift();
}
if (!rcs.length) return;
rcs.forEach(rc => {
if (!rc.comment) return;
let page = rc.comment.match(/\[\[:?([^|\]]+)\]\]/)[1];
if (rc.comment.includes(']] added to category')) {
rc.action = 'addition';
} else if (rc.comment.includes(']] removed from category')) {
rc.action = 'removal';
} else if (addedKeys.some(key => rc.comment === mw.msg(key, page))) {
rc.action = 'addition';
} else if (removedKeys.some(key => rc.comment === mw.msg(key, page))) {
rc.action = 'removal';
}
if (this.latest.hasOwnProperty(page)) {
if (isRefresh) {
this.latest[page].duplicate = true;
this.latest[page] = rc;
} else {
rc.duplicate = true;
}
} else {
this.latest[page] = rc;
}
this.rcs[isRefresh ? 'unshift' : 'push'](rc);
this.addRow(rc, page);
});
this.initNav();
this.queryTitles(
Object.entries(this.titles)
.filter(([k, v]) => !v.processed).map(([k]) => k)
);
}
initNav() {
let rcsToShow = hideAdditionsCheckbox.isSelected()
? this.rcs.filter(rc => rc.action !== 'addition')
: hideRemovalsCheckbox.isSelected()
? this.rcs.filter(rc => rc.action !== 'removal')
: this.rcs;
if (hideDuplicatesCheckbox.isSelected()) {
rcsToShow = rcsToShow.filter(rc => !rc.duplicate);
}
this.visibleRows = rcsToShow.map(rc => rc.$row[0]);
this.pageCount = Math.ceil(this.visibleRows.length / perPageNum) || 1;
let z = this.rcs.length > perPageNum
? perPageNum * this.pageCount - this.visibleRows.length
: this.rcs.length - this.visibleRows.length;
for (let i = 0; i < z; i++) {
this.visibleRows.push(
$('
);
}
if (!this.$table) {
this.$tbody = $('
this.$table = $('
').text('±'),
$(' | ').text('Date'),
$(' | ').text('Page'),
$(' | ').text('User'),
$(' | ').text('Bot')
) ), this.$tbody ); } this.setPage(); navLayout.toggle(true).$element.before(this.$table); } setPage(increment) { if (this.pageCount > 1) { if (increment === 'first') { this.curPage = 0; } else if (increment === 'last') { this.curPage = this.pageCount - 1; } else if (increment) { this.curPage += increment; if (this.curPage < 0) { this.curPage = this.pageCount - 1; } if (this.curPage > this.pageCount - 1) { this.curPage = 0; } } else if (this.curPage > this.pageCount - 1) { this.curPage = this.pageCount - 1; } } else { this.curPage = 0; } let start = this.curPage * perPageNum; this.$tbody.html( this.visibleRows.slice(start, start + perPageNum) ); navLabel.setLabel(this.curPage + 1 + ' / ' + this.pageCount); this.resetNavButtons(); } resetNavButtons() { firstButton.setDisabled(this.curPage === 0); prevButton.setDisabled(this.pageCount < 2); nextButton.setDisabled(this.pageCount < 2); lastButton.setDisabled(this.curPage === this.pageCount - 1); } setDisabledAll(disabled) { [ limitInput, filtersButton, userInput, untilInput, button, refreshButton, firstButton, prevButton, nextButton, lastButton, hideAdditionsCheckbox, hideRemovalsCheckbox, hideDuplicatesCheckbox ].forEach(widget => { widget.setDisabled(disabled); }); } addRow(rc, page) { let symbol = rc.action === 'addition' ? '+' : rc.action === 'removal' ? '−' : '?'; let $userLink = this.makeLink( (rc.anon || rc.temp ? 'Special:Contributions/' : 'User:') + rc.user, rc.user ); if (rc.temp) { $userLink.addClass('mw-tempuserlink').attr('data-mw-target', rc.user); this.hasTemps = true; } rc.$row = $(' |
---|---|---|---|---|
').text(symbol),
$(' | ').append(
$('').attr('href', mw.util.getUrl(page, { oldid: rc.revid })).text(formatTimeAndDate(new Date(rc.timestamp))), ' ', $('').addClass('mw-changeslist-links').append( $('').append( $('').attr('href', mw.util.getUrl(page, { diff: rc.revid })).text('diff') ), $('').append( $('').attr('href', mw.util.getUrl(page, { curid: rc.pageid, action: 'history' })).text('hist') ) ) ), $(' | ').append(this.makeLink(page)),
$(' | ').append(
$userLink, ' ', $('').addClass('mw-changeslist-links').append( $('').append( this.makeLink('User talk:' + rc.user, 'talk') ), !rc.anon && !rc.temp && $('').append( this.makeLink('Special:Contributions/' + rc.user, 'contribs') ) ) ), $(' | ').text(rc.bot ? 'Yes' : 'No')
); this.newRcs.push(rc); } makeLink(title, text) { let obj; if (this.titles.hasOwnProperty(title)) { obj = this.titles[title]; } else { obj = { links: [] }; this.titles[title] = obj; } let params = obj.red && { action: 'edit', redlink: 1 } || obj.redirect && { redirect: 'no' }; let $link = $('').attr({ href: mw.util.getUrl(obj.canonical || title, params), title: obj.canonical || title }).addClass(obj.classes).text(text || title); if (!obj.processed) { obj.links.push($link[0]); } return $link; } queryTitles(titles) { if (!titles.length) { this.fireHook(); return; } let curTitles = titles.slice(0, 50); curTitles.forEach(title => { this.titles[title].processed = true; }); api.post({ action: 'query', titles: curTitles, prop: 'info', inprop: 'linkclasses', inlinkcontext: mw.config.get('wgPageName'), formatversion: 2 }, { headers: { 'Promise-Non-Write-API-Action': 1 } }).always(response => { let query = response && response.query; if (!query) { this.fireHook(); return; } (query.normalized || []).forEach(entry => { if (!this.titles.hasOwnProperty(entry.from)) return; let obj = this.titles[entry.from]; obj.canonical = entry.to; this.titles[entry.to] = obj; }); (query.pages || []).forEach(page => { if (!this.titles.hasOwnProperty(page.title)) return; let obj = this.titles[page.title]; let classes = page.linkclasses || []; if (page.missing && !page.known) { classes.push('new'); obj.red = true; } else if (classes.includes('mw-redirect')) { obj.redirect = true; } if (classes.length) { obj.classes = classes; } }); curTitles.forEach(title => { let obj = this.titles[title]; let $links = $(obj.links).addClass(obj.classes); $links.attr('href', mw.util.getUrl( obj.canonical || title, obj.red && { action: 'edit', redlink: 1 } )); if (obj.canonical) { $links.attr('title', obj.canonical); } delete obj.links; }); this.queryTitles(titles.slice(50)); }); } fireHook() { if (!this.newRcs.length) return; let tempRows = this.newRcs.map(rc => rc.$row.clone()[0]); let $tempTable = $(' |