Jump to content

User:Aram/diff restorer.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/**
 * 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);
})();