/**
* AdminNewsletterTools
* Administrators' newsletter related updates for a specific month
* @author DreamRimmer ([[en:User:DreanRimmer]])
*
* <nowiki>
*/
$(document).ready(function() {
function initializeAdminNewsTools() {
$('#mw-content-text > p').remove();
$('#firstHeading').text('AdminNewsTools');
var monthSelect = new OO.ui.DropdownWidget({
menu: {
items: [
new OO.ui.MenuOptionWidget({ data: '1', label: 'January' }),
new OO.ui.MenuOptionWidget({ data: '2', label: 'February' }),
new OO.ui.MenuOptionWidget({ data: '3', label: 'March' }),
new OO.ui.MenuOptionWidget({ data: '4', label: 'April' }),
new OO.ui.MenuOptionWidget({ data: '5', label: 'May' }),
new OO.ui.MenuOptionWidget({ data: '6', label: 'June' }),
new OO.ui.MenuOptionWidget({ data: '7', label: 'July' }),
new OO.ui.MenuOptionWidget({ data: '8', label: 'August' }),
new OO.ui.MenuOptionWidget({ data: '9', label: 'September' }),
new OO.ui.MenuOptionWidget({ data: '10', label: 'October' }),
new OO.ui.MenuOptionWidget({ data: '11', label: 'November' }),
new OO.ui.MenuOptionWidget({ data: '12', label: 'December' })
]
}
}),
yearInput = new OO.ui.NumberInputWidget({
value: new Date().getFullYear(),
min: 2005,
max: new Date().getFullYear() + 1,
step: 1
}),
sectionSelect = new OO.ui.DropdownWidget({
menu: {
items: [
new OO.ui.MenuOptionWidget({ data: 'all', label: 'All sections' }),
new OO.ui.MenuOptionWidget({ data: 'rights', label: 'Rights changes' }),
new OO.ui.MenuOptionWidget({ data: 'tech', label: 'Tech News' }),
new OO.ui.MenuOptionWidget({ data: 'centralized', label: 'CENT' }),
new OO.ui.MenuOptionWidget({ data: 'arbcom', label: 'ArbCom updates' }),
new OO.ui.MenuOptionWidget({ data: 'rfc', label: 'Recent RfCs' }),
new OO.ui.MenuOptionWidget({ data: 'misc', label: 'Miscellaneous' })
]
}
}),
checkButton = new OO.ui.ButtonWidget({
label: 'Check',
flags: ['primary', 'progressive']
}),
resultsContainer = $("<div>").hide();
var currentDate = new Date();
var currentMonth = String(currentDate.getMonth() + 1);
var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
var quickLinks = $('<p>').css({
'display': 'flex',
'justify-content': 'space-between',
'align-items': 'center',
'margin': '10px 0',
'padding': '10px 15px',
'background': '#f8f9fa',
'border': '1px solid #a2a9b1',
'border-radius': '3px'
}).html(`
<a href="https://en.wikipedia.org/wiki/Wikipedia:Administrators%27_newsletter/${currentDate.getFullYear()}/${currentDate.getMonth() + 1}" target="_blank">Help with ${monthNames[currentDate.getMonth()]} ${currentDate.getFullYear()} issue</a>
<span>•</span>
<a href="https://en.wikipedia.org/wiki/Wikipedia:Administrators%27_newsletter/Archive" target="_blank">Archive</a>
<span>•</span>
<a href="https://en.wikipedia.org/wiki/Wikipedia:Administrators%27_newsletter/Subscribe" target="_blank">Subscribe</a>
<span>•</span>
<a href="https://en.wikipedia.org/wiki/Wikipedia:Administrators%27_newsletter/Write" target="_blank">Write</a>
<span>•</span>
<a href="https://en.wikipedia.org/wiki/Wikipedia_talk:Administrators%27_newsletter" target="_blank">Discuss</a>
`);
monthSelect.getMenu().selectItemByData(currentMonth);
sectionSelect.getMenu().selectItemByData('all');
var labels = {
monthLabel: $('<p>').text('Month:').css('font-weight', 'bold'),
yearLabel: $('<p>').text('Year:').css('font-weight', 'bold'),
sectionLabel: $('<p>').text('Section:').css('font-weight', 'bold'),
description: $('<p>').html('The <a href="https://en.wikipedia.org/wiki/Wikipedia:Administrators%27_newsletter" target="_blank">administrators\' newsletter</a> is a monthly update containing relevant information on administrative issues aimed at <a href="https://en.wikipedia.org/wiki/WP:ADMIN" target="_blank">administrators</a>. It is intended to help keep administrators up to date with changes to Wikipedia that they may otherwise miss. You can view various Administrators\' newsletter related updates for a specific month here. Please choose a month and year to get results.')
};
$('#mw-content-text').append(
labels.description,
quickLinks,
$('<div>').css({
'background': '#f8f9fa',
'border': '1px solid #a2a9b1',
'padding': '15px',
'margin-bottom': '20px',
'border-radius': '3px'
}).append(
$('<h3>').text('Select Month, Year and Section'),
$('<div>').css({
'display': 'flex',
'gap': '15px',
'align-items': 'end',
'flex-wrap': 'wrap'
}).append(
$('<div>').append(labels.monthLabel, monthSelect.$element),
$('<div>').append(labels.yearLabel, yearInput.$element),
$('<div>').append(labels.sectionLabel, sectionSelect.$element),
$('<div>').append(checkButton.$element)
)
),
resultsContainer
);
function formatDate(date) {
const options = { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit', timeZone: 'UTC' };
return date.toLocaleDateString('en-GB', options).replace(',', '') + ' UTC';
}
function formatDateFromTimestamp(timestamp) {
const date = new Date(timestamp);
const options = { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit', timeZone: 'UTC' };
return date.toLocaleDateString('en-GB', options).replace(',', '') + ' UTC';
}
function parseWikilinks(text, isMeta = false) {
if (!text) return '';
const baseWikiUrl = isMeta ? 'https://meta.wikimedia.org' : 'https://en.wikipedia.org';
try {
text = text.replace(/\[([^\s\]]+)\s+([^\]]+)\]/g, function(match, url, displayText) {
try {
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//')) {
return `<a href="${url}" target="_blank">${displayText}</a>`;
}
return match;
} catch (e) {
return match;
}
});
text = text.replace(/\[([^\s\]]+)\]/g, function(match, url) {
try {
if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//')) {
return `<a href="${url}" target="_blank">${url}</a>`;
}
return match;
} catch (e) {
return match;
}
});
text = text.replace(/\[\[([^\]|]+)(\|([^\]]+))?\]\]/g, function(match, link, pipe, display) {
try {
const displayText = display || link;
let url;
if (link.startsWith('Special:')) {
if (link.startsWith('Special:Diff/')) {
const diffPart = link.replace('Special:Diff/', '');
const hashIndex = diffPart.indexOf('#');
const diffId = hashIndex !== -1 ? diffPart.substring(0, hashIndex) : diffPart;
const fragment = hashIndex !== -1 ? diffPart.substring(hashIndex) : '';
if (/^\d+$/.test(diffId)) {
url = `${baseWikiUrl}/w/index.php?diff=${diffId}${fragment}`;
} else {
return match;
}
} else if (link.startsWith('Special:Contributions/')) {
const user = link.replace('Special:Contributions/', '');
if (user.trim()) {
url = `${baseWikiUrl}/wiki/Special:Contributions/${encodeURIComponent(user)}`;
} else {
return match;
}
} else {
url = `${baseWikiUrl}/wiki/${encodeURIComponent(link)}`;
}
} else if (link.startsWith('User:') || link.startsWith('User talk:')) {
url = `${baseWikiUrl}/wiki/${encodeURIComponent(link)}`;
} else if (link.startsWith('Wikipedia:')) {
url = `${baseWikiUrl}/wiki/${encodeURIComponent(link)}`;
} else {
url = `${baseWikiUrl}/wiki/${encodeURIComponent(link)}`;
}
return `<a href="${url}" target="_blank">${displayText}</a>`;
} catch (e) {
return match;
}
});
return text;
} catch (e) {
return text;
}
}
function getWeeksInMonth(year, month) {
const weeks = [];
const startDate = new Date(year, month - 1, 1);
const endDate = new Date(year, month, 0);
let currentDate = new Date(startDate);
while (currentDate <= endDate) {
const weekNumber = getISOWeek(currentDate);
if (!weeks.includes(weekNumber)) {
weeks.push(weekNumber);
}
currentDate.setDate(currentDate.getDate() + 1);
}
return weeks;
}
function getISOWeek(date) {
const target = new Date(date.valueOf());
const dayNr = (date.getDay() + 6) % 7;
target.setDate(target.getDate() - dayNr + 3);
const jan4 = new Date(target.getFullYear(), 0, 4);
const dayDiff = (target - jan4) / 86400000;
return 1 + Math.ceil(dayDiff / 7);
}
function processLogEntries(entries, targetGroups) {
const granted = [];
const removed = [];
entries.forEach(entry => {
if (!entry.title) return;
const timestamp = entry.timestamp;
const userTarget = entry.title;
const actor = entry.user;
let oldGroups = new Set();
let newGroups = new Set();
if (entry.params) {
if (entry.params.oldgroups) {
oldGroups = new Set(entry.params.oldgroups);
}
if (entry.params.newgroups) {
newGroups = new Set(entry.params.newgroups);
}
if (entry.params['4::oldgroups']) {
oldGroups = new Set(entry.params['4::oldgroups']);
}
if (entry.params['5::newgroups']) {
newGroups = new Set(entry.params['5::newgroups']);
}
}
if (entry.comment) {
const commentMatch = entry.comment.match(/changed group membership for (.+?) from (.+?) to (.+)/);
if (commentMatch) {
const oldGroupsStr = commentMatch[2];
const newGroupsStr = commentMatch[3];
if (oldGroupsStr !== '(none)') {
oldGroups = new Set(oldGroupsStr.split(', '));
}
if (newGroupsStr !== '(none)') {
newGroups = new Set(newGroupsStr.split(', '));
}
}
}
targetGroups.forEach(grp => {
if (newGroups.has(grp) && !oldGroups.has(grp)) {
granted.push([timestamp, userTarget, grp, actor, entry.logid, entry.comment || '']);
}
if (oldGroups.has(grp) && !newGroups.has(grp)) {
removed.push([timestamp, userTarget, grp, actor, entry.logid, entry.comment || '']);
}
});
});
return { granted: granted.sort(), removed: removed.sort() };
}
function createRightsTable(title, data, isMeta = false) {
if (!data.length) return '';
let html = `<h4>${title}</h4><table class="wikitable sortable"><thead><tr><th>Timestamp</th><th>User</th><th>Right</th><th>By</th><th>Comment</th></tr></thead><tbody>`;
data.forEach(([ts, tgt, grp, by, logid, comment]) => {
const sign = title.includes('granted') ? '+' : '-';
const baseUrl = isMeta ? 'https://meta.wikimedia.org' : 'https://en.wikipedia.org';
const logUrl = `${baseUrl}/w/index.php?title=Special:Log&logid=${logid}`;
const timestampLink = `<a href="${logUrl}" target="_blank">${formatDateFromTimestamp(ts)}</a>`;
let userLink, byLink;
if (isMeta) {
const cleanUsername = tgt.replace('@enwiki', '');
const finalUsername = cleanUsername.startsWith('User:') ? cleanUsername.substring(5) : cleanUsername;
userLink = `<a href="https://en.wikipedia.org/wiki/User:${encodeURIComponent(finalUsername)}" target="_blank">${tgt}</a>`;
const finalByUsername = by.startsWith('User:') ? by.substring(5) : by;
byLink = `<a href="https://meta.wikimedia.org/wiki/User:${encodeURIComponent(finalByUsername)}" target="_blank">${by}</a>`;
} else {
const finalTgtUsername = tgt.startsWith('User:') ? tgt.substring(5) : tgt;
userLink = `<a href="https://en.wikipedia.org/wiki/User:${encodeURIComponent(finalTgtUsername)}" target="_blank">${tgt}</a>`;
const finalByUsername = by.startsWith('User:') ? by.substring(5) : by;
byLink = `<a href="https://en.wikipedia.org/wiki/User:${encodeURIComponent(finalByUsername)}" target="_blank">${by}</a>`;
}
html += `<tr><td>${timestampLink}</td><td>${userLink}</td><td>${sign}${grp}</td><td>${byLink}</td><td>${parseWikilinks(comment, isMeta)}</td></tr>`;
});
html += '</tbody></table>';
return html;
}
function createEditTable(title, data, showDiff = false) {
if (!data.length) return `<p>No ${title.toLowerCase()} found for this period.</p>`;
const headerLabel = title.includes('ArbCom topics') ? 'Topic' : 'Comment';
let html = `<h4>${title}</h4><table class="wikitable sortable"><thead><tr><th>Date</th><th>User</th><th>${headerLabel}</th></tr></thead><tbody>`;
data.forEach(item => {
const timestamp = showDiff && item.revid ?
`<a href="https://en.wikipedia.org/w/index.php?diff=${item.revid}" target="_blank">${formatDateFromTimestamp(item.timestamp)}</a>` :
formatDateFromTimestamp(item.timestamp);
const finalUsername = item.user.startsWith('User:') ? item.user.substring(5) : item.user;
const userLink = `<a href="https://en.wikipedia.org/wiki/User:${encodeURIComponent(finalUsername)}" target="_blank">${item.user}</a>`;
let cellContent = parseWikilinks(item.comment || '', false);
if (title.includes('ArbCom topics') && item.comment && item.revid) {
const sectionMatch = item.comment.match(/\/\*\s*([^*]+?)\s*\*\//);
if (sectionMatch) {
const sectionName = sectionMatch[1].trim();
const encodedSection = encodeURIComponent(sectionName.replace(/ /g, '_'));
cellContent = `<a href="https://en.wikipedia.org/w/index.php?diff=${item.revid}#${encodedSection}" target="_blank">${sectionName}</a>`;
}
}
html += `<tr><td>${timestamp}</td><td>${userLink}</td><td>${cellContent}</td></tr>`;
});
html += '</tbody></table>';
return html;
}
function createCollapsibleSection(title, content, extraLinks = '') {
const sectionId = title.replace(/\s+/g, '').toLowerCase();
return `
<div style="border: 1px solid #ddd; margin-bottom: 15px; border-radius: 3px;">
<div style="background: #f8f9fa; padding: 10px; border-bottom: 1px solid #ddd; cursor: pointer;" onclick="toggleSection('${sectionId}')">
<h3 style="margin: 0; display: inline-block;">${title}</h3>
<span id="${sectionId}-toggle" style="float: right;">[hide]</span>
${extraLinks}
</div>
<div id="${sectionId}" style="display: block; padding: 15px;">
${content}
</div>
</div>
`;
}
function fetchAllLogEvents(api, params, results = []) {
return api.get(params).then(response => {
results.push(...(response.query.logevents || []));
if (response.continue) {
return fetchAllLogEvents(api, { ...params, ...response.continue }, results);
}
return results;
});
}
function fetchAllRevisions(api, params, results = []) {
return api.get(params).then(response => {
const pages = response.query.pages || {};
Object.values(pages).forEach(page => {
if (page.revisions) {
results.push(...page.revisions);
}
});
if (response.continue) {
return fetchAllRevisions(api, { ...params, ...response.continue }, results);
}
return results;
});
}
const metaApi = new mw.ForeignApi('https://meta.wikimedia.org/w/api.php', { headers: { 'Api-User-Agent': 'https://en.wikipedia.org/wiki/User:DreamRimmer' } });
const localApi = new mw.Api();
function fetchRightsChanges(startTs, endTs, startLabel, endLabel) {
return Promise.all([
fetchAllLogEvents(metaApi, {
action: 'query',
list: 'logevents',
letype: 'rights',
lestart: endTs,
leend: startTs,
lelimit: 'max',
leprop: 'timestamp|title|user|userid|details|params|type|comment|ids',
format: 'json'
}),
fetchAllLogEvents(localApi, {
action: 'query',
list: 'logevents',
letype: 'rights',
lestart: endTs,
leend: startTs,
lelimit: 'max',
leprop: 'timestamp|title|user|userid|details|params|type|comment|ids',
format: 'json'
})
]).then(([metaEntries, localEntries]) => {
const metaFiltered = metaEntries.filter(entry =>
entry.title && entry.title.endsWith('@enwiki')
);
const targetMeta = ['suppress', 'checkuser'];
const targetLocal = ['sysop', 'interface-admin', 'bureaucrat'];
const metaResults = processLogEntries(metaFiltered, targetMeta);
const localResults = processLogEntries(localEntries, targetLocal);
const tablesHtml = createRightsTable('Meta granted', metaResults.granted, true) +
createRightsTable('Meta removed', metaResults.removed, true) +
createRightsTable('Local granted', localResults.granted, false) +
createRightsTable('Local removed', localResults.removed, false);
return tablesHtml || '<p>No rights changes found for this period.</p>';
});
}
function fetchTechNews(year, month) {
const weeks = getWeeksInMonth(year, month);
const promises = weeks.map(week => {
return metaApi.get({
action: 'parse',
page: `Tech/News/${year}/${week}`,
format: 'json',
prop: 'text'
}).then(response => {
if (response.parse && response.parse.text) {
const html = response.parse.text['*'];
const match = html.match(/(\d{4}), week (\d+) \(([^)]+)\)/);
if (match) {
const weekDate = match[3];
const parsedDate = new Date(weekDate);
if (parsedDate.getMonth() === month - 1) {
return { week, date: weekDate, url: `https://meta.wikimedia.org/wiki/Tech/News/${year}/${week}` };
}
}
}
return null;
}).catch(() => null);
});
return Promise.all(promises).then(results => {
const validWeeks = results.filter(r => r !== null);
if (!validWeeks.length) {
return '<p>No tech news found for this period.</p>';
}
let html = '<h4>Tech News Links</h4><ul>';
validWeeks.forEach(({ week, date, url }) => {
html += `<li><a href="${url}" target="_blank">Tech/News/${year}/${week}</a> (${date})</li>`;
});
html += '</ul>';
return html;
});
}
function fetchCentralizedDiscussion(startTs, endTs) {
return fetchAllRevisions(localApi, {
action: 'query',
titles: 'Template:Centralized discussion',
prop: 'revisions',
rvstart: endTs,
rvend: startTs,
rvlimit: 'max',
rvprop: 'timestamp|user|comment|ids',
format: 'json'
}).then(revisions => {
return createEditTable('CENT edits', revisions, true);
});
}
function fetchArbComNotices(startTs, endTs) {
return fetchAllRevisions(localApi, {
action: 'query',
titles: 'Wikipedia:Arbitration Committee/Noticeboard',
prop: 'revisions',
rvstart: endTs,
rvend: startTs,
rvlimit: 'max',
rvprop: 'timestamp|user|comment|tags|ids',
format: 'json'
}).then(revisions => {
const filtered = revisions.filter(rev => rev.tags && rev.tags.includes('discussiontools-newtopic'));
return createEditTable('ArbCom topics', filtered, true);
});
}
function checkPageExists(title) {
return localApi.get({
action: 'query',
titles: title,
format: 'json'
}).then(response => {
const pages = response.query.pages || {};
return !Object.values(pages).some(page => page.hasOwnProperty('missing'));
}).catch(() => false);
}
function fetchData() {
var selectedItem = monthSelect.getMenu().findSelectedItem();
if (!selectedItem) {
OO.ui.alert('Please select a month');
return;
}
const month = parseInt(selectedItem.getData());
const year = parseInt(yearInput.getValue());
const section = sectionSelect.getMenu().findSelectedItem().getData();
if (year < 2005 || year > new Date().getFullYear() + 1) {
OO.ui.alert('Please enter a valid year between 2005 and ' + (new Date().getFullYear() + 1));
return;
}
const currentDate = new Date();
const isCurrentOrLastMonth = (year === currentDate.getFullYear() && month >= currentDate.getMonth()) ||
(year === currentDate.getFullYear() - 1 && month === 12 && currentDate.getMonth() === 0);
const startDt = new Date(Date.UTC(year, month - 1, 1, 0, 0, 0));
const nextMonthDt = new Date(Date.UTC(month === 12 ? year + 1 : year, month % 12, 1, 0, 0, 0));
const startTs = startDt.toISOString();
const endTs = nextMonthDt.toISOString();
const startLabel = formatDate(startDt);
const endLabel = formatDate(new Date(nextMonthDt.getTime() - 1000));
resultsContainer.empty();
resultsContainer.append($('<p>').text('Loading...'));
resultsContainer.show();
checkButton.setDisabled(true);
checkButton.setLabel('Loading...');
const promises = [];
const sections = [];
if (section === 'all' || section === 'rights') {
promises.push(fetchRightsChanges(startTs, endTs, startLabel, endLabel));
sections.push('rights');
}
if (section === 'all' || section === 'tech') {
promises.push(fetchTechNews(year, month));
sections.push('tech');
}
if (section === 'all' || section === 'centralized') {
promises.push(fetchCentralizedDiscussion(startTs, endTs));
sections.push('centralized');
}
if (section === 'all' || section === 'arbcom') {
promises.push(fetchArbComNotices(startTs, endTs));
sections.push('arbcom');
}
Promise.all(promises).then(results => {
resultsContainer.empty();
const headerHtml = `<h2>Results from ${startLabel} to ${endLabel}</h2>`;
resultsContainer.append(headerHtml);
window.toggleSection = function(sectionId) {
const content = document.getElementById(sectionId);
const toggle = document.getElementById(sectionId + '-toggle');
if (content.style.display === 'none') {
content.style.display = 'block';
toggle.textContent = '[hide]';
} else {
content.style.display = 'none';
toggle.textContent = '[show]';
}
};
let resultIndex = 0;
if (sections.includes('rights')) {
const rightsHtml = createCollapsibleSection('Rights changes', results[resultIndex]);
resultsContainer.append(rightsHtml);
resultIndex++;
}
if (sections.includes('tech')) {
const techHtml = createCollapsibleSection('Tech News', results[resultIndex]);
resultsContainer.append(techHtml);
resultIndex++;
}
if (sections.includes('centralized')) {
const centralizedHtml = createCollapsibleSection('CENT', results[resultIndex]);
resultsContainer.append(centralizedHtml);
resultIndex++;
}
if (sections.includes('arbcom')) {
const arbcomHtml = createCollapsibleSection('ArbCom updates', results[resultIndex]);
resultsContainer.append(arbcomHtml);
resultIndex++;
}
if ((section === 'all' || section === 'arbcom') && isCurrentOrLastMonth) {
const arbcomTasksHtml = createCollapsibleSection('Arbitration Committee open and recently closed cases', '<p><a href="https://en.wikipedia.org/wiki/Template:ArbComOpenTasks" target="_blank">Arbitration Committee open and recently closed cases</a></p>');
resultsContainer.append(arbcomTasksHtml);
}
if ((section === 'all' || section === 'rfc') && isCurrentOrLastMonth) {
const rfcHtml = createCollapsibleSection('Recent RfCs', '<p><a href="https://en.wikipedia.org/wiki/Wikipedia:Requests_for_comment/All" target="_blank">Recent RfCs</a></p>');
resultsContainer.append(rfcHtml);
}
if (section === 'all' || section === 'misc') {
const miscPromises = [];
let miscSections = [];
if (year >= 2025 && isCurrentOrLastMonth) {
miscSections.push('<h4>Backlog drive schedule</h4><p><a href="https://en.wikipedia.org/wiki/Wikipedia:Backlog_drive_schedule" target="_blank">Wikipedia:Backlog drive schedule</a></p>');
}
if ((year > 2024 || (year === 2024 && month >= 10)) && isCurrentOrLastMonth) {
miscSections.push('<h4>Administrator elections</h4><p><a href="https://en.wikipedia.org/wiki/Wikipedia:Administrator_elections" target="_blank">Administrator elections</a> updates</p>');
}
if (year >= 2006) {
miscPromises.push(
checkPageExists(`Wikipedia:Arbitration_Committee_Elections_December_${year}`)
.then(exists => exists ? `<h4>Arbitration Committee elections</h4><p><a href="https://en.wikipedia.org/wiki/Wikipedia:Arbitration_Committee_Elections_December_${year}" target="_blank">Arbitration Committee elections</a></p>` : '')
);
} else {
miscPromises.push(Promise.resolve(''));
}
if (year >= 2024 && isCurrentOrLastMonth) {
miscSections.push('<h4>UCoC election updates</h4><p><a href="https://meta.wikimedia.org/wiki/Universal_Code_of_Conduct/Coordinating_Committee/Election" target="_blank">UCoC election updates</a></p>');
}
if (miscPromises.length > 0) {
Promise.all(miscPromises).then(dynamicSections => {
let miscContent = miscSections.join('') + dynamicSections.join('');
if (miscContent) {
const miscHtml = createCollapsibleSection('Miscellaneous', miscContent);
resultsContainer.append(miscHtml);
}
});
} else {
let miscContent = miscSections.join('');
if (miscContent) {
const miscHtml = createCollapsibleSection('Miscellaneous', miscContent);
resultsContainer.append(miscHtml);
}
}
}
}).catch(error => {
console.error('Error:', error);
resultsContainer.empty();
resultsContainer.append($('<p>').css('color', 'red').text(`Error fetching data: ${error}`));
}).finally(() => {
checkButton.setDisabled(false);
checkButton.setLabel('Check');
});
}
checkButton.on('click', fetchData);
}
$.when(mw.loader.using('mediawiki.util'), $.ready).then(function() {
mw.util.addPortletLink(
'p-tb',
mw.util.getUrl('Special:BlankPage/AdminNewsTools'),
'Administrators\' newsletter tools',
't-adminnewstools',
'View various Administrators\' newsletter related updates for a specific month'
);
});
if (mw.config.get('wgCanonicalSpecialPageName') === 'Blankpage' && mw.config.get('wgTitle').split('/', 2)[1] === 'AdminNewsTools') {
$.when(
mw.loader.using([
'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows'
]),
$.ready
).then(function() {
initializeAdminNewsTools();
});
}
});
//</nowiki>