User:CFA/scripts/attributetranslation.js

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.
(function () {
    'use strict';
	const ts = Date.now() + (29 * 24 * 60 * 60 * 1000);
	const pageTitle = mw.config.get('wgPageName').replace(/_/g, ' ');
    function createInputGUI(contributors) {
        var gui = document.createElement('div');
        gui.style.position = 'fixed';
        gui.style.top = '50%';
        gui.style.left = '50%';
        gui.style.transform = 'translate(-50%, -50%)';
        gui.style.backgroundColor = '#f8f9fa';
        gui.style.border = '1px solid #a2a9b1';
        gui.style.padding = '20px';
        gui.style.zIndex = 10000;
        gui.style.overflowY = 'auto';
        gui.style.maxHeight = '80vh';
        gui.innerHTML = `
            <label for="langCode" style="font-weight:bold">Language code</label>
            <p style="font-size: 50%;">The two-letter language code of the Wikipedia that this article was translated from.</p>
            <input type="text" id="langCode" placeholder="e.g. zh, es, fr"><br><br>
            <label for="articleName" style="font-weight:bold">Article name</label>
            <p style="font-size: 50%;">The name of the page on the other Wikipedia that this article was translated from.</p>
            <input type="text" id="articleName" placeholder="e.g. 维基百科"><br><br>
            <label for="addTranslatedPageTemplate" style="font-weight:bold">
                <input checked type="checkbox" id="addTranslatedPageTemplate"> Add {{Translated page}} to talk page
            </label>
            <p style="font-size: 50%;margin-bottom:1rem;">Add <a href="/wiki/Template:Translated page">{{Translated page}}</a> to the talk page <a href="/wiki/Help:Translation#License requirements">if</a> the translated content is significant.</p>
            <label style="font-weight:bold">Warn users</label>
            <p style="font-size: 50%;">Send a notice to the user(s) who added the non-attributed text.</p>
            <div id="contributorsContainer">
                <div id="contributorsList"></div>
            </div><br>
            <button id="submitTranslation">Submit</button>
            <button id="closeGUI">Close</button>
            <div id="logContainer" style="display: none; margin-top: 20px; border-top: 1px solid #a2a9b1; padding-top: 10px;">
                <h3>Log</h3>
                <ul id="logList" style="list-style-type: none; padding-left: 0;"></ul>
            </div>
        `;

        document.body.appendChild(gui);

        var contributorsList = document.getElementById('contributorsList');
        if (!contributorsList) {
            console.error('Contributors list container not found.');
            return;
        }

        contributors.forEach(function (contributor) {
            var checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.style = "transform: scale(0.75);margin-right:3px;";
            checkbox.name = 'contributors';
            checkbox.value = contributor.username;
            var label = document.createElement('label');
            label.style = "font-size:75%";
            label.appendChild(checkbox);
            label.appendChild(document.createTextNode(`${contributor.username}${contributor.edits} edit${contributor.edits === 1 ? '' : 's'}${contributor.isCreator ? ' (Page Creator)' : ''}`));
            label.appendChild(document.createElement('br'));
            contributorsList.appendChild(label);
        });

        document.getElementById('submitTranslation').addEventListener('click', function () {
            var langCode = document.getElementById('langCode').value.trim();
            var articleName = document.getElementById('articleName').value.trim();
            var selectedContributors = Array.from(document.querySelectorAll('input[name=contributors]:checked')).map(function (checkbox) {
                return checkbox.value;
            });
            var addTemplate = document.getElementById('addTranslatedPageTemplate').checked;

            if (langCode && articleName) {
                processTranslation(langCode, articleName, selectedContributors, addTemplate);
                document.getElementById('logContainer').style.display = 'block';
            } else {
                alert('Please fill in both fields.');
            }
        });


        document.getElementById('closeGUI').addEventListener('click', function () {
            document.body.removeChild(gui);
        });
    }

    function processTranslation(langCode, articleName, selectedContributors, addTemplate) {
        var editSummary = `This article contains content translated from the Wikipedia article at [[${langCode}:${articleName}]]; see its history for attribution. ([[User:Clearfrienda/scripts/AttributeTranslation|AT]])`;

        var tasksCompleted = 0;
        var totalTasks = 1 + (addTemplate ? 1 : 0) + selectedContributors.length;
        var logList = document.getElementById('logList');

        function addLogEntry(message, success) {
            var listItem = document.createElement('li');
            listItem.style.display = 'flex';
            listItem.style.alignItems = 'center';

            var checkmark = document.createElement('span');
            checkmark.style.display = 'inline-block';
            checkmark.style.width = '16px';
            checkmark.style.height = '16px';
            checkmark.style.marginRight = '8px';
            checkmark.style.backgroundColor = success ? 'green' : 'red';
            checkmark.style.borderRadius = '50%';

            listItem.appendChild(checkmark);
            listItem.appendChild(document.createTextNode(message));
            logList.appendChild(listItem);
        }

        function checkCompletion() {
            tasksCompleted++;
            if (tasksCompleted === totalTasks) {
                setTimeout(() => {
                    location.reload();
                }, 1000);
            }
        }

        // Dummy edit
        $.ajax({
            url: mw.util.wikiScript('api'),
            type: 'GET',
            data: {
                format: 'json',
                action: 'query',
                prop: 'revisions',
                rvprop: 'content',
                titles: pageTitle
            },
            success: function (data) {
                var pages = data.query.pages;
                var pageContent = '';
                for (var pageId in pages) {
                    if (pages.hasOwnProperty(pageId)) {
                        pageContent = pages[pageId].revisions[0]['*'];
                    }
                }
                pageContent += '\n​';

                $.ajax({
                    url: mw.util.wikiScript('api'),
                    type: 'POST',
                    data: {
                        format: 'json',
                        action: 'edit',
                        title: pageTitle,
                        text: pageContent,
                        summary: editSummary,
                        minor: true,
                        watchlist: 'nochange',
                        // watchlistExpiry: '2 weeks',
                        token: mw.user.tokens.get('csrfToken')
                    },
                    success: function () {
                        console.log('Dummy edit successful');
                        addLogEntry('Added dummy edit to article', true);
                        checkCompletion();
                    },
                    error: function (jqXHR, textStatus, errorThrown) {
                        console.error('Error performing dummy edit:', textStatus, errorThrown);
                        addLogEntry('Failed to add dummy edit to article', false);
                        checkCompletion();
                    }
                });
            },
            error: function (jqXHR, textStatus, errorThrown) {
                console.error('Error retrieving page content:', textStatus, errorThrown);
                addLogEntry('Failed to retrieve page content', false);
                checkCompletion();
            }
        });

        // Add {{Translated page}} template
        if (addTemplate) {
            var talkPageTitle = 'Talk:' + pageTitle;

            $.ajax({
                url: mw.util.wikiScript('api'),
                type: 'POST',
                data: {
                    format: 'json',
                    action: 'edit',
                    title: talkPageTitle,
                    summary: `Adding {{Translated page}} attribution ([[User:Clearfrienda/scripts/AttributeTranslation|AT]])`,
                    section: 'new',
                    watchlist: 'nochange',
                    // watchlistExpiry: '2 weeks',
                    sectiontitle: '',
                    text: `{{Translated page|${langCode}|${articleName}|small=no}}`,
                    token: mw.user.tokens.get('csrfToken')
                },
                success: function () {
                    console.log('Article talk page updated');
                    addLogEntry('Added template to talk page', true);
                    checkCompletion();
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    console.error('Error updating article talk page:', textStatus, errorThrown);
                    addLogEntry('Failed to add template to talk page', false);
                    checkCompletion();
                }
            });
        }

        // Notify contributors
        selectedContributors.forEach(function (contributor) {
            notifyContributor(contributor, langCode, articleName);
        });

        function notifyContributor(contributor, langCode, articleName) {
            var subst = "subst:";
            var sig = "~~" + "~~";
            var notificationMessage = `{{${subst}uw-translation|1=${langCode}:${articleName}|to=${pageTitle}}} ${sig}`;

            $.ajax({
                url: mw.util.wikiScript('api'),
                type: 'POST',
                data: {
                    format: 'json',
                    action: 'edit',
                    title: 'User talk:' + contributor,
                    summary: `Adding non-attributed translation notice ([[User:Clearfrienda/scripts/AttributeTranslation|AT]])`,
                    section: 'new',
                    watchlist: 'nochange',
                    // watchlistExpiry: '2 weeks',
                    sectiontitle: 'Non-attributed translations',
                    text: notificationMessage,
                    token: mw.user.tokens.get('csrfToken')
                },
                success: function () {
                    console.log('Notification sent to user talk page');
                    addLogEntry(`Warned user (${contributor})`, true);
                    checkCompletion();
                },
                error: function (jqXHR, textStatus, errorThrown) {
                    console.error('Error sending notification:', textStatus, errorThrown);
                    addLogEntry(`Failed to warn user (${contributor})`, false);
                    if (confirm("Could not notify contributor. Retry?")) {
                        notifyContributor(contributor, langCode, articleName);
                    } else {
                        checkCompletion();
                    }
                }
            });
        }
    }

    function getPageContributors(pageTitle, callback) {
        $.ajax({
            url: mw.util.wikiScript('api'),
            type: 'GET',
            data: {
                format: 'json',
                action: 'query',
                prop: 'revisions',
                rvprop: 'user',
                titles: pageTitle,
                rvlimit: 'max',
                rvdir: 'newer'
            },
            success: function (data) {
                var pages = data.query.pages;
                var contributors = [];
                for (var pageId in pages) {
                    if (pages.hasOwnProperty(pageId)) {
                        var revisions = pages[pageId].revisions;
                        var creator = true;
                        revisions.forEach(function (revision) {
                            if (revision.user && revision.user !== '') {
                                var contributorIndex = contributors.findIndex(function (item) {
                                    return item.username === revision.user;
                                });
                                if (contributorIndex === -1) {
                                    contributors.push({ username: revision.user, edits: 1, isCreator: creator });
                                } else {
                                    contributors[contributorIndex].edits++;
                                }
                                creator = false;
                            }
                        });
                    }
                }
                callback(contributors);
            },
            error: function (jqXHR, textStatus, errorThrown) {
                console.error('Error retrieving page contributors:', textStatus, errorThrown);
            }
        });
    }

    function addButtonToToolbar() {
        var allowedNamespaces = [0, 118];

        var currentNamespace = mw.config.get('wgNamespaceNumber');

        if (allowedNamespaces.includes(currentNamespace)) {
            mw.util.addPortletLink(
                'p-cactions',
                '#',
                'Translation attribution',
                'ca-translation-attribution',
                'AttributeTranslation',
                null,
                null
            );

            document.getElementById('ca-translation-attribution').addEventListener('click', function () {
                getPageContributors(pageTitle, function (contributors) {
                    createInputGUI(contributors);
                });
            });
        }
    }

    addButtonToToolbar();
})();