User:TheAstorPastor/common.js

From Test Wiki

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
// Only run on Test Wiki:Request for Permissions page
if (mw.config.get('wgTitle') === 'Request for permissions' && 
    mw.config.get('wgNamespaceNumber') === 4) { // NS 4 is "Project" namespace (Test Wiki:)

    $(document).ready(function() {
        // Initialize the script
        testWikiRfPManager.init();
    });

    const testWikiRfPManager = {
        // Configuration
        config: {
            // Rights that can be assigned with corresponding templates
            rights: {
                'sysop': {
                    displayName: 'Administrator',
                    template: '{{administrator granted}} [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)'
                },
                'bureaucrat': {
                    displayName: 'Bureaucrat',
                    template: '{{bureaucrat granted}} [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)'
                },
                'interface-admin': {
                    displayName: 'Interface Administrator',
                    template: '{{interface administrator granted}} [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)'
                },
                'non-stewardsuppress': {
                    displayName: 'Suppressor',
                    template: '{{done}}. [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)'
                },
                'abusefilter-admin': {
                    displayName: 'Abuse Filter Administrator',
                    template: '{{done}}. [[User:TheAstorPastor|<span style="font-family:Segoe print; color:#8B0000; text-shadow:gray 0.2em 0.2em 0.4em;">The AP </span>]] ([[User talk:TheAstorPastor|<span style="font-family:Segoe print; color:#AA336A">''talk''</span>]]) 13:57, 11 April 2025 (UTC)'
                }
            }
        },

        // Initialize the script
        init: function() {
            // Check if user has the required permissions
            if (!mw.config.get('wgUserGroups').includes('sysop')) {
                console.log('TestWiki RfP Manager: User does not have sysop rights');
                return;
            }

            // Load required styles
            this.loadStyles();
            
            // Process each RfP section
            this.processRfPSections();
        },

        // Load CSS styles for the script
        loadStyles: function() {
            mw.loader.addStyleTag(`
                .twrm-container {
                    background-color: #f8f9fa;
                    border: 1px solid #a2a9b1;
                    border-radius: 3px;
                    padding: 12px;
                    margin: 10px 0;
                    box-shadow: 0 1px 2px rgba(0,0,0,0.1);
                }
                .twrm-header {
                    font-weight: bold;
                    margin-bottom: 8px;
                    color: #222;
                    font-size: 14px;
                    padding-bottom: 5px;
                    border-bottom: 1px solid #eaecf0;
                }
                .twrm-controls {
                    display: flex;
                    flex-wrap: wrap;
                    gap: 10px;
                    align-items: center;
                    margin-bottom: 8px;
                }
                .twrm-select {
                    padding: 6px 8px;
                    border: 1px solid #a2a9b1;
                    border-radius: 2px;
                    background-color: white;
                    min-width: 200px;
                }
                .twrm-button {
                    padding: 6px 12px;
                    background-color: #36c;
                    color: white;
                    border: none;
                    border-radius: 2px;
                    cursor: pointer;
                    font-weight: bold;
                }
                .twrm-button:hover {
                    background-color: #447ff5;
                }
                .twrm-button:disabled {
                    background-color: #c8ccd1;
                    cursor: not-allowed;
                }
                .twrm-result {
                    margin-top: 10px;
                    padding: 8px;
                    background-color: #eaf3ff;
                    border: 1px solid #c8ccd1;
                    border-radius: 2px;
                    display: none;
                }
                .twrm-copy-button {
                    padding: 4px 8px;
                    background-color: #f8f9fa;
                    border: 1px solid #a2a9b1;
                    border-radius: 2px;
                    margin-top: 8px;
                    cursor: pointer;
                }
                .twrm-copy-button:hover {
                    background-color: #eaecf0;
                }
                .twrm-user-info {
                    margin-bottom: 8px;
                    font-style: italic;
                }
                .twrm-success {
                    color: #14866d;
                    font-weight: bold;
                    margin-top: 5px;
                    display: none;
                }
            `);
        },

        // Process all RfP sections on the page
        processRfPSections: function() {
            const self = this;
            
            // Find all h2 headers which typically denote user request sections
            $('.mw-parser-output > h2').each(function() {
                const $header = $(this);
                const username = $header.find('.mw-headline').text().trim();
                
                if (username) {
                    // Find the section content
                    let $section = $header.nextUntil('h2, h1');
                    
                    // Extract requested right from the section
                    const requestedRight = self.extractRequestedRight($section);
                    
                    // Create rights manager UI
                    self.createRightsManagerUI($header, username, requestedRight);
                }
            });
        },

        // Extract the requested right from section content
        extractRequestedRight: function($section) {
            let rightText = '';
            
            // Look for the "Requested right" line
            $section.each(function() {
                const text = $(this).text();
                if (text.includes('Requested right')) {
                    // Extract the right from the text
                    const match = text.match(/Requested right[:\s]*([^*\n]+)/i);
                    if (match && match[1]) {
                        rightText = match[1].trim();
                    }
                    return false; // Break the loop
                }
            });

            // Map common variations to our defined rights
            const rightMap = {
                'administrator': 'sysop',
                'admin': 'sysop',
                'sysop': 'sysop',
                'bureaucrat': 'bureaucrat',
                'crat': 'bureaucrat',
                'interface administrator': 'interface-admin',
                'interface admin': 'interface-admin',
                'suppressor': 'non-stewardsuppress',
                'suppress': 'non-stewardsuppress',
                'abuse filter administrator': 'abusefilter-admin',
                'abusefilter admin': 'abusefilter-admin',
                'abuse filter admin': 'abusefilter-admin'
            };
            
            // Try to match the requested right
            for (const [key, value] of Object.entries(rightMap)) {
                if (rightText.toLowerCase().includes(key.toLowerCase())) {
                    return value;
                }
            }
            
            return ''; // No match found
        },

        // Create the UI for managing rights
        createRightsManagerUI: function($header, username, preselectedRight) {
            const self = this;
            
            // Create container
            const $container = $('<div>')
                .addClass('twrm-container')
                .insertAfter($header);
                
            // Create header
            $('<div>')
                .addClass('twrm-header')
                .text('Rights Manager')
                .appendTo($container);
                
            // Add user info
            $('<div>')
                .addClass('twrm-user-info')
                .text(`Request by: ${username}`)
                .appendTo($container);
                
            // Create controls container
            const $controls = $('<div>')
                .addClass('twrm-controls')
                .appendTo($container);
                
            // Create right selection dropdown
            const $select = $('<select>')
                .addClass('twrm-select')
                .appendTo($controls);
                
            // Add default option
            $('<option>')
                .val('')
                .text('Select right to grant...')
                .appendTo($select);
                
            // Add options for each available right
            for (const [right, config] of Object.entries(this.config.rights)) {
                $('<option>')
                    .val(right)
                    .text(config.displayName)
                    .prop('selected', right === preselectedRight)
                    .appendTo($select);
            }
            
            // Create grant button
            const $grantButton = $('<button>')
                .addClass('twrm-button')
                .text('Grant Rights')
                .prop('disabled', !preselectedRight)
                .appendTo($controls);
                
            // Create API button
            const $apiButton = $('<button>')
                .addClass('twrm-button')
                .text('Grant via API')
                .prop('disabled', !preselectedRight)
                .appendTo($controls);
                
            // Create result container
            const $result = $('<div>')
                .addClass('twrm-result')
                .appendTo($container);
                
            // Create copy button
            const $copyButton = $('<button>')
                .addClass('twrm-copy-button')
                .text('Copy to Clipboard')
                .appendTo($result);
                
            // Create success message
            const $success = $('<div>')
                .addClass('twrm-success')
                .text('Rights granted successfully!')
                .appendTo($container);
                
            // Handle select change
            $select.on('change', function() {
                const hasSelection = $(this).val() !== '';
                $grantButton.prop('disabled', !hasSelection);
                $apiButton.prop('disabled', !hasSelection);
                $result.hide();
                $success.hide();
            });
            
            // Handle grant button click
            $grantButton.on('click', function() {
                const selectedRight = $select.val();
                if (selectedRight && self.config.rights[selectedRight]) {
                    // Show template response
                    $result.show().text(self.config.rights[selectedRight].template);
                }
            });
            
            // Handle API button click
            $apiButton.on('click', function() {
                const selectedRight = $select.val();
                if (selectedRight && self.config.rights[selectedRight]) {
                    // Set button to loading state
                    const originalText = $apiButton.text();
                    $apiButton.text('Processing...').prop('disabled', true);
                    
                    // Grant the right using MediaWiki API
                    self.grantRightViaAPI(username, selectedRight)
                        .then(function(success) {
                            if (success) {
                                $success.show();
                                $result.show().text(self.config.rights[selectedRight].template);
                            } else {
                                $result.show().text('Error granting rights. Please try again or use manual method.');
                            }
                        })
                        .catch(function() {
                            $result.show().text('Error granting rights. Please try again or use manual method.');
                        })
                        .finally(function() {
                            // Reset button state
                            $apiButton.text(originalText).prop('disabled', false);
                        });
                }
            });
            
            // Handle copy button click
            $copyButton.on('click', function() {
                const textToCopy = $result.text();
                navigator.clipboard.writeText(textToCopy).then(function() {
                    $copyButton.text('Copied!');
                    setTimeout(function() {
                        $copyButton.text('Copy to Clipboard');
                    }, 2000);
                });
            });
        },

        // Grant rights via MediaWiki API
        grantRightViaAPI: function(username, right) {
            return new Promise(function(resolve) {
                // Get API token
                mw.user.getToken('csrf').then(function(token) {
                    // Make API request to grant right
                    new mw.Api().postWithToken('csrf', {
                        action: 'userrights',
                        user: username,
                        add: right,
                        reason: 'Granted per request on [[Test Wiki:Request for Permissions]]',
                        token: token
                    })
                    .done(function() {
                        resolve(true);
                    })
                    .fail(function() {
                        resolve(false);
                    });
                });
            });
        }
    };
}