/* global mw */
(function() {
'use strict';
const USERSCRIPT_NAME = 'Unsigned generator';
const VERSION = 4;
const locale = {
portletText: "Unsigned gen", // short to keep the "Tools" menu narrow
portletTooltip: "Generate template for unsigned discussion messages",
template: "subst:Unsigned", // see [[Template:Unsigned]]
templateIp: "subst:Unsigned IP", // see [[Template:Unsigned IP]]
prefixText: "Unsigned wikitext: ",
previewPrefix: "Unsigned preview: ",
errorPrefix: "Error: ",
copyButtonText: "Copy",
errorTimestamp: "Cannot find the timestamp of the edit. Aborting.",
errorAuthor: "Cannot find the author of the edit. Aborting.",
};
const LOG_PREFIX = `[${USERSCRIPT_NAME} v${VERSION}]:`;
const USERSCRIPT_OUTPUT_ID = 'userscript-unsigned-generator';
function error(...toLog) {
console.error(LOG_PREFIX, ...toLog);
}
function warn(...toLog) {
console.warn(LOG_PREFIX, ...toLog);
}
function info(...toLog) {
console.info(LOG_PREFIX, ...toLog);
}
function debug(...toLog) {
console.debug(LOG_PREFIX, ...toLog);
}
function notify(notificationMessage) {
mw.notify(notificationMessage, {
title: USERSCRIPT_NAME
});
}
function errorAndNotify(errorMessage, exception) {
error(errorMessage, exception);
notify(errorMessage);
}
function createTimestampWikitext(timestamp) {
// https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##time_format_like_in_signatures
return `\u007B\u007Bsubst:#time:H:i, j xg Y "(UTC)"|${timestamp}}}`;
}
function createWikitext(template, user, timestamp) {
const timestampWikitext = createTimestampWikitext(timestamp);
return `{{${template}|${user}|${timestampWikitext}}}`;
}
function createErrorSpan(errorMessage) {
const errorSpan = document.createElement('span');
errorSpan.style.color = 'maroon';
const prefix = document.createElement('b');
prefix.appendChild(document.createTextNode(locale.errorPrefix));
errorSpan.appendChild(prefix);
errorSpan.appendChild(document.createTextNode(errorMessage));
return errorSpan;
}
function createPreview(wikitext) {
const previewContainer = document.createElement('div');
const previewPrefix = document.createElement('span');
previewPrefix.appendChild(document.createTextNode(locale.previewPrefix));
previewContainer.appendChild(previewPrefix);
const previewProper = document.createElement('div');
previewContainer.appendChild(previewProper);
const query = {
action: 'parse',
prop: ['text'],
pst: true, // PST = pre-save transform; this makes substitution work properly
preview: true,
disablelimitreport: true,
disableeditsection: true,
disablestylededuplication: true,
text: wikitext,
title: mw.config.get('wgPageName'),
};
const api = new mw.Api();
api.get(query).then(
response => {
// debug('Q:', query);
// debug('R:', response);
// previewDiv.appendChild(createErrorSpan('example of an error message'));
previewProper.innerHTML = response.parse.text['*'];
},
rejection => {
previewContainer.appendChild(createErrorSpan(rejection));
}
);
return previewContainer;
}
/*
* Adapted from [[User:Enterprisey/diff-permalink.js]]
* https://en.wikipedia.org/wiki/User:Enterprisey/diff-permalink.js
*/
function showWikitextAboveBodyContent(wikitext) {
info(wikitext);
const wikitextInput = document.createElement('input');
wikitextInput.id = USERSCRIPT_OUTPUT_ID;
wikitextInput.value = wikitext;
wikitextInput.style.fontFamily = 'monospace';
wikitextInput.setAttribute('size', wikitext.length);
const copyButton = document.createElement('button');
copyButton.textContent = locale.copyButtonText;
copyButton.style.padding = '0.5em';
copyButton.style.cursor = 'pointer';
copyButton.style.marginLeft = '0.5em';
copyButton.onclick = () => {
document.getElementById(USERSCRIPT_OUTPUT_ID).select();
document.execCommand('copy');
};
const container = document.createElement('div');
container.appendChild(document.createTextNode(locale.prefixText));
container.appendChild(wikitextInput);
container.appendChild(copyButton);
const preview = createPreview(wikitext);
container.appendChild(preview);
document.getElementById('bodyContent').prepend(container);
}
function runPortletOnDiff() {
/*
* Reference documentation about keys and values in mw.config:
* https://www.mediawiki.org/wiki/Manual:Interface/JavaScript#mw.config
*/
const diffTimestampElement = document.querySelector('#mw-diff-ntitle1 .mw-diff-timestamp');
if (!diffTimestampElement) {
errorAndNotify(locale.errorTimestamp);
return;
}
const mwUserlink = document.querySelector('#mw-diff-ntitle2 .mw-userlink');
if (!mwUserlink) {
errorAndNotify(locale.errorAuthor);
return;
}
const template = mwUserlink.classList.contains('mw-anonuserlink') ? locale.templateIp : locale.template;
const usernameOrIp = mwUserlink.innerText;
const isoTimestamp = diffTimestampElement.getAttribute('data-timestamp');
const wikitext = createWikitext(template, usernameOrIp, isoTimestamp);
showWikitextAboveBodyContent(wikitext);
}
function runPortletOnPermalink() {
const params = new URLSearchParams(document.location.search);
if (params.get('oldid') === null) {
warn('No oldid in the URL. Bug in script?');
return;
}
const title = mw.config.get('wgPageName');
const revisionId = mw.config.get("wgRevisionId");
const api = new mw.Api();
const queryParams = {
action: 'query',
prop: 'revisions',
rvprop: 'ids|user|timestamp',
rvslots: 'main',
formatversion: 2, // v2 has nicer field names in responses
titles: title,
rvstartid: revisionId,
rvendid: revisionId,
};
api.get(queryParams).then(
response => {
// debug('Q:', queryParams);
// debug('R:', response);
const revision = response?.query?.pages[0]?.revisions[0];
if (!revision) {
errorAndNotify(`Cannot parse response to query ${queryParams} (getting ${revisionId} for page ${title}).`);
return;
}
const template = revision.anon === true ? locale.templateIp : locale.template;
const usernameOrIp = revision.user;
const isoTimestamp = revision.timestamp;
const wikitext = createWikitext(template, usernameOrIp, isoTimestamp);
showWikitextAboveBodyContent(wikitext);
},
rejection => {
errorAndNotify(`Cannot load revision ${revisionId} for page ${title}.`, rejection);
}
);
}
/*
* The main function of the script.
*/
function runPortlet() {
if (mw.config.get('wgDiffNewId') === null && mw.config.get('wgDiffOldId') === null) {
runPortletOnPermalink();
} else {
runPortletOnDiff();
}
}
function wait(message) {
info(message);
setTimeout(lazyLoadUnsignedGenerator, 200);
}
/*
* Infrastructure to ensure the script can run.
*/
function lazyLoadUnsignedGenerator() {
const params = new URLSearchParams(document.location.search);
if (mw.config.get('wgDiffNewId') === null && mw.config.get('wgDiffOldId') === null && params.get('oldid') === null) {
info('Not a diff or permalink view. Aborting.');
return;
}
const namespaceId = mw.config.get('wgNamespaceNumber');
if (namespaceId % 2 == 0 && namespaceId != 4) {
// not a talk page and not project namespace
info('Not a discussion namespace. Aborting.');
return;
}
if (!mw.loader.using) {
wait('Function mw.loader.using is no loaded yet. Waiting...');
return;
}
debug('Loading...');
mw.loader.using(
['mediawiki.util'],
() => {
const link = mw.util.addPortletLink('p-cactions', '#', locale.portletText, 'ca-unsigned-generator', locale.portletTooltip);
if (!link) {
info('Cannot create portlet link (mw.util.addPortletLink). Assuming unsupported skin. Aborting.');
return;
}
link.onclick = event => {
mw.loader.using('mediawiki.api', runPortlet);
};
},
(e) => {
error('Cannot add portlet link', e);
}
);
}
if (document.readyState !== 'loading') {
debug('document.readyState =', document.readyState);
lazyLoadUnsignedGenerator();
} else {
warn('Cannot load yet. Setting up a listener...');
document.addEventListener('DOMContentLoaded', lazyLoadUnsignedGenerator);
}
})();