['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$(async function () {
let section = $('input[name="wpSection"]').val();
if (section === 'new') return;
if (section?.startsWith('T-')) {
section = section.slice(2);
}
if (section === '0') {
section = null;
}
let dependencies = [
'jquery.textSelection', 'oojs-ui-core',
'oojs-ui.styles.icons-editing-core'
];
if (section) {
dependencies.push('mediawiki.api');
}
await mw.loader.using(dependencies);
mw.loader.addStyleTag('.diff > tbody > tr{position:relative} .diffundo{position:absolute;inset-inline-end:0;bottom:0} tr:not(:hover) > td > .diffundo:not(:focus-within){opacity:0} .diffundo-undone{text-decoration:line-through;opacity:0.5}');
let idxMap = new WeakMap(), offset = 0, rev;
let handler = button => {
let $row = button.$element.closest('tr');
let numRow = $row.prevAll().get().find(row => idxMap.has(row));
if (!numRow) {
mw.notify(`Couldn't get the line number.`, {
tag: 'diffundo',
type: 'error'
});
return;
}
let isUndone = $row.hasClass('diffundo-undone');
let $toReplace = $row.children(isUndone ? '.diff-deletedline' : '.diff-addedline');
let $toRestore = $row.children(isUndone ? '.diff-addedline' : '.diff-deletedline');
let isInsert = !$toReplace.length;
let isRemove = !$toRestore.length;
let $midLines = $row.prevUntil(numRow).map(function () {
return this.querySelector(
this.classList.contains('diffundo-undone')
? ':scope > .diff-deletedline'
: ':scope > .diff-context, :scope > .diff-addedline'
);
});
let lineIdx = idxMap.get(numRow) + $midLines.length;
let $textarea = $('#wpTextbox1');
let lines = $textarea.textSelection('getContents').split('\n');
let canUndo;
if (isInsert) {
canUndo = !$midLines.length ||
lines[lineIdx - 1] === $midLines[0].textContent;
} else {
canUndo = lines[lineIdx] === $toReplace.text();
}
if (!canUndo) {
mw.notify('The line has been modified since the diff.', {
tag: 'diffundo',
type: 'warn'
});
return;
}
let coords = [window.scrollX, window.scrollY];
let [start, end] = $textarea.textSelection('getCaretPosition', { startAndEnd: true });
let beforeLen = lines.slice(0, lineIdx).join('').length + lineIdx;
if (isRemove) {
let toReplaceLen = lines[lineIdx].length;
lines.splice(lineIdx, 1);
[start, end] = [start, end].map(idx => {
if (idx > beforeLen + toReplaceLen) {
return idx - toReplaceLen - 1;
} else if (idx > beforeLen) {
return beforeLen;
}
return idx;
});
$row.nextAll().each(function () {
if (idxMap.has(this)) {
idxMap.set(this, idxMap.get(this) - 1);
}
});
} else if (isInsert) {
let text = $toRestore.text();
lines.splice(lineIdx, 0, text);
[start, end] = [start, end].map(idx => {
if (idx > beforeLen) {
return idx + text.length + 1;
}
return idx;
});
$row.nextAll().each(function () {
if (idxMap.has(this)) {
idxMap.set(this, idxMap.get(this) + 1);
}
});
} else {
let toReplaceLen = lines[lineIdx].length;
let text = $toRestore.text();
lines.splice(lineIdx, 1, text);
[start, end] = [start, end].map(idx => {
if (idx > beforeLen + toReplaceLen) {
return idx - (toReplaceLen - text.length);
} else if (idx > beforeLen) {
return beforeLen;
}
return idx;
});
}
$textarea.textSelection('setContents', lines.join('\n'));
$textarea.textSelection('setSelection', { start, end })
.textSelection('scrollToCaretPosition');
$row.toggleClass('diffundo-undone', !isUndone);
window.scrollTo(...coords);
setTimeout(() => {
button.focus();
});
};
let updateOffset = async () => {
if (rev) {
let { query } = await new mw.Api().get({
action: 'query',
titles: mw.config.get('wgPageName'),
prop: 'info',
formatversion: 2
});
if (query.pages[0].lastrevid === rev) return;
}
let { parse } = await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'revid|sections|wikitext',
formatversion: 2
});
let charOffset = parse.sections.find(s => s.index === section)?.byteoffset;
if (!charOffset && charOffset !== 0) {
mw.notify(`Couldn't get the section offset.`, {
tag: 'diffundo',
type: 'error'
});
return false;
}
offset = charOffset
? [...parse.wikitext].slice(0, charOffset - 1).join('').split('\n').length
: 0;
rev = parse.revid;
};
mw.hook('wikipage.diff').add(async $diff => {
let $lineNums = $diff.find('.diff-lineno:last-child');
if (!$lineNums.length ||
section && (await updateOffset() === false || !$diff[0].isConnected)
) {
return;
}
$lineNums.each(function () {
let num = this.textContent.replace(/\D/g, '');
if (!num) return;
idxMap.set(this.parentElement, num - 1 - offset);
});
$diff.find('.diff-addedline, .diff-empty.diff-side-added').append(() => {
let button = new OO.ui.ButtonWidget({
classes: ['diffundo'],
framed: false,
icon: 'undo',
title: 'Undo this line'
});
return button.on('click', handler, [button]).$element;
});
});
});