Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* global Set */
(function() {
'use strict';
var config = mw.config.get([
'wgCanonicalSpecialPageName',
'wgFormattedNamespaces',
'wgPageName',
'wgUserGroups'
]);
var ui;
if (
!/sysop|bot|soap|staff|bot-global|wiki-specialist|util/.test(config.wgUserGroups.join('\n')) ||
window.RemoveLegacyThreads && window.RemoveLegacyThreads.init
) {
return;
}
var RLT = {
BLANK_TIMEOUT: 1000,
RATELIMIT_TIMEOUT: 30000,
THREAD_REGEX: /(thread.*@|(blog|talk).*\/@comment-\d+)/i,
init: function(i18n, placement, dorui) {
this.api = new mw.Api();
this.i18n = i18n;
ui = dorui;
if (config.wgCanonicalSpecialPageName === 'Whatlinkshere') {
this.initWLH();
} else if (
config.wgCanonicalSpecialPageName === 'Blankpage' &&
config.wgPageName.toLowerCase().endsWith('removelegacythreads')
) {
this.initUI();
}
placement.util({
script: 'RemoveLegacyThreads',
element: 'tools',
type: 'append',
content: ui.li({
child: ui.a({
attrs: {
href: mw.util.getUrl('Special:BlankPage/RemoveLegacyThreads')
},
text: i18n.msg('title').plain()
})
})
});
},
initWLH: function() {
var target = $('#mw-htmlform-whatlinkshere-target input').val();
$('#mw-htmlform-whatlinkshere-target')
.closest('form')
.find('.mw-htmlform-submit-buttons')
.append(
' ',
ui.button({
attrs: {
title: this.i18n.msg('description-short').plain()
},
classes: ['wds-button'],
events: {
click: this.clickWLH.bind(this, target)
},
text: this.i18n.msg('title').plain()
}),
ui.span({
attrs: {
id: 'RemoveLegacyThreadsStatus'
}
})
);
},
clickWLH: function(page, event) {
event.preventDefault();
$(event.currentTarget).prop('disabled', 'disabled');
var $status = $('#RemoveLegacyThreadsStatus');
var usageArr;
var totalPages;
var currentlyDone = 0;
var errors = 0;
this.getUsage(page).then(function(usage) {
usageArr = Array.from(usage).filter(function(pageTitle) {
return pageTitle.match(this.THREAD_REGEX);
}, this);
if (usageArr.length === 0) {
$status.text(this.i18n.msg('no-usage').plain());
return;
}
var usageArrCopy = [].concat(usageArr);
totalPages = usageArr.length;
this.blankPages(usageArr, function(type, title) {
if (type === 'failure') {
console.error('[RemoveLegacyThreads] Failed on', title);
++errors;
}
if (++currentlyDone === totalPages) {
usageArrCopy.forEach(function(link) {
$('#mw-whatlinkshere-list > li > a[title="' + link + '"]').parent().remove();
});
$status.text(this.i18n.msg('done').plain());
} else {
$status.text(this.i18n.msg(
'status',
currentlyDone,
totalPages,
Math.floor(currentlyDone / totalPages * 100),
errors
).plain());
}
}.bind(this));
}.bind(this)).fail(function(error) {
$status.text(this.i18n.msg('error-usage').plain());
console.error('[RemoveLegacyThreads] Error while fetching usage', error);
}.bind(this));
},
initUI: function() {
$('#firstHeading').text(this.i18n.msg('title').plain());
$('#mw-content-text').html(ui.div({
children: [
ui.p({
html: this.i18n.msg('description-long').parse()
}),
ui.button({
classes: ['wds-button', 'RemoveLegacyThreadsStart'],
events: {
click: this.clickUI.bind(this)
},
text: this.i18n.msg('start').plain()
}),
ui.p({
attrs: {
id: 'RemoveLegacyThreadsStatus'
}
}),
ui.progress({
attrs: {
id: 'RemoveLegacyThreadsProgress',
max: 100,
value: 0
}
}),
ui.p({
text: this.i18n.msg('error-log').plain()
}),
ui.pre({
attrs: {
id: 'RemoveLegacyThreadsErrorLog'
}
})
]
}));
},
clickUI: function(event) {
$(event.currentTarget).prop('disabled', 'disabled');
var $status = $('#RemoveLegacyThreadsStatus');
var $progress = $('#RemoveLegacyThreadsProgress');
var $errorLog = $('#RemoveLegacyThreadsErrorLog');
var totalPages;
var currentlyDone = 0;
var errors = 0;
this.getAllThreads().then(function(threads) {
if (threads.length === 0) {
$status.text(this.i18n.msg('no-threads').plain());
return;
}
totalPages = threads.length;
this.blankPages(threads, function(type, title) {
if (type === 'failure') {
$errorLog.text(($errorLog.text() + '\n' + title).trim());
console.error('[RemoveLegacyThreads] Failed on', title);
++errors;
}
if (++currentlyDone === totalPages) {
$status.text(this.i18n.msg('done').plain());
$progress.prop('value', 100);
} else {
var progress = Math.floor(currentlyDone / totalPages * 100);
$status.text(this.i18n.msg('status', currentlyDone, totalPages, progress, errors).plain());
$progress.prop('value', progress);
}
}.bind(this));
}.bind(this)).fail(function(error) {
$status.text(this.i18n.msg('error-threads').plain());
console.error('[RemoveLegacyThreads] Error while fetching threads', error);
}.bind(this));
},
getUsage: function(page, cont, oldResults) {
var results = oldResults || [];
var list = ['backlinks', 'embeddedin'];
var options = {
action: 'query',
bltitle: page,
bllimit: 'max',
eititle: page,
eilimit: 'max',
formatversion: 2
};
if (cont) {
options.blcontinue = cont.blcontinue;
options.cmcontinue = cont.cmcontinue;
options.eicontinue = cont.eicontinue;
options.fucontinue = cont.fucontinue;
}
try {
var title = new mw.Title(page);
if (title.namespace === 14) {
list.push('categorymembers');
options.cmtitle = page;
options.cmlimit = 'max';
} else if (title.namespace === 6) {
options.prop = 'fileusage';
options.titles = page;
options.fulimit = 'max';
}
} catch (error) {
console.error('Error while parsing page title: ', error);
}
options.list = list;
return this.api.get(options).then(function(data) {
list.forEach(function(type) {
if (!data.query[type]) {
return;
}
results = results.concat(data.query[type].map(function(obj) {
return obj.title;
}));
});
if (data.query.pages && data.query.pages[0]) {
results = results.concat(data.query.pages[0].fileusage.map(function(obj) {
return obj.title;
}));
}
// eslint-disable-next-line dot-notation
var contData = data['continue'];
if (!contData) {
return new Set(results);
}
return this.getUsage(page, contData, results);
}.bind(this));
},
getAllThreads: function() {
return $.when(
// Talk
this.getAllNamespacePages(1),
// User blog comment
this.getAllNamespacePages(501),
// Thread
this.getAllNamespacePages(1201),
// Board Thread
this.getAllNamespacePages(2001)
).then(function(talk, blogComment, thread, boardThread) {
var filteredTalk = talk.filter(function(title) {
return title.replace(config.wgFormattedNamespaces[1] + ':', 'Talk:').match(this.THREAD_REGEX);
}, this);
return filteredTalk.concat(blogComment, thread, boardThread);
}.bind(this));
},
getAllNamespacePages: function(namespace, cont, oldResults) {
var results = oldResults || [];
var $promise = $.Deferred();
this.api.get({
action: 'query',
list: 'allpages',
aplimit: 'max',
apminsize: 1,
apnamespace: namespace,
apcontinue: (cont || {}).apcontinue,
formatversion: 2
}).then(function(data) {
results = results.concat(data.query.allpages.map(function(obj) {
return obj.title;
}));
// eslint-disable-next-line dot-notation
var contData = data['continue'];
if (contData) {
this.getAllNamespacePages(namespace, contData, results)
.then($promise.resolve.bind($promise));
} else {
$promise.resolve(results);
}
}.bind(this)).fail(function(code, info) {
if (
code === 'badvalue' &&
info &&
info.error &&
typeof info.error.info === 'string' &&
info.error.info.includes('apnamespace')
) {
// The wiki just doesn't have the namespace we're blanking.
$promise.resolve([]);
} else {
$promise.reject(code, info);
}
});
return $promise;
},
blankPage: function(page) {
return this.api.postWithEditToken({
action: 'edit',
text: '',
title: page,
summary: this.i18n.inContentLang().msg('summary').plain(),
bot: true,
minor: true
});
},
blankPages: function(pages, progress) {
var page = pages.shift();
if (!page) {
return;
}
this.blankPage(page).then(function() {
progress('success', page);
setTimeout(this.blankPages.bind(this, pages, progress), this.BLANK_TIMEOUT);
}.bind(this)).fail(function(code) {
if (code === 'ratelimited') {
pages.unshift(page);
setTimeout(this.blankPages.bind(this, pages, progress), this.RATELIMIT_TIMEOUT);
} else {
progress('failure', page);
setTimeout(this.blankPages.bind(this, pages, progress), this.BLANK_TIMEOUT);
}
}.bind(this));
},
whenHookFires: function(hook) {
var $promise = $.Deferred();
mw.hook(hook).add(function(lib) {
$promise.resolve(lib);
});
return $promise;
},
whenI18nLoads: function() {
var $promise = $.Deferred();
mw.hook('dev.i18n').add(function(i18nLib) {
i18nLib.loadMessages('RemoveLegacyThreads').then(function(i18n) {
$promise.resolve(i18n);
});
});
return $promise;
}
};
$.when(
RLT.whenI18nLoads(),
RLT.whenHookFires('dev.placement'),
RLT.whenHookFires('doru.ui'),
mw.loader.using([
'mediawiki.api',
'mediawiki.Title',
'mediawiki.util'
])
).then(RLT.init.bind(RLT));
window.RemoveLegacyThreads = $.extend(window.RemoveLegacyThreads, RLT);
importArticles({
articles: [
'u:dev:MediaWiki:I18n-js/code.js',
'u:dev:MediaWiki:Placement.js',
'u:dev:MediaWiki:Dorui.js',
'u:dev:MediaWiki:RemoveLegacyThreads.css'
]
});
})();