/**
* Description: Restore deleted lines easier while you are editing and viewing changes
* Documentation: [[User:Aram/diff restorer]]
* Version: 1.0.3
*/
'use strict';
mw.loader.load('//en.wikipedia.org/w/index.php?title=User:Aram/diff_restorer.css&action=raw&ctype=text/css', 'text/css');
function getInsertPosition(element, content) {
const initialLineRow = element.closest('tr')
.prevAll('tr')
.find('td.diff-lineno:first');
if (!initialLineRow.length) return null;
const lineText = initialLineRow.text();
// Transform the text by replacing separators and converting digits
let normalizedText = lineText;
const separatorMap = mw.language.getSeparatorTransformTable();
Object.entries(separatorMap).forEach(([latin, local]) => {
normalizedText = normalizedText.replace(new RegExp(local, 'g'), latin);
});
const digitMap = mw.language.getDigitTransformTable();
digitMap.forEach((latin, index) => {
normalizedText = normalizedText.replace(new RegExp(latin, 'g'), index);
});
// Extract all numbers from the text
const numbers = normalizedText.match(/\d+/g);
if (!numbers || numbers.length === 0) return null;
// Use the last number when moving from bottom to top
const exactLineNumber = parseInt(numbers[numbers.length - 1]);
// Count non-deleted rows between current position and initial line
const prevRows = element.closest('tr')
.prevUntil(initialLineRow.closest('tr'))
.not(':has(td.diff-empty.diff-side-deleted)');
const lineNumber = exactLineNumber + prevRows.length;
const lines = content.split('\n');
// If line number exceeds content length, return end of content
if (lineNumber > lines.length) return content.length;
// Calculate insert position
return lines.slice(0, lineNumber - 1).join('\n').length + (lineNumber > 1 ? 1 : 0);
}
(function () {
function addRestoreButtons() {
$('table.diff tbody tr:not(.diff-title) .diff-side-deleted:not(.diff-context)').each(function () {
var deletedLine = $(this);
var addedLine = deletedLine.closest('tr').find('.diff-side-added');
var movedPara = deletedLine.closest('tr').find('.mw-diff-movedpara-left');
if (movedPara.length) {
var targetAnchor = movedPara.attr('href').substring(1);
addedLine = $('.diff-side-added').has('a[name="' + targetAnchor + '"]');
}
if (!addedLine.length) return;
var restoreBtn = $('<span>')
.addClass('restore-btn')
.attr('title', 'Restore this line')
.click(function () {
var editBox = $('#wpTextbox1');
if (!editBox.length || editBox.css('display') === 'none') {
mw.notify('Edit box not found. Please ensure you are in edit mode, or if you are, turn off code editor', { type: 'error', autoHide: false });
return;
}
var editContent = editBox.val();
var deletedText = deletedLine.text();
var success = false;
if (movedPara.length) {
var addedText = addedLine.text();
var currentPosition = editContent.indexOf(addedText);
if (currentPosition !== -1) {
var contentWithoutMoved = editContent.substring(0, currentPosition) +
editContent.substring(currentPosition + addedText.length +
(editContent[currentPosition + addedText.length] === '\n' ? 1 : 0));
var targetPosition = getInsertPosition(deletedLine, contentWithoutMoved);
if (targetPosition !== null) {
editBox.val(contentWithoutMoved.substring(0, targetPosition) +
deletedText + '\n' +
contentWithoutMoved.substring(targetPosition));
success = true;
}
}
} else if (addedLine.hasClass('diff-empty') || addedLine.find('br').length) {
var position = getInsertPosition(deletedLine, editContent);
if (position !== null) {
var newContent = addedLine.hasClass('diff-empty')
? editContent.substring(0, position) + deletedText + '\n' + editContent.substring(position)
: editContent.substring(0, position) + editContent.substring(position + 1);
editBox.val(newContent);
success = true;
}
} else if (editContent.includes(addedLine.text())) {
editBox.val(editContent.replace(addedLine.text(), deletedText));
success = true;
}
restoreBtn
.addClass(success ? 'restore-success' : 'restore-fail')
.off('click');
});
deletedLine.append(restoreBtn);
});
}
mw.hook('wikipage.diff').add(addRestoreButtons);
})();