Jump to content

User:Max/WhyNotResolve.js

From Meta, a Wikimedia project coordination 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.
//<nowiki>
$(() => {
    const excludedNamespaces = [
        // From [[Special:NamespaceInfo]]
        6,     // File
        8,     // MediaWiki
        10,    // Template
        14,    // Category
        828,   // Module
        1198,  // Translations
    ];

    if (excludedNamespaces.includes(mw.config.get('wgNamespaceNumber'))) {
        return;
    }
    
    const ReportResolver = {
        config: {
            debug: false,
            h3Pages: (window.ReportResolverH3Pages || []).concat([
                'Meta:Requests for CheckUser information'
            ])
        },
        
        actionOptions: [
            { data: 'resolved', label: 'Mark as resolved' },
            { data: 'done', label: 'Mark as Done' },
            { data: 'notdone', label: 'Mark as Not Done' },
            { data: 'granted', label: 'Mark as Granted', rfhOnly: true },
            { data: 'blocked', label: 'Mark as Blocked', rfhOnly: true },
            { data: 'tparevoked', label: 'Mark as TPA Revoked', rfhOnly: true },
            { data: 'deleted', label: 'Mark as Deleted', rfdOnly: true },
            { data: 'kept', label: 'Mark as Kept', rfdOnly: true },
            { data: 'redirected', label: 'Mark as Redirected', rfdOnly: true },
            { data: 'comment', label: 'Comment' },
            { data: 'unresolved', label: 'Unmark as resolved' }
        ],

        sectionResolvedCache: {},

        init() {
            const currentPage = mw.config.get('wgPageName').replace(/_/g, ' ');
            if (currentPage.startsWith('Steward requests/')) return;
            
            mw.loader.using(['oojs-ui', 'mediawiki.util', 'mediawiki.api'], this.run.bind(this));
        },

        async run() {
            this.addStyle();
            this.setupSectionHandlers();
            this.addCopyIconStyle();
            await this.preloadSectionResolved();
        },
        
        addStyle() {
            mw.util.addCSS(`
                .script-resolver-handler {
                    background-color: var(--background-color-neutral-subtle, #f5f5f5);
                    border: 1px solid var(--border-color-base, #ccc);
                    margin: 10px 0;
                    padding: 10px;
                    border-radius: 5px;
                    color: var(--color-base, #000);
                }
                .script-resolver-handler .oo-ui-fieldLayout {
                    margin-bottom: 10px;
                }
                .script-resolver-handler .oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body {
                    display: flex !important;
                    align-items: center !important;
                }
                .script-resolver-handler .oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-header {
                    flex: 0 0 160px !important;
                    margin-right: 13px !important;
                }
                .script-resolver-handler .oo-ui-fieldLayout-align-left > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field {
                    flex: 1;
                }
                .script-resolver-handler .oo-ui-dropdownWidget .oo-ui-dropdownWidget-handle,
                .script-resolver-handler .oo-ui-multilineTextInputWidget {
                    width: 100% !important;
                    max-width: 400px !important;
                }
                .script-resolver-status { 
                    margin: 10px 0; 
                }
                .script-resolver-handler h4 {
                    color: inherit !important;
                }
                .script-resolver-handler h5 {
                    color: inherit !important;
                }                
                .script-resolver-preview { 
                    background-color: var(--background-color-base, white);
                    color: var(--color-base, #000);
                    border: 1px solid var(--border-color-base, #ccc);
                    padding: 10px;
                    margin: 10px 0;
                    border-radius: 3px;
                }
            `);
        },

        addCopyIconStyle() {
            mw.util.addCSS(`
                .script-resolver-copy-icons {
                    display: flex;
                    gap: 6px;
                    position: absolute;
                    right: 8px;
                    bottom: 5px;
                    z-index: 10;
                }
                .script-resolver-copy-icon {
                    width: 16px;
                    height: 16px;
                    cursor: pointer;
                    opacity: 0.7;
                    transition: opacity 0.1s;
                    background: none;
                    border: none;
                    padding: 0;
                }
                .script-resolver-copy-icon:hover {
                    opacity: 1;
                }
                .script-resolver-copy-icon-copied {
                    opacity: 1;
                    box-shadow: 0 0 2px 2px var(--border-color-success, #a2e37c);
                }
                .script-resolver-copy-tooltip {
                    position: absolute;
                    right: 24px;
                    bottom: 24px;
                    padding: 1px 6px;
                    border-radius: 2px;
                    font-size: 11px;
                    white-space: nowrap;
                    background-color: var(--background-color-base, #fff);
                    color: var(--color-base, #000);
                    border: 1px solid var(--border-color-base, #ccc);
                    box-shadow: 0 1px 3px rgba(0,0,0,0.2);
                }
            `);
        },
        
        setupSectionHandlers() {
            const currentPage = mw.config.get('wgPageName').replace(/_/g, ' ');
            const isRfDPage = currentPage.startsWith('Meta:Requests for deletion');
            const isH3Page = this.config.h3Pages.some(page => currentPage.startsWith(page));
            const headingSelector = (isRfDPage || isH3Page) ? 'div.mw-heading h3' : 'div.mw-heading h2';
            
            $(headingSelector).each((_, heading) => {
                const sectionNum = this.getSectionNumber($(heading).parent().find('.mw-editsection'));
                if (sectionNum !== -1 && $(`#script-resolver-launch-${sectionNum}`).length > 0) return;

                let editLinks = $(heading).parent().find('.mw-editsection');

                if (editLinks.length === 0) {
                    editLinks = $(heading).closest('div').parent().find('.mw-editsection');
                }
                
                if (editLinks.length === 0) {
                    editLinks = $(heading).parent().siblings('.mw-editsection');
                }
                
                if (editLinks.length === 0) {
                    editLinks = $(heading).parents().first().find('.mw-editsection');
                }
                
                if (editLinks.length > 0) {
                    const sectionNum = this.getSectionNumber(editLinks);
                    
                    if (sectionNum !== -1) {
                        const reporterName = $(heading).text().trim();
                        const nextSection = $(heading).parent().next('section');
                        if (nextSection.length > 0) {
                            nextSection.prepend($('<div>').attr('id', `script-resolver-${sectionNum}`));
                        } else {
                            $(heading).parent().after($('<div>').attr('id', `script-resolver-${sectionNum}`));
                        }
                        this.addHandlerLink(editLinks, reporterName, sectionNum);
                    }
                }
            });
        },
        
        getSectionNumber(editLinks) {
            const editSectionUrl = editLinks.find('a:first').attr('href');
            const sectionMatch = editSectionUrl && editSectionUrl.match(/&section=(\d+)(?:$|&)/);
            return sectionMatch ? parseInt(sectionMatch[1]) : -1;
        },
        
        addHandlerLink(editLinks, sectionName, sectionNum) {
            const handlerLink = $('<a>').attr('id', `script-resolver-launch-${sectionNum}`).text('WhyNotResolve');
            handlerLink.click((e) => {
                e.stopPropagation();
                if (!handlerLink.hasClass('script-resolver-disabled')) {
                    handlerLink.addClass('script-resolver-disabled');
                    this.showHandler(sectionName, sectionNum);
                }
            });
            if (editLinks.find(`#script-resolver-launch-${sectionNum}`).length === 0) {
                editLinks.children().last().before(handlerLink);
            }
        },

        async preloadSectionResolved() {
            const sections = [];
            const currentPage = mw.config.get('wgPageName').replace(/_/g, ' ');
            const isRfDPage = currentPage.startsWith('Meta:Requests for deletion');
            const isH3Page = this.config.h3Pages.some(page => currentPage.startsWith(page));
            const headingSelector = (isRfDPage || isH3Page) ? 'div.mw-heading h3' : 'div.mw-heading h2';
            
            $(headingSelector).each((_, heading) => {
                const editLinks = $(heading).parent().find('.mw-editsection');
                const sectionNum = this.getSectionNumber(editLinks);
                if (sectionNum !== -1) sections.push(sectionNum);
            });

            const api = new mw.Api();
            const promises = sections.map(sectionNum =>
                api.get({
                    action: 'parse',
                    page: mw.config.get('wgPageName'),
                    section: sectionNum,
                    prop: 'wikitext',
                    formatversion: 2
                }).then(response => {
                    this.sectionResolvedCache[sectionNum] =
                        (response && response.parse && response.parse.wikitext &&
                        (response.parse.wikitext.includes('{{section resolved') ||
                         response.parse.wikitext.includes('{{Section resolved')));
                }).catch(() => {
                    this.sectionResolvedCache[sectionNum] = false;
                })
            );
            await Promise.all(promises);
        },

        async checkSectionResolved(sectionNum) {
            if (Object.prototype.hasOwnProperty.call(this.sectionResolvedCache, sectionNum)) {
                return this.sectionResolvedCache[sectionNum];
            }
            try {
                const api = new mw.Api();
                const response = await api.get({
                    action: 'parse',
                    page: mw.config.get('wgPageName'),
                    section: sectionNum,
                    prop: 'wikitext',
                    formatversion: 2
                });
                
                if (response && response.parse && response.parse.wikitext) {
                    return response.parse.wikitext.includes('{{section resolved') ||
                           response.parse.wikitext.includes('{{Section resolved');
                }
                return false;
            } catch (error) {
                return false;
            }
        },
        
        async showHandler(sectionName, sectionNum) {
            $(`#script-resolver-${sectionNum}`).empty();
            
            let markAsResolved = true;
            const isResolved = await this.checkSectionResolved(sectionNum);
            let selectedAction = isResolved ? 'unresolved' : 'resolved';
            
            const $container = $('<div>').addClass('script-resolver-handler').css('position', 'relative');
            $container.append($('<h4>').text(`Resolving section: ${sectionName}`));
            
            const $optionsArea = $('<div>');
            if (isResolved) {
                $optionsArea.hide();
            }

            let forceCheckbox, forceLayout;
            if (isResolved) {
                $container.append($('<div>').css({
                    'background': 'var(--background-color-warning-subtle, #fffacd)',
                    'color': 'var(--color-warning, #543500)',
                    'padding': '8px',
                    'margin': '10px 0',
                    'border-radius': '3px',
                    'border': '1px solid var(--border-color-warning, #edba28)'
                })
                    .html('This section appears to be already resolved.'));
                
                forceCheckbox = new OO.ui.CheckboxInputWidget();
                forceLayout = new OO.ui.FieldLayout(forceCheckbox, {
                    label: 'Handle this resolved section anyway',
                    align: 'inline'
                });
                $container.append(forceLayout.$element);
            }
            
            const currentPage = mw.config.get('wgPageName').replace(/_/g, ' ');
            const isRfDPage = currentPage.startsWith('Meta:Requests for deletion');
            const isRfHPage = currentPage.startsWith('Meta:Requests for help from a sysop or bureaucrat');
            
            const actionItems = this.actionOptions
                .filter(opt => {
                    if (opt.data === 'unresolved') {
                        return isResolved;
                    }
                    if (opt.rfdOnly) {
                        return isRfDPage;
                    }
                    if (opt.rfhOnly) {
                        return isRfHPage;
                    }
                    return true;
                })
                .map(opt => new OO.ui.MenuOptionWidget({ data: opt.data, label: opt.label }));
            
            const actionDropdown = new OO.ui.DropdownWidget({
                label: 'Select Action',
                menu: {
                    items: actionItems
                }
            });
            actionDropdown.getMenu().selectItemByData(isResolved ? 'unresolved' : 'resolved');
            
            const actionLayout = new OO.ui.FieldLayout(actionDropdown, {
                label: 'Action:',
                align: 'left'
            });
            $optionsArea.append(actionLayout.$element);
            
            const commentInput = new OO.ui.MultilineTextInputWidget({
                placeholder: 'Optional comment',
                autosize: true
            });
            const commentLayout = new OO.ui.FieldLayout(commentInput, {
                label: 'Comment:',
                align: 'left'
            });
            $optionsArea.append(commentLayout.$element);
            
            commentInput.on('change', () => {
                if ($previewArea && $previewArea.is(':visible')) {
                    this.showPreview($previewArea, selectedAction, commentInput.getValue(), markAsResolved, sectionNum);
                }
            });            
            
            const resolvedCheckbox = new OO.ui.CheckboxInputWidget({
                selected: true
            });
            const resolvedLayout = new OO.ui.FieldLayout(resolvedCheckbox, {
                label: 'Mark section as resolved',
                align: 'inline'
            });
            $optionsArea.append(resolvedLayout.$element);
            actionDropdown.getMenu().on('choose', (item) => {
                selectedAction = item.getData();
                if (selectedAction === 'resolved') {
                    commentLayout.$element.hide();
                    resolvedCheckbox.setSelected(true);
                    resolvedCheckbox.setDisabled(true);
                } else if (selectedAction === 'unresolved') {
                    commentLayout.$element.show();
                    resolvedCheckbox.setSelected(false);
                    resolvedCheckbox.setDisabled(true);
                } else {
                    commentLayout.$element.show();
                    resolvedCheckbox.setDisabled(false);
                }
                if ($previewArea && $previewArea.is(':visible')) {
                    this.showPreview($previewArea, selectedAction, commentInput.getValue(), markAsResolved, sectionNum);
                }
            });
            if (selectedAction === 'resolved') {
                commentLayout.$element.hide();
                resolvedCheckbox.setDisabled(true);
            } else if (selectedAction === 'unresolved') {
                commentLayout.$element.show();
                resolvedCheckbox.setSelected(false);
                resolvedCheckbox.setDisabled(true);
            }           
            
            $container.append($optionsArea);

            const $copyIconsArea = $('<div>').addClass('script-resolver-copy-icons');
            const revisionId = mw.config.get('wgRevisionId');
            const sectionAnchor = sectionName.replace(/\s/g, '_');
            const wikiPermRequest = `per [[Special:Permalink/${revisionId}#${sectionAnchor}|request]]`;
            const wikiPerm = `per [[Special:Permalink/${revisionId}#${sectionAnchor}]]`;
            const icon1 = $('<button>')
                .attr('type', 'button')
                .attr('tabindex', '0')
                .addClass('script-resolver-copy-icon')
                .html('<svg width="16" height="16" viewBox="0 0 16 16"><rect x="3" y="3" width="10" height="10" rx="2" style="fill: var(--color-subtle, #888);"/><rect x="7" y="7" width="6" height="6" rx="1" style="fill: var(--background-color-base, #fff); stroke: var(--color-subtle, #888); stroke-width: 1;"/></svg>')
                .attr('aria-label', 'Copy wikitext with request');
            const icon2 = $('<button>')
                .attr('type', 'button')
                .attr('tabindex', '0')
                .addClass('script-resolver-copy-icon')
                .html('<svg width="16" height="16" viewBox="0 0 16 16"><rect x="3" y="3" width="10" height="10" rx="2" style="fill: var(--color-subtle, #888);"/><rect x="7" y="7" width="6" height="6" rx="1" style="fill: var(--background-color-base, #fff); stroke: var(--color-subtle, #888); stroke-width: 1;"/></svg>')
                .attr('aria-label', 'Copy wikitext');
            let tooltip = null;
            icon1.on('mouseenter focus', function() {
                tooltip && tooltip.remove();
                tooltip = $('<div>').addClass('script-resolver-copy-tooltip').text('Copy: request');
                $copyIconsArea.append(tooltip);
            }).on('mouseleave blur', function() {
                tooltip && tooltip.remove();
                tooltip = null;
            });
            icon2.on('mouseenter focus', function() {
                tooltip && tooltip.remove();
                tooltip = $('<div>').addClass('script-resolver-copy-tooltip').text('Copy: plain');
                $copyIconsArea.append(tooltip);
            }).on('mouseleave blur', function() {
                tooltip && tooltip.remove();
                tooltip = null;
            });
            icon1.on('click', function() {
                navigator.clipboard.writeText(wikiPermRequest);
                icon1.addClass('script-resolver-copy-icon-copied');
                setTimeout(()=>icon1.removeClass('script-resolver-copy-icon-copied'),900);
                tooltip && tooltip.remove();
                tooltip = $('<div>').addClass('script-resolver-copy-tooltip').text('Copied!');
                $copyIconsArea.append(tooltip);
                setTimeout(()=>{tooltip && tooltip.remove();},800);
            });
            icon2.on('click', function() {
                navigator.clipboard.writeText(wikiPerm);
                icon2.addClass('script-resolver-copy-icon-copied');
                setTimeout(()=>icon2.removeClass('script-resolver-copy-icon-copied'),900);
                tooltip && tooltip.remove();
                tooltip = $('<div>').addClass('script-resolver-copy-tooltip').text('Copied!');
                $copyIconsArea.append(tooltip);
                setTimeout(()=>{tooltip && tooltip.remove();},800);
            });
            $copyIconsArea.append(icon1, icon2);
            $container.append($copyIconsArea);

            const $statusArea = $('<div>').addClass('script-resolver-status').hide();
            $container.append($statusArea);
            
            const submitBtn = new OO.ui.ButtonWidget({
                label: 'Submit',
                flags: ['progressive']
            });
            const cancelBtn = new OO.ui.ButtonWidget({
                label: 'Cancel',
                flags: ['destructive']
            });
            const previewBtn = new OO.ui.ButtonWidget({
                label: 'Preview'
            });
            
            const $buttonArea = $('<div>').css('margin-top', '15px')
                .append(submitBtn.$element)
                .append(' ')
                .append(previewBtn.$element)
                .append(' ')
                .append(cancelBtn.$element);
            $container.append($buttonArea);
            
            const $previewArea = $('<div>').addClass('script-resolver-preview').hide();
            $container.append($previewArea);
            
            $(`#script-resolver-${sectionNum}`).append($container);
            
            resolvedCheckbox.on('change', (selected) => {
                markAsResolved = selected;
                if ($previewArea && $previewArea.is(':visible')) {
                    this.showPreview($previewArea, selectedAction, commentInput.getValue(), markAsResolved, sectionNum);
                }
            });

            let setButtonsDisabled = (disable) => {
                submitBtn.setDisabled(disable);
                previewBtn.setDisabled(disable);
            };

            if (isResolved) {
                setButtonsDisabled(true);
                forceCheckbox.on('change', function(selected) {
                    if (selected) {
                        $optionsArea.show();
                        setButtonsDisabled(false);
                    } else {
                        $optionsArea.hide();
                        setButtonsDisabled(true);
                    }
                });
            }
            
            previewBtn.on('click', () => {
                if (!previewBtn.isDisabled()) {
                    if ($previewArea.is(':visible')) {
                        $previewArea.hide();
                        previewBtn.setLabel('Preview');
                        previewBtn.setFlags([]);
                    } else {
                        this.showPreview($previewArea, selectedAction, commentInput.getValue(), markAsResolved, sectionNum);
                        previewBtn.setLabel('Hide');
                        previewBtn.setFlags(['progressive']);
                    }
                }
            });
            
            submitBtn.on('click', () => {
                if (isResolved && forceCheckbox && !forceCheckbox.isSelected()) {
                    OO.ui.alert('Please check the option to handle resolved sections if you want to proceed.');
                    return;
                }
                if (!submitBtn.isDisabled()) {
                    this.handleSubmit(sectionNum, selectedAction, commentInput.getValue(), markAsResolved, sectionName, $statusArea, submitBtn, previewBtn, cancelBtn);
                }
            });
            
            cancelBtn.on('click', () => {
                $(`#script-resolver-${sectionNum}`).empty();
                $(`#script-resolver-launch-${sectionNum}`).removeClass('script-resolver-disabled');
            });
        },
        
        async showPreview($previewArea, action, comment, markAsResolved, sectionNum) {
            $previewArea.show().html('<p>Loading preview...</p>');
            
            const wikitext = this.generateWikitext(action, comment, markAsResolved);
            
            if (action === 'unresolved') {
                try {
                    const api = new mw.Api();
                    const response = await api.get({
                        action: 'parse',
                        page: mw.config.get('wgPageName'),
                        section: sectionNum,
                        prop: 'wikitext',
                        formatversion: 2
                    });
                    
                    if (response && response.parse && response.parse.wikitext) {
                        const resolvedTemplateMatch = response.parse.wikitext.match(/\{\{[Ss]ection resolved\s*\|?\s*1?\s*=\s*[^}]*\}\}/g);
                        let previewHtml = '';
                        
                        if (resolvedTemplateMatch) {
                            const templateWikitext = resolvedTemplateMatch.join('\n');
                            previewHtml += '<h5>Will be removed:</h5><div style="background-color: var(--background-color-error-subtle, #ffe0e0); color: var(--color-error, #7a0000); padding: 8px; border: 1px solid var(--border-color-error, #ffaaaa); border-radius: 3px; margin-bottom: 10px;"><code>' + 
                                templateWikitext.replace(/</g, '&lt;').replace(/>/g, '&gt;') + 
                                '</code></div>';
                        }
                        
                        if (wikitext) {
                            const parseResponse = await api.get({
                                action: 'parse',
                                formatversion: 2,
                                title: mw.config.get('wgPageName'),
                                text: wikitext,
                                prop: 'text',
                                pst: true,
                                disablelimitreport: true,
                                disableeditsection: true,
                                sectionpreview: true
                            });
                            
                            if (parseResponse && parseResponse.parse && parseResponse.parse.text) {
                                previewHtml += '<h5>Will be added:</h5>' + parseResponse.parse.text;
                            }
                        }
                        
                        if (previewHtml) {
                            $previewArea.html(previewHtml);
                        } else {
                            $previewArea.html('<p>No section resolved template found.</p>');
                        }
                    } else {
                        $previewArea.html('<p>Could not load section content.</p>');
                    }
                } catch (error) {
                    $previewArea.html('<p>Error loading preview.</p>');
                }
                return;
            }
            
            if (!wikitext) {
                $previewArea.html('<p>No content to preview.</p>');
                return;
            }
            
            const api = new mw.Api();
            api.get({
                action: 'parse',
                formatversion: 2,
                title: mw.config.get('wgPageName'),
                text: wikitext,
                prop: 'text',
                pst: true,
                disablelimitreport: true,
                disableeditsection: true,
                sectionpreview: true
            }).then((response) => {
                if (response && response.parse && response.parse.text) {
                    $previewArea.html('<h5>Preview:</h5>' + response.parse.text);
                } else {
                    $previewArea.html('<p>Error loading preview.</p>');
                }
            }).catch(() => {
                $previewArea.html('<p>Error loading preview.</p>');
            });
        },
        
        generateWikitext(action, comment, markAsResolved) {
            let wikitext = '';
            const timestamp = '~~~~';
            
            if (action === 'resolved') {
                wikitext = `{{Section resolved|1=${timestamp}}}`;
            } else if (action === 'done') {
                wikitext = `: {{done}}${comment ? ' ' + comment : ''} ${timestamp}`;
                if (markAsResolved) {
                    wikitext += `\n{{Section resolved|1=${timestamp}}}`;
                }
            } else if (action === 'notdone') {
                wikitext = `: {{not done}}${comment ? ' ' + comment : ''} ${timestamp}`;
                if (markAsResolved) {
                    wikitext += `\n{{Section resolved|1=${timestamp}}}`;
                }
            } else if (action === 'granted') {
                wikitext = `: {{Done|Granted}}${comment ? ' ' + comment : ''} ${timestamp}`;
                if (markAsResolved) {
                    wikitext += `\n{{Section resolved|1=${timestamp}}}`;
                }
            } else if (action === 'blocked') {
                wikitext = `: {{Done|Blocked}}${comment ? ' ' + comment : ''} ${timestamp}`;
                if (markAsResolved) {
                    wikitext += `\n{{Section resolved|1=${timestamp}}}`;
                }
            } else if (action === 'tparevoked') {
                wikitext = `: {{Done|Revoked talkpage access}}${comment ? ' ' + comment : ''} ${timestamp}`;
                if (markAsResolved) {
                    wikitext += `\n{{Section resolved|1=${timestamp}}}`;
                }
            } else if (action === 'deleted') {
                wikitext = `: {{icon|delete|Deleted}}${comment ? ' ' + comment : ''} ${timestamp}`;
                if (markAsResolved) {
                    wikitext += `\n{{Section resolved|1=${timestamp}}}`;
                }
            } else if (action === 'kept') {
                wikitext = `: {{icon|keep|Kept}}${comment ? ' ' + comment : ''} ${timestamp}`;
                if (markAsResolved) {
                    wikitext += `\n{{Section resolved|1=${timestamp}}}`;
                }
            } else if (action === 'redirected') {
                wikitext = `: {{icon|redirect|Redirected}}${comment ? ' ' + comment : ''} ${timestamp}`;
                if (markAsResolved) {
                    wikitext += `\n{{Section resolved|1=${timestamp}}}`;
                }
            } else if (action === 'comment') {
                wikitext = comment ? `: ${comment} ${timestamp}` : '';
                if (markAsResolved) {
                    wikitext += `\n{{Section resolved|1=${timestamp}}}`;
                }
            } else if (action === 'unresolved') {
                wikitext = (comment && comment.trim()) ? `: ${comment} ${timestamp}` : '';
            }
            
            return wikitext;
        },
        
        removeSectionResolved(sectionWikitext) {
            return sectionWikitext.replace(/\{\{[Ss]ection resolved\s*\|?\s*1?\s*=\s*[^}]*\}\}\n?/g, '');
        },        
        
        async handleSubmit(sectionNum, action, comment, markAsResolved, sectionName, $statusArea, submitBtn, previewBtn, cancelBtn) {
            $statusArea.show().html('<p>Processing request...</p>');
            
            try {
                if (action === 'unresolved') {
                    await this.removeResolvedTemplate(sectionNum, comment, sectionName);
                } else {
                    const wikitext = this.generateWikitext(action, comment, markAsResolved);
                    if (wikitext) {
                        await this.updateSection(sectionNum, wikitext, sectionName, action, comment, markAsResolved);
                    }
                }
                submitBtn.setDisabled(true);
                previewBtn.setDisabled(true);
                cancelBtn.setDisabled(true);
                $statusArea.html('<p style="color: var(--color-success, green);">Request processed successfully! <a href="#" onclick="location.reload(); return false;"><b>Reload page</b></a></p>');
                
            } catch (error) {
                console.error('Error processing request:', error);
                $statusArea.html('<p style="color: var(--color-error, red);">Error processing request. Check console for details.</p>');
            }
        },

        async removeResolvedTemplate(sectionNum, comment, sectionName) {
            const api = new mw.Api();
            
            const response = await api.get({
                action: 'parse',
                page: mw.config.get('wgPageName'),
                section: sectionNum,
                prop: 'wikitext',
                formatversion: 2
            });
            
            if (!response || !response.parse || !response.parse.wikitext) {
                throw new Error('Could not fetch section wikitext');
            }
            
            let newWikitext = this.removeSectionResolved(response.parse.wikitext);
            
            if (comment && comment.trim()) {
                newWikitext += `\n: ${comment} ~~~~`;
            }
            
            const summaryAction = (comment && comment.trim()) ? 'Unmarked as resolved; commented' : 'Unmarked as resolved';
            
            await api.postWithEditToken({
                action: 'edit',
                title: mw.config.get('wgPageName'),
                section: sectionNum,
                summary: `/* ${sectionName} */ ${summaryAction}`,
                text: newWikitext,
				tags: 'WhyNotResolve',
                token: mw.user.tokens.get('csrfToken')
            });
        },
        
        async updateSection(sectionNum, wikitext, sectionName, action, comment, markAsResolved) {
            const api = new mw.Api();
            
            let summaryPrefix = '';
            if (action === 'done' || action === 'notdone' || action === 'deleted' || action === 'kept' || action === 'redirected' ||
                action === 'granted' || action === 'blocked' || action === 'tparevoked') {
                summaryPrefix = 'Resolved as ';
            }
        
            let summaryAction = '';
            if (action === 'resolved') {
                summaryAction = 'Marked as resolved';
            } else if (action === 'done') {
                summaryAction = 'done';
            } else if (action === 'notdone') {
                summaryAction = 'not done';
            } else if (action === 'granted') {
                summaryAction = 'granted';
            } else if (action === 'blocked') {
                summaryAction = 'blocked';
            } else if (action === 'tparevoked') {
                summaryAction = 'talkpage access revoked';
            } else if (action === 'deleted') {
                summaryAction = 'deleted';
            } else if (action === 'kept') {
                summaryAction = 'kept';
            } else if (action === 'redirected') {
                summaryAction = 'redirected';
            } else if (action === 'comment') {
                if (comment && markAsResolved) {
                    summaryAction = 'Commented; marked as resolved';
                } else if (comment) {
                    summaryAction = 'Commented';
                } else if (markAsResolved) {
                    summaryAction = 'Marked as resolved';
                }
            }
        
            await api.postWithEditToken({
                action: 'edit',
                title: mw.config.get('wgPageName'),
                section: sectionNum,
                summary: `/* ${sectionName} */ ${summaryPrefix}${summaryAction}`,
                appendtext: `\n${wikitext}`,
				tags: 'WhyNotResolve',
                token: mw.user.tokens.get('csrfToken')
            });
        }
    };
    
    ReportResolver.init();
});
//</nowiki>