User:DreamRimmer/adminnewslettertools.js: Difference between revisions

Content deleted Content added
fix
add convenient-discussions tag (DR)
 
(25 intermediate revisions by the same user not shown)
Line 7:
*/
$(document).ready(function() {
var apiCache = {};
var CACHE_DURATION = 10 * 60 * 1000; // 10 minutes
 
function getCacheKey(api, params) {
return JSON.stringify({api: api.toString(), params: params});
}
 
function getCachedResult(key) {
var cached = apiCache[key];
if (cached && (Date.now() - cached.timestamp < CACHE_DURATION)) {
return cached.data;
}
return null;
}
 
function setCachedResult(key, data) {
apiCache[key] = {
data: data,
timestamp: Date.now()
};
}
 
function initializeAdminNewsTools() {
$('#mw-content-text > p').remove();
$('#firstHeading').text('AdminNewsToolsAdministrators\' newsletter tools');
 
var monthSelect = new OO.ui.DropdownWidget({
Line 33 ⟶ 55:
min: 2005,
max: new Date().getFullYear() + 1,
step: 1,
showButtons: true
}),
sectionSelect = new OO.ui.DropdownWidget({
Line 80 ⟶ 103:
monthSelect.getMenu().selectItemByData(currentMonth);
sectionSelect.getMenu().selectItemByData('all');
 
var labels = {
monthLabel: $('<p>').text('Month:').css('font-weight', 'bold'),
Line 364 ⟶ 386:
}
 
function fetchAllLogEventsfetchAllLogEventsBatched(api, params, results = [], batchSize = 50, delay = 200) {
returnvar api.get(params).then(responsecacheKey => {getCacheKey(api, params);
var cached = results.pushgetCachedResult(...(response.query.logevents || [])cacheKey);
if (response.continuecached) {
return fetchAllLogEventsPromise.resolve(api, { ...params, ...response.continue }, resultscached);
}
 
return results;
var currentParams = {...params, lelimit: Math.min(batchSize, params.lelimit || 500)};
});
function fetchBatch() {
return new Promise((resolve, reject) => {
var timeout = setTimeout(() => {
reject(new Error('API request timeout'));
}, 15000);
 
api.get(currentParams).then(response => {
clearTimeout(timeout);
var newResults = response.query.logevents || [];
results.push(...newResults);
if (response.continue && results.length < (params.lelimit || 500)) {
currentParams = {...currentParams, ...response.continue};
setTimeout(() => {
fetchBatch().then(resolve).catch(reject);
}, delay);
} else {
setCachedResult(cacheKey, results);
resolve(results);
}
}).catch(error => {
clearTimeout(timeout);
reject(error);
});
});
}
return fetchBatch();
}
 
function fetchAllRevisionsfetchAllRevisionsBatched(api, params, results = [], batchSize = 50, delay = 200) {
returnvar api.get(params).then(responsecacheKey => {getCacheKey(api, params);
var const pagescached = response.query.pages || {}getCachedResult(cacheKey);
if Object.values(pagescached).forEach(page => {
return if Promise.resolve(page.revisionscached) {;
results.push(...page.revisions);}
 
}
var currentParams = {...params, rvlimit: Math.min(batchSize, params.rvlimit || 500)};
function fetchBatch() {
return new Promise((resolve, reject) => {
var timeout = setTimeout(() => {
reject(new Error('API request timeout'));
}, 15000);
 
api.get(currentParams).then(response => {
clearTimeout(timeout);
const pages = response.query.pages || {};
Object.values(pages).forEach(page => {
if (page.revisions) {
results.push(...page.revisions);
}
});
if (response.continue && results.length < (params.rvlimit || 500)) {
currentParams = {...currentParams, ...response.continue};
setTimeout(() => {
fetchBatch().then(resolve).catch(reject);
}, delay);
} else {
setCachedResult(cacheKey, results);
resolve(results);
}
}).catch(error => {
clearTimeout(timeout);
reject(error);
});
});
if (response.continue) {}
return fetchAllRevisions(api, { ...params, ...response.continue }, results);
return }fetchBatch();
return results;
});
}
 
Line 395 ⟶ 474:
 
return Promise.all([
fetchAllLogEventsfetchAllLogEventsBatched(metaApi, {
action: 'query',
list: 'logevents',
Line 401 ⟶ 480:
lestart: endTs,
leend: startTs,
lelimit: 'max'500,
leprop: 'timestamp|title|user|userid|details|params|type|comment|ids',
format: 'json'
}),
fetchAllLogEventsfetchAllLogEventsBatched(localApi, {
action: 'query',
list: 'logevents',
Line 411 ⟶ 490:
lestart: endTs,
leend: startTs,
lelimit: 'max'500,
leprop: 'timestamp|title|user|userid|details|params|type|comment|ids',
format: 'json'
Line 435 ⟶ 514:
}
 
function fetchTechNewsfetchTechNewsBatched(year, month) {
const weeks = getWeeksInMonth(year, month);
var cacheKey = `tech_news_${year}_${month}`;
var cached = getCachedResult(cacheKey);
if (cached) {
return Promise.resolve(cached);
}
constfunction promises = weeks.mapfetchWeekBatch(week =>weekBatch) {
return metaApiPromise.getall(weekBatch.map(week => {
action:return 'parse',new Promise((resolve) => {
page: `Tech/News/${year}/$ var timeout = setTimeout(() => {week}`,
format: 'json', resolve(null);
prop: 'text' }, 10000);
 
}).then(response => {
if (response.parse && response metaApi.parse.text) get({
const html = response.parse.text[ action: '*parse'];,
const match = html.match( page: `Tech/News/(\d${4year}), week (\d+) \(([^)]+)\)/);${week}`,
if (match) { format: 'json',
constprop: weekDate = match[3];'text'
const parsedDate}).then(response => new Date(weekDate);{
if clearTimeout(parsedDate.getMonth() === month - 1timeout) {;
if (response.parse && return { week, date: weekDate, url: `https://metaresponse.wikimediaparse.org/wiki/Tech/News/$text) {year}/${week}` };
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) {
resolve({ week, date: weekDate, url: `https://meta.wikimedia.org/wiki/Tech/News/${year}/${week}` });
return;
}
}
}
} resolve(null);
}).catch(() => {
return null clearTimeout(timeout);
}).catch(() => resolve(null);
});
});
}));
}
 
returnconst Promise.all(promises).then(resultsbatchSize => {2;
const batches = [];
for (let i = 0; i < weeks.length; i += batchSize) {
batches.push(weeks.slice(i, i + batchSize));
}
 
return batches.reduce((promise, batch) => {
return promise.then(results => {
return fetchWeekBatch(batch).then(batchResults => {
return results.concat(batchResults);
});
});
}, Promise.resolve([])).then(results => {
const validWeeks = results.filter(r => r !== null);
let html;
if (!validWeeks.length) {
returnhtml = '<p>No tech news found for this period.</p>';
} else {
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>';
}
setCachedResult(cacheKey, html);
 
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;
});
Line 477 ⟶ 588:
function fetchCentralizedDiscussion(startTs, endTs) {
return fetchAllRevisionsfetchAllRevisionsBatched(localApi, {
action: 'query',
titles: 'Template:Centralized discussion',
Line 483 ⟶ 594:
rvstart: endTs,
rvend: startTs,
rvlimit: 'max'500,
rvprop: 'timestamp|user|comment|ids',
format: 'json'
Line 493 ⟶ 604:
function fetchArbComNotices(startTs, endTs) {
return fetchAllRevisionsfetchAllRevisionsBatched(localApi, {
action: 'query',
titles: 'Wikipedia:Arbitration Committee/Noticeboard',
Line 499 ⟶ 610:
rvstart: endTs,
rvend: startTs,
rvlimit: 'max'500,
rvprop: 'timestamp|user|comment|tags|ids',
format: 'json'
}).then(revisions => {
const filtered = revisions.filter(rev => rev.tags && (rev.tags.includes('discussiontools-newtopic') || rev.tags.includes('convenient-discussions')));
return createEditTable('ArbCom topics', filtered, true);
});
}
 
function fetchArbComCases() {
var cacheKey = 'arbcom_cases';
var cached = getCachedResult(cacheKey);
if (cached) {
return Promise.resolve(cached);
}
 
return Promise.all([
new Promise((resolve) => {
var timeout = setTimeout(() => resolve({ parse: null }), 10000);
localApi.get({
action: 'parse',
page: 'Template:ArbComOpenTasks/Cases',
format: 'json',
prop: 'text'
}).then(response => {
clearTimeout(timeout);
resolve(response);
}).catch(() => {
clearTimeout(timeout);
resolve({ parse: null });
});
}),
new Promise((resolve) => {
var timeout = setTimeout(() => resolve({ parse: null }), 10000);
localApi.get({
action: 'parse',
page: 'Template:ArbComOpenTasks/ClosedCases',
format: 'json',
prop: 'text'
}).then(response => {
clearTimeout(timeout);
resolve(response);
}).catch(() => {
clearTimeout(timeout);
resolve({ parse: null });
});
})
]).then(([openResponse, closedResponse]) => {
let html = '';
if (openResponse.parse && openResponse.parse.text) {
const openHtml = openResponse.parse.text['*'];
const openCases = parseArbComCases(openHtml, 'open');
html += createArbComTable('Open cases', openCases, 'open');
} else {
html += '<h4>Open cases</h4><p>No cases are in this period open.</p>';
}
if (closedResponse.parse && closedResponse.parse.text) {
const closedHtml = closedResponse.parse.text['*'];
const closedCases = parseArbComCases(closedHtml, 'closed');
html += createArbComTable('Recently closed cases', closedCases, 'closed');
} else {
html += '<h4>Recently closed cases</h4><p>No cases are in this period closed.</p>';
}
html += '<p><a href="https://en.wikipedia.org/wiki/Template:ArbComOpenTasks" target="_blank">See detailed statistics</a></p>';
setCachedResult(cacheKey, html);
return html;
});
}
function parseArbComCases(html, type) {
const cases = [];
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const rows = doc.querySelectorAll('table.wikitable tbody tr');
rows.forEach((row, index) => {
if (index === 0) return;
const cells = row.querySelectorAll('td');
if (cells.length === 0) return;
const firstCell = cells[0];
const link = firstCell.querySelector('a');
if (!link) return;
const caseName = link.textContent.trim();
const href = link.getAttribute('href');
const fullUrl = href.startsWith('/wiki/') ? `https://en.wikipedia.org${href}` : href;
if (type === 'open' && cells.length >= 4) {
const evidenceDeadline = cells[2] ? cells[2].textContent.trim() : '';
const pdDeadline = cells[3] ? cells[3].textContent.trim() : '';
cases.push({
name: caseName,
url: fullUrl,
evidenceDeadline: evidenceDeadline,
pdDeadline: pdDeadline
});
} else if (type === 'closed' && cells.length >= 2) {
const dateClosed = cells[1] ? cells[1].textContent.trim() : '';
cases.push({
name: caseName,
url: fullUrl,
dateClosed: dateClosed
});
}
});
return cases;
}
function createArbComTable(title, cases, type) {
let html = `<h4>${title}</h4>`;
if (cases.length === 0) {
const period = type === 'open' ? 'open' : 'closed';
html += `<p>No cases are in this period ${period}.</p>`;
return html;
}
if (type === 'open') {
html += '<table class="wikitable"><thead><tr><th>Case name</th><th>Evidence deadline</th><th>PD deadline</th></tr></thead><tbody>';
cases.forEach(caseItem => {
html += `<tr><td><i><a href="${caseItem.url}" target="_blank">${caseItem.name}</a></i></td><td>${caseItem.evidenceDeadline}</td><td>${caseItem.pdDeadline}</td></tr>`;
});
} else {
html += '<table class="wikitable"><thead><tr><th>Case name</th><th>Date closed</th></tr></thead><tbody>';
cases.forEach(caseItem => {
html += `<tr><td><i><a href="${caseItem.url}" target="_blank">${caseItem.name}</a></i></td><td>${caseItem.dateClosed}</td></tr>`;
});
}
html += '</tbody></table>';
return html;
}
 
function fetchRecentRfCsBatched() {
var cacheKey = 'recent_rfcs';
var cached = getCachedResult(cacheKey);
if (cached) {
return Promise.resolve(cached);
}
 
function fetchRecentRfCs() {
const rfcPages = [
{ name: 'Biographies', url: 'Wikipedia:Requests_for_comment/Biographies' },
Line 529 ⟶ 777:
];
 
constfunction promises = rfcPages.mapfetchPageBatch(page =>pageBatch) {
return localApiPromise.getall(pageBatch.map(page => {
action:return 'parse',new Promise((resolve) => {
page: page.url, var timeout = setTimeout(() => {
format: 'json', resolve([]);
prop: 'wikitext' }, 10000);
}).then(response => {
if (response.parse && response.parse.wikitext) {
const wikitext = response.parse.wikitext['*'];
const rfcs = parseRfCs(wikitext, page.name);
return rfcs;
}
return [];
}).catch(() => []);
});
 
localApi.get({
return Promise.all(promises).then(results => {
const allRfcs = results.flat(); action: 'parse',
return createRfCDisplay(allRfcs); page: page.url,
format: 'json',
prop: 'wikitext'
}).then(response => {
clearTimeout(timeout);
if (response.parse && response.parse.wikitext) {
const wikitext = response.parse.wikitext['*'];
const rfcs = parseRfCs(wikitext, page.name);
resolve(rfcs);
} else {
resolve([]);
}
}).catch(() => {
clearTimeout(timeout);
resolve([]);
});
});
}));
}
 
const batchSize = 3;
const batches = [];
for (let i = 0; i < rfcPages.length; i += batchSize) {
batches.push(rfcPages.slice(i, i + batchSize));
}
 
return batches.reduce((promise, batch, index) => {
return promise.then(results => {
return fetchPageBatch(batch).then(batchResults => {
if (index < batches.length - 1) {
return new Promise(resolve => {
setTimeout(() => {
resolve(results.concat(batchResults.flat()));
}, 300);
});
} else {
return results.concat(batchResults.flat());
}
});
});
}, Promise.resolve([])).then(allRfcs => {
const result = createRfCDisplay(allRfcs);
setCachedResult(cacheKey, result);
return result;
});
}
Line 553 ⟶ 835:
function parseRfCs(wikitext, topicName) {
const rfcs = [];
const rfcPatternrfcEntryPattern = /'''\[\[([^\]|]+)(?:\|([^\]]+))?\]\]'''[\s\S]*?\{\{rfcquote\|text=((?:[^{}]|\{[^{]|\}[^}]|\{\{(?:[^{}]|\{[^{]|\}[^}])*\}\})*)\}\}/ggi;
let match;
whileconst ((matchnow = rfcPattern.execnew Date(wikitext)) !== null) {;
const linkTextcurrentUtcYear = match[1]now.getUTCFullYear();
const currentUtcMonth = let pageName, anchornow.getUTCMonth();
const currentUtcDate = now.getUTCDate();
const currentUtcHours = if (linkTextnow.includesgetUTCHours('#')) {;
const [pageName, anchor]currentUtcMinutes = linkTextnow.splitgetUTCMinutes('#');
const currentUtc = new Date(Date.UTC(currentUtcYear, currentUtcMonth, currentUtcDate, currentUtcHours, currentUtcMinutes, 0, 0));
let match;
while ((match = rfcEntryPattern.exec(wikitext)) !== null) {
let pageName, anchor, displayTitle;
const link = match[1];
displayTitle = match[2] || (anchor ? `${link}#${anchor}` : link);
if (link.includes('#')) {
[pageName, anchor] = link.split('#');
} else {
pageName = linkTextlink;
anchor = '';
}
const afterMatchrfcText = wikitext.substring(match.index + match[03].length);
constlet rfcquoteMatchtimestampStr = afterMatch.match(/\{\{rfcquote\|text=([^}]+)\}\}/)null;
let daysDiff = null;
if (rfcquoteMatch) {
const timestampMatch = rfcText.match(/(\d{2}):(\d{2}), const(\d{1,2}) rfcquoteText(\w+) =(\d{4}) rfcquoteMatch[1]\(UTC\)/);
if const (timestampMatch) = rfcquoteText.match(/(\d{2}:\d{2}, \d{1,2} \w+ \d{4} \(UTC\))/);
const [fullMatch, hour, minute, day, monthText, year] = timestampMatch;
timestampStr = fullMatch;
ifconst (timestampMatch)months {= [
const"January", timestampStr"February", ="March", timestampMatch[1];"April", "May", "June",
const"July", rfcDate"August", ="September", new Date(timestampStr.replace(' (UTC)'"October", '"November", UTC'));"December"
const now = new Date()];
const daysDiffmonthNum = Mathmonths.floorindexOf((now - rfcDate) / (1000 * 60 * 60 * 24)monthText);
if (monthNum !== -1) {
const tsDate = new Date(Date.UTC(
parseInt(year, 10),
monthNum,
parseInt(day, 10),
parseInt(hour, 10),
parseInt(minute, 10),
0, 0
));
const urltimeDiff = anchorcurrentUtc.getTime() ?- tsDate.getTime();
daysDiff = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
`https://en.wikipedia.org/wiki/${encodeURIComponent(pageName)}#${encodeURIComponent(anchor)}` :
`https://en.wikipedia.org/wiki/${encodeURIComponent(pageName)}`;
rfcs.pushif ({daysDiff < 0) daysDiff = 0;
topic: topicName,
title: pageName,
url: url,
daysOld: daysDiff,
timestamp: timestampStr
});
}
}
const url = anchor ?
`https://en.wikipedia.org/wiki/${encodeURIComponent(pageName)}#${encodeURIComponent(anchor)}` :
`https://en.wikipedia.org/wiki/${encodeURIComponent(pageName)}`;
rfcs.push({
topic: topicName,
title: displayTitle || pageName,
url: url,
daysOld: daysDiff,
timestamp: timestampStr || "unavailable"
});
}
Line 607 ⟶ 914:
const ageOptions = [3, 7, 10, 15, 20, 25, 30];
const topicNamesrfcMap = [new Map();
'Biographies', 'Economy, trade, and companies', 'History and geography',
rfcs.forEach(rfc => {
'Language and linguistics', 'Maths, science, and technology',
const uniqueKey = `${rfc.url}|${rfc.title}`;
'Art, architecture, literature, and media', 'Politics, government, and law',
if (rfcMap.has(uniqueKey)) {
'Religion and philosophy', 'Society, sports, and culture',
'Wikipedia style and naming', 'Wikipediaconst policiesexisting and= guidelines',rfcMap.get(uniqueKey);
if (!existing.topics.includes(rfc.topic)) {
'WikiProjects and collaborations', 'Wikipedia technical issues and templates',
'Wikipedia proposals', 'Unsorted', 'User names' existing.topics.push(rfc.topic);
]; }
} else {
 
rfcMap.set(uniqueKey, {
...rfc,
topics: [rfc.topic]
});
}
});
const deduplicatedRfcs = Array.from(rfcMap.values());
let html = `<p><small>Current date: ${currentDate}</small></p>`;
html += `<p>Age filter: <select id="rfc-age-filter" onchange="filterRfCs()">`;
ageOptions.forEach(age => {
const selected = age === 7 ? ' selected' : '';
Line 624 ⟶ 940:
});
html += `</select></p>`;
 
function renderRfCTable(filteredRfcs) {
html += '<div id="rfc-summary-table"><h4>RfC Count Summary</h4><table class="wikitable"><thead><tr><th>Topic</th>';
ageOptions.forEach if (agefilteredRfcs.length => 0) {
let table = '<h4>Current RfCs</h4><table class="wikitable sortable"><thead><tr><th>Topic</th><th>RfC</th><th>Days Old</th><th>Started</th></tr></thead><tbody>';
html += `<th>${age}d</th>`;
filteredRfcs.sort((a, b) => a.daysOld - b.daysOld).forEach((rfc, index) => {
});
html += '</tr></thead><tbody>' let topicDisplay;
if (rfc.topics.length === 1) {
 
topicNames.forEach(topic topicDisplay => {rfc.topics[0];
html += `<tr><td>${topic }</td>`; else {
ageOptions.forEach(age const mainTopic => {rfc.topics[0];
const count = rfcs.filter(rfc => rfc.topic === topic &&const rfc.daysOldadditionalCount <= age)rfc.topics.length - 1;
html + const additionalTopics = `<td>${count}</td>`rfc.topics.slice(1).join(', ');
});
html + topicDisplay = '</tr>';`
<span class="main-topic">${mainTopic}</span>
});
<span class="additional-topics" style="color: #0645ad; cursor: pointer; text-decoration: underline;"
html += '</tbody></table></div>';
onclick="toggleTopics('topics-${index}')"
 
title="${additionalTopics}">
ageOptions.forEach(age => {
const filteredRfcs = rfcs.filter(rfc => rfc.daysOld <= age (+${additionalCount} more);
const displayStyle = age === 7 ? 'block' : 'none'; </span>
<div id="topics-${index}" style="display: none; margin-top: 5px; font-size: 0.9em; color: #666;">
${rfc.topics.slice(1).map(topic => `<div>• ${topic}</div>`).join('')}
html += `<div class="rfc-age-group" data-age="${age}" style="display: ${displayStyle};">`;
</div>
if (filteredRfcs.length === 0) { `;
html += `<p>No RfCs found that are ${age} days old or newer.</p>`;
} else {
topicNames.forEach(topic => {
const topicRfcs = filteredRfcs.filter(rfc => rfc.topic === topic);
if (topicRfcs.length > 0) {
html += `<h4>${topic} (${topicRfcs.length})</h4><ul>`;
topicRfcs.forEach(rfc => {
html += `<li><a href="${rfc.url}" target="_blank">${rfc.title}</a> (${rfc.daysOld} days old)</li>`;
});
html += '</ul>';
}
table += `<tr><td>${topicDisplay}</td><td><a href="${rfc.url}" target="_blank">${rfc.title}</a></td><td>${rfc.daysOld}</td><td>${rfc.timestamp}</td></tr>`;
});
table += '</tbody></table>';
return table;
} else {
return `<p>No RfCs found for selected age filter.</p>`;
}
html += '</div>';}
});
html += `<div id="rfc-list-table"></div>`;
 
html += '<p><a href="https://en.wikipedia.org/wiki/Wikipedia:Requests_for_comment/All" target="_blank">All open RfCs</a></p>';
 
html += `<script>
functionwindow._rfcs filterRfCs()= ${JSON.stringify(deduplicatedRfcs)};
const selectedAgewindow._ageOptions = document${JSON.getElementByIdstringify('rfc-age-filter'ageOptions).value};
const groups = document.querySelectorAll('.rfc-age-group');
function groups.forEachtoggleTopics(group =>elementId) {
var group.style.displayelement = groupdocument.dataset.age == selectedAge ? 'block' : 'none'getElementById(elementId);
if (element.style.display === 'none') {
element.style.display = 'block';
} else {
element.style.display = 'none';
}
}
function updateRfCTable() {
var rfcs = window._rfcs;
var age = parseInt(document.getElementById('rfc-age-filter').value, 10);
var filteredRfcs = rfcs.filter(function(rfc) {
return !isNaN(rfc.daysOld) && rfc.daysOld <= age;
});
document.getElementById('rfc-list-table').innerHTML = (${renderRfCTable.toString()})(filteredRfcs);
}
document.getElementById('rfc-age-filter').addEventListener('change', updateRfCTable);
updateRfCTable();
</script>`;
 
return html;
}
 
function checkPageExists(title) {
returnvar localApi.get(cacheKey = `page_exists_${title}`;
var cached = action: 'query',getCachedResult(cacheKey);
if (cached !== null) titles: title,{
format:return 'json'Promise.resolve(cached);
}).then(response => {
 
const pages = response.query.pages || {};
return new Promise((resolve) => {
return !Object.values(pages).some(page => page.hasOwnProperty('missing'));
}).catch var timeout = setTimeout(() => false);{
setCachedResult(cacheKey, false);
resolve(false);
}, 10000);
 
localApi.get({
action: 'query',
titles: title,
format: 'json'
}).then(response => {
clearTimeout(timeout);
const pages = response.query.pages || {};
const exists = !Object.values(pages).some(page => page.hasOwnProperty('missing'));
setCachedResult(cacheKey, exists);
resolve(exists);
}).catch(() => {
clearTimeout(timeout);
setCachedResult(cacheKey, false);
resolve(false);
});
});
}
 
Line 735 ⟶ 1,082:
 
if (section === 'all' || section === 'tech') {
promises.push(fetchTechNewsfetchTechNewsBatched(year, month));
sections.push('tech');
}
Line 750 ⟶ 1,097:
 
if ((section === 'all' || section === 'rfc') && isCurrentOrLastMonth) {
promises.push(fetchRecentRfCsfetchRecentRfCsBatched());
sections.push('rfc');
}
Line 796 ⟶ 1,143:
resultsContainer.append(arbcomHtml);
resultIndex++;
fetchArbComCases().then(arbcomCasesHtml => {
const arbcomTasksHtml = createCollapsibleSection('Arbitration Committee open and recently closed cases', arbcomCasesHtml);
resultsContainer.append(arbcomTasksHtml);
});
}
if (sections.includes('rfc')) {
Line 802 ⟶ 1,154:
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 === 'misc') {
const miscPromises = [];