['edit', 'submit'].includes(mw.config.get('wgAction')) &&
$.when($.ready, mw.loader.using('jquery.textSelection')).then(function () {
let section = $('input[name="wpSection"]').val();
if (section === 'new') return;
if (section?.startsWith('T-')) {
section = section.slice(2);
}
if (section === '0') {
section = null;
}
mw.loader.addStyleTag('.diffundo-undone{text-decoration:line-through;opacity:0.5}');
let idxMap = new WeakMap(), offset = 0;
let handler = function (e) {
if (e.type === 'keydown' && (
e.which !== 13 || this !== e.target ||
e.ctrlKey || e.shiftKey || e.metaKey || e.altKey
)) {
return;
}
e.preventDefault();
let $row = $(this);
let numRow = $row.prevAll().get().find(row => idxMap.has(row));
let chunkIdx = idxMap.get(numRow);
if (!chunkIdx && chunkIdx !== 0) {
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 = chunkIdx + $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 [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);
};
mw.hook('wikipage.diff').add(async $diff => {
let $lineNums = $diff.find('.diff-lineno:last-child');
if (!$lineNums.length) return;
if (section) {
await mw.loader.using('mediawiki.api');
let response = await new mw.Api().get({
action: 'parse',
page: mw.config.get('wgPageName'),
prop: 'sections|wikitext',
formatversion: 2
});
if (!$diff[0].isConnected) return;
let charOffset = response.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;
}
offset = charOffset
? [...response.parse.wikitext].slice(0, charOffset - 1)
.join('').split('\n').length
: 0;
}
$lineNums.each(function () {
idxMap.set(this.parentElement, this.textContent.replace(/\D/g, '') - 1 - offset);
});
$diff.find('.diff-deletedline, .diff-addedline').parent().attr({
tabindex: 0,
role: 'button'
}).on('dblclick keydown', handler);
});
});