'use strict';
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'mediawiki.DateFormatter', 'user.options']).then(async () => {
if (mw.config.get('wgNamespaceNumber') !== 14) return;
const DEFAULT_OPTIONS = { pages: [] };
const NOTIFY_OPTIONS = { autoHide: false, title: 'Catatonic' };
const pageId = mw.config.get('wgRelevantArticleId');
const api = new mw.Api();
let options = await getOptions();
if (mw.config.get('wgAction') === 'info') {
addToggle();
} else if (options.pages.includes(pageId)) {
addCSS();
addTags();
}
async function addToggle() {
function updateLink() {
link.textContent = `${listed ? 'disable' : 'enable'} activity tags`;
link.title = `Catatonic ${listed ? 'enabled' : 'disabled'}`;
}
const td = document.querySelector('#mw-pageinfo-article-id td:nth-child(2)');
if (!td) return;
let listed = options.pages.includes(pageId);
const link = document.createElement('a');
link.href = '#';
updateLink();
link.addEventListener('click', async event => {
event.preventDefault();
try {
listed = !listed;
if (listed) options.pages.push(pageId);
else options.pages.splice(options.pages.indexOf(pageId), 1);
options.pages = [...new Set(options.pages)].sort((a, b) => a - b);
await saveOptions(options);
options = await getOptions();
updateLink();
showListedPages();
} catch (error) {
mw.notify(`Error: ${error}`, { ...NOTIFY_OPTIONS, type: 'error' });
link.textContent = 'error';
link.style.pointerEvents = 'none';
}
});
td.append(' (', link, ')');
}
function addCSS() {
mw.util.addCSS(`
.catatonic-inactivity { color: var(--color-base, #808080); }
.catatonic-inactivity[data-weeks] { color: var(--color-error, #de5a49); }
.catatonic-inactivity[data-weeks="1"] { color: var(--color-warning, #a97e2a); }
.catatonic-inactivity[data-weeks="0"] { color: var(--color-success, #229679); }
`);
}
async function addTags() {
const formatTimeAndDate = mw.loader.require('mediawiki.DateFormatter').formatTimeAndDate;
const articlePath = mw.config.get('wgArticlePath');
const articleStatic = articlePath.replace('$1', '');
const articleRegex = new RegExp(articlePath.replace('$1', '([^#?]+)'));
const nodes = document.querySelectorAll(`#mw-content-text .mw-category li > a[href*="${articleStatic}"]`);
const pages = new Map();
for (const link of nodes) {
const li = link.closest('li');
if (!li) continue;
const match = link.getAttribute('href')?.match(articleRegex);
const title = match ? decodeURIComponent(match[1]).replace(/_/g, ' ') : link.textContent;
if (title) pages.set(title, li);
}
for (const group of batch(Array.from(pages.keys()), 50)) {
const titles = group.join('|');
try {
const result = await api.get({
action: 'query',
format: 'json',
formatversion: 2,
prop: 'revisions',
rvprop: 'timestamp',
titles
});
for (const page of result?.query?.pages || []) {
const li = pages.get(page.title);
if (!li) continue;
const span = document.createElement('span');
span.className = 'catatonic-inactivity';
const ts = page.revisions?.[0]?.timestamp;
if (ts) {
const days = Math.floor(daysSince(ts));
span.dataset.days = days;
span.dataset.weeks = Math.floor(days / 7);
span.dataset.months = Math.floor(days / 30);
span.textContent = days;
span.title = `last activity: ${formatTimeAndDate(new Date(ts))}`;
} else {
span.textContent = 'unknown';
span.title = 'last activity: unknown';
}
li.append(' \u00b7 ', span);
}
} catch (error) {
console.error('Catatonic: addTags error', error);
}
}
}
async function getOptions() {
try {
const stored = mw.user.options.get('userjs-catatonic') || '{}';
const parsed = JSON.parse(stored);
return { ...DEFAULT_OPTIONS, ...parsed };
} catch (error) {
console.error('Catatonic: getOptions error', error);
await saveOptions(DEFAULT_OPTIONS);
return DEFAULT_OPTIONS;
}
}
async function saveOptions(options) {
try {
const value = JSON.stringify(options);
if (mw.user.isNamed()) await api.saveOption('userjs-catatonic', value);
mw.user.options.set('userjs-catatonic', value);
} catch (error) {
console.error('Catatonic: saveOptions error', error);
throw error;
}
}
function showListedPages() {
const message = document.createElement('div');
message.textContent = 'Listed pages: ';
if (options.pages.length) {
options.pages.forEach((id, i) => {
if (i > 0) message.append(', ');
const a = document.createElement('a');
a.href = mw.util.getUrl(null, { curid: id, action: 'info' });
a.textContent = id;
a.title = `Page information for ${id}`;
a.target = '_blank';
message.append(a);
});
} else {
message.append('none');
}
mw.notify(message, { ...NOTIFY_OPTIONS, tag: 'catatonic-list' });
}
function batch(items, max) {
const bins = Math.ceil(items.length / max);
const size = Math.ceil(items.length / bins);
const batches = [];
for (let i = 0; i < items.length; i += size) {
batches.push(items.slice(i, i + size));
}
return batches;
}
function daysSince(timestamp) {
return (new Date() - new Date(timestamp)) / (1000 * 60 * 60 * 24);
}
});