Jump to content

User:Qwerfjkl/scripts/CFDlister.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.
// Fork of [[User:Writ Keeper/Scripts/autoCloser.js]]
//<nowiki>
console.log("Test script loaded.");

mw.loader.using(["oojs-ui-core", "oojs-ui-windows", "oojs-ui-widgets"], function() {
    function escapeRegexp(string) {
        return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
    }

    function parseHTML(html) {
        // Create a temporary div to parse the HTML
        var tempDiv = $('<div>').html(html);

        // Find all li elements
        var liElements = tempDiv.find('li');

        // Array to store extracted hrefs
        var hrefs = [];

        let existinghrefRegexp = /^https:\/\/en\.wikipedia.org\/wiki\/(Category:[^?&]+?)$/;
        let nonexistinghrefRegexp = /^https:\/\/en\.wikipedia\.org\/w\/index\.php\?title=(Category:[^&?]+?)&action=edit&redlink=1$/;

        // Iterate through each li element
        liElements.each(function() {
            // Find all anchor (a) elements within the current li
            let hrefline = [];
            var anchorElements = $(this).find('a');

            // Extract href attribute from each anchor element
            anchorElements.each(function() {
                var href = $(this).attr('href');
                if (href) {
                    var existingMatch = existinghrefRegexp.exec(href);
                    var nonexistingMatch = nonexistinghrefRegexp.exec(href);

                    if (existingMatch) {
                        hrefline.push(decodeURIComponent(existingMatch[1]).replaceAll('_', ' '));
                    }
                    if (nonexistingMatch) {
                        hrefline.push(decodeURIComponent(nonexistingMatch[1]).replaceAll('_', ' '));
                    }
                }
            });
            hrefs.push(hrefline);
        });

        return hrefs;
    }

    function handlepaste(widget, e) {
        var types, pastedData, parsedData;
        // Browsers that support the 'text/html' type in the Clipboard API (Chrome, Firefox 22+)
        if (e && e.clipboardData && e.clipboardData.types && e.clipboardData.getData) {
            // Check for 'text/html' in types list
            types = e.clipboardData.types;
            if (((types instanceof DOMStringList) && types.contains("text/html")) ||
                ($.inArray && $.inArray('text/html', types) !== -1)) {
                // Extract data and pass it to callback
                pastedData = e.clipboardData.getData('text/html');

                parsedData = parseHTML(pastedData);

                // Check if it's an empty array
                if (!parsedData || parsedData.length === 0) {
                    // Allow the paste event to propagate for plain text or empty array
                    return true;
                }
                let confirmed = confirm('You have pasted formatted text. Do you want this to be converted into wikitext?');
                if (!confirmed) return true;
                processPaste(widget, pastedData);

                // Stop the data from actually being pasted
                e.stopPropagation();
                e.preventDefault();
                return false;
            }
        }

        // Allow the paste event to propagate for plain text
        return true;
    }

    function waitForPastedData(widget, savedContent) {
        // If data has been processed by the browser, process it
        if (widget.getValue() !== savedContent) {
            // Retrieve pasted content via widget's getValue()
            var pastedData = widget.getValue();

            // Restore saved content
            widget.setValue(savedContent);

            // Call callback
            processPaste(widget, pastedData);
        }
        // Else wait 20ms and try again
        else {
            setTimeout(function() {
                waitForPastedData(widget, savedContent);
            }, 20);
        }
    }

    function processPaste(widget, pastedData) {
        // Parse the HTML
        var parsedArray = parseHTML(pastedData);
        let stringOutput = '';
        for (const cats of parsedArray) {
            if (cats.length === 1) stringOutput += `* [[:${cats[0]}]]\n`;
            if (cats.length === 2) stringOutput += `* [[:${cats[0]}]] to [[:${cats[1]}]]\n`;
            if (cats.length === 3) stringOutput += `* [[:${cats[0]}]] to [[:${cats[1]}]] and [[:${cats[2]}]]\n`;
            if (cats.length > 3) {
                let firstCat = cats.pop(0);
                let lastCat = cats.pop(0);
                stringOutput += `* [[:${firstCat}}]] to [[:${cats.join(']], [[:')}]] and [[:${lastCat}]]\n`;
            }
        }
        widget.insertContent(stringOutput);
    }

    // Code from https://doc.wikimedia.org/oojs-ui/master/js/#!/api/OO.ui.Dialog

    // Add interface shell
    function CfdDialog(config) {
        CfdDialog.super.call(this, config);
        this.InputText = config.InputText;
        this.sectionIndex = config.sectionIndex || null;
        CfdDialog.static.title = config.dialogTitle;
        CfdDialog.static.actions = config.dialogActions;
    }
    OO.inheritClass(CfdDialog, OO.ui.ProcessDialog);
    CfdDialog.static.name = 'CfdDialog';

    CfdDialog.prototype.initialize = function() {
        CfdDialog.super.prototype.initialize.call(this);
        this.content = new OO.ui.PanelLayout({
            padded: false,
            expanded: false
        });
        this.content.$element.append('<p style="padding-left: 5px">Make any changes necessary:</p>');
        CfdDialog.prototype.cfdlisterTextBox = new OO.ui.MultilineTextInputWidget({
            autosize: true,
            value: this.InputText,
            id: "CFD-lister-text",
            rows: Math.min((this.InputText.match(/\n/g) || []).length + 2, 10),
            maxrows: 25
        });

        let textInputElement = CfdDialog.prototype.cfdlisterTextBox.$element.get(0);
        let handler = handlepaste.bind(this, CfdDialog.prototype.cfdlisterTextBox);
        // Modern browsers. Note: 3rd argument is required for Firefox <= 6
        if (textInputElement.addEventListener) {
            textInputElement.addEventListener('paste', handler, false);
        }
        // IE <= 8
        else {
            textInputElement.attachEvent('onpaste', handler);
        }


        mw.loader.using('ext.wikiEditor', function() {
            mw.addWikiEditor(CfdDialog.prototype.cfdlisterTextBox.$input);

        });
        CfdDialog.prototype.cfdlisterTextBox.$input.on('input', () => this.updateSize());

        this.content.$element.append(CfdDialog.prototype.cfdlisterTextBox.$element);
        this.$body.append(this.content.$element);
    };

    CfdDialog.prototype.getActionProcess = function(action) {
        var dialog = this;
        if (action) {
            return new OO.ui.Process(function() {
                dialog.close({
                    text: '\n\n' + CfdDialog.prototype.cfdlisterTextBox.value,
                    sectionIndex: this.sectionIndex
                });
            });
        }
        return CfdDialog.super.prototype.getActionProcess.call(this, action);
    };

    function editDiscussions() {
        var text = localStorage.getItem('CFDNAClist');
        if (!text) {
            mw.notify('No discussions listed yet.', {
                type: 'error'
            });
            return;
        }
        var windowManager = new OO.ui.WindowManager();
        var cfdDialog = new CfdDialog({
            InputText: text.trim(), // newlines will be stripped here and added back implicitly in the closing call
            dialogTitle: 'Edit listed discussions',
            dialogActions: [{
                    action: 'add',
                    label: 'Save',
                    flags: ['primary', 'progressive']
                },
                {
                    label: 'Cancel',
                    flags: ['destructive', 'safe']
                }
            ]
        });

        windowManager.defaultSize = 'full';
        $(document.body).append(windowManager.$element);
        windowManager.addWindows([cfdDialog]);
        windowManager.openWindow(cfdDialog);
        windowManager.on('closing', (win, closing, data) => {
            if (!data) return;
            if (!data.text.trim()) {
                OO.ui.confirm('Are you sure you want to delete all listed discussions?').done((response) => {
                    if (response) {
                        localStorage.setItem('CFDNAClist', '');
                        localStorage.setItem('CFDNAClist-count', '');
                    } else mw.notify('Aborted changes to listed discussions.');
                });
            } else {
                localStorage.setItem('CFDNAClist', data.text);
                mw.notify('Listed discussions updated.');
            }
        });
    }

    var editDiscussionslink = mw.util.addPortletLink('p-cactions', '#', 'Edit discussions', 'pt-cfdnaceditlist', 'Edit listed discussions', null, listDiscussionslink);
    $(editDiscussionslink).click(function(event) {
        event.preventDefault();
        editDiscussions();
    });



    function quickClose(option, editLink, headerElement) {
        if (typeof editLink !== "undefined") {
            var regexResults = /title=([^&]+).*&section=[\D]*(\d+)/.exec(editLink.href);
            if (regexResults === null) {
                return false;
            }
            var pageTitle = regexResults[1].replaceAll('_', ' '); // prettify
            var sectionIndex = regexResults[2];
            const params = {
                action: "parse",
                format: "json",
                page: pageTitle,
                prop: "wikitext|sections",
                section: sectionIndex

            };
            const api = new mw.Api();

            api.get(params).done(data => {
                sectionTitle = data.parse.sections[0].line.replaceAll('_', ' ');
                wikitext = data.parse.wikitext["*"];
                const closedRegexp = /<div class="boilerplate cfd vfd xfd-closed mw-archivedtalk"/i;
                if (closedRegexp.test(wikitext)) { // already closed
                    mw.notify('Discussion already closed, aborted closure.', {
                        type: 'error'
                    });
                    return;
                }
                const newWikitext = `\n${wikitext.replace(/====.+====/, `$&\n{{subst:cfd top|'''${option}'''}}${mw.config.get('wgUserGroups').includes('sysop') ? '' : ' {{subst:nacd}}'}. ~~~~\n`)}\n{{subst:cfd bottom}}`;
                var requestData = {
                    action: "edit",
                    title: pageTitle,
                    format: "json",
                    section: sectionIndex,
                    text: newWikitext,
                    summary: `/* ${sectionTitle} */ Quick close as ${option} via [[User:Qwerfjkl/scripts/CFDlister.js|script]]`,
                    notminor: 1,
                    nocreate: 1,
                    token: mw.user.tokens.get('csrfToken')

                };
                $.ajax({
                        url: mw.util.wikiScript('api'),
                        type: 'POST',
                        dataType: 'json',
                        data: requestData
                    })
                    .then(function(data) {
                        if (data && data.edit && data.edit.result && data.edit.result == 'Success') {
                            mw.notify(`Discussion closed as ${option}.`);

                            // Now use wikitext from before, don't bother refetching
                            let result = option;
                            const categoryRegex = /====.+====\s+([\s\S]+)\s+[:\*]* *'''(?:Nominator'?s )?rationale?/i;
                            if (categoryRegex.test(wikitext)) { // correctly formatted
                                wikitext = wikitext.match(categoryRegex)[1];
                            } else {
                                alert("This nomination is missing a '''Nominator's rationale''': and so the script cannot recognise the categories nominated. Please manually fix this by adding '''Nominator's rationale''': just before the nominator's rationale.");
                                return;
                            }
                            // Cleanup
                            wikitext = wikitext.replace(/\{\{(?:lc|cl|cls|lcs|clc|cat)\|(.+?)\}\}/gi, '[[:Category:$1]]'); // fix category templates
                            wikitext = wikitext.replaceAll(':Category:Category:', ':Category:'); // fix double category - can be caused by above
                            wikitext = wikitext.replace(/'''(propose|delet|renam|split|(?:up)?merg|container).*?'''/gi, '');
                            wikitext = wikitext.replace(/^ *[:#\*]* */gm, '* '); // fix indentation
                            wikitext = wikitext.replace(/^\s*[\*\: ]*\s*\n/gm, ''); // remove lines with just *, : and whitespace
                            wikitext = wikitext.replace(/\n?\s*\*\s*$/, ''); // remove trailing asterisk
                            wikitext = wikitext.replace(/<br ?\/?>/g, ''); // remove br tags
                            wikitext = `* [[${pageTitle}#${sectionTitle}]]\nResult: '''${result}'''\n<syntaxhighlight lang="wikitext">\n; [[${pageTitle}]]\n${wikitext}\n</syntaxhighlight>`;
                            var incorrectOptionRegexp;
                            switch (option) {
                                case 'rename':
                                case 'merge':
                                    incorrectOptionRegexp = /\* *\[\[:Category:[^\]\n]+?\]\]$/gim;
                                    if (incorrectOptionRegexp.test(wikitext)) {
                                        mw.notify('Error parsing nomination. Please click "list discussion" to list it manually.', {
                                            type: 'error'
                                        });
                                        return;
                                    }
                                    break;
                                case 'delete':
                                    incorrectOptionRegexp = /\* *\[\[:Category:[^\]\n]+?\]\].+\[\[:Category:/gim;
                                    if (incorrectOptionRegexp.test(wikitext)) {
                                        mw.notify('Error parsing nomination. Please click "list discussion" to list it manually.', {
                                            type: 'error'
                                        });
                                        return;
                                    }
                                    break;
                                default: // shouldn't happen unless the user has modified their html
                                    mw.notify(`Error handling closure: ${option}. Please click "list discussion" to list it manually.`, {
                                        type: 'error'
                                    });
                                    return;
                            }
                            if (wikitext.includes('<s>') || wikitext.includes('{{') || wikitext.includes('<!--')) {
                                // something probably needs manual review
                                mw.notify('Error parsing nomination. Please click "list discussion" to list it manually.', {
                                    type: 'error'
                                });
                                return;
                            }

                            addCFD(null, null, {
                                text: '\n\n' + wikitext,
                                sectionIndex: sectionIndex
                            });

                        } else {
                            console.error('The edit query returned an error. =(\n\n' + JSON.stringify(data));
                            mw.notify('Error closing discussion, see console for details', {
                                type: 'error'
                            });
                        }
                    })
                    .catch(function(e) {
                        console.error('The ajax request failed.\n\n' + JSON.stringify(e));
                    });
            });
        }
    }

    function listDiscussion() {
        editLink = $(this).siblings("a.sectionEditLink")[0];
        if (typeof editLink !== "undefined") {
            var regexResults = /title=([^&]+).*&section=[\D]*(\d+)/.exec(editLink.href);
            if (regexResults === null) {
                return false;
            }
            var pageTitle = regexResults[1].replaceAll('_', ' ');
            var sectionIndex = regexResults[2];
            const params = {
                action: "parse",
                format: "json",
                page: pageTitle,
                prop: "wikitext|sections",
                section: sectionIndex

            };
            const api = new mw.Api();

            api.get(params).done(data => {
                sectionTitle = data.parse.sections[0].line.replaceAll('_', ' ');
                wikitext = data.parse.wikitext["*"];
                resultRegexp = /<div class="boilerplate cfd vfd xfd-closed(?: mw-archivedtalk)?" style="background(?:-color)?:#bff9fc; margin:0 auto; padding:0 10px 0 10px; border:1px solid #AAAAAA;">\n:''The following is an archived discussion concerning one or more categories\. <span style="color:red"\>'''Please do not modify it\.'''<\/span> Subsequent comments should be made on an appropriate discussion page \(such as the category's \[\[Help:Using talk pages\|talk page\]\] or in a \[\[Wikipedia:Deletion review\|deletion review\]\]\)\. No further edits should be made to this section\.''\n\n:''The result of the discussion was:'' ?(?:<!-- ?Template:Cfd top ?-->)? ?'''(.+?)'''/i;
                if (resultRegexp.test(wikitext)) { // match
                    result = wikitext.match(resultRegexp)[1];
                } else {
                    result = 'RESULT';
                }
                wikitext = wikitext.replace(new RegExp(resultRegexp.source + '.+', 'i'), ''); // remove closure text, unneeded
                const categoryRegex = /====.+====\s+([\s\S]+)\s+[:\*]* *'''(?:Nominator'?s )?rationale?/i;
                if (categoryRegex.test(wikitext)) { // correctly formatted
                    wikitext = wikitext.match(categoryRegex)[1];
                } else {
                    alert("This nomination is missing a '''Nominator's rationale''': and so the script cannot recognise the categories nominated. Please manually fix this by adding '''Nominator's rationale''': just before the nominator's rationale.");
                    return;
                }
                // Cleanup
                wikitext = wikitext.replace(/\{\{(?:lc|cl|cls|lcs|clc|cat)\|(.+?)\}\}/gi, '[[:Category:$1]]'); // fix category templates
                wikitext = wikitext.replaceAll(':Category:Category:', ':Category:'); // fix double category - can be caused by above
                wikitext = wikitext.replace(/'''(propose|delet|renam|split|(?:up)?merg|container).*?'''/gi, '');
                wikitext = wikitext.replace(/^ *[:#\*]* */gm, '* '); // fix indentation
                wikitext = wikitext.replace(/^\s*[\*\: ]*\s*\n/gm, ''); // remove lines with just *, : and whitespace
                wikitext = wikitext.replace(/\n?\s*\*\s*$/, ''); // remove trailing asterisk
                wikitext = wikitext.replace(/<br ?\/?>/g, ''); // remove br tags
                wikitext = "* [[" + pageTitle + "#" + sectionTitle + "]]\nResult: '''" + result + "'''\n<syntaxhighlight lang=\"wikitext\">\n; [[" + pageTitle + "]]\n" + wikitext + "\n</syntaxhighlight>";

                var windowManager = new OO.ui.WindowManager();

                var cfdDialog = new CfdDialog({
                    InputText: wikitext,
                    sectionIndex: sectionIndex,
                    dialogTitle: 'List discussion for processing',
                    dialogActions: [{
                            action: 'add',
                            label: 'Add',
                            flags: ['primary', 'progressive']
                        },
                        {
                            label: 'Cancel',
                            flags: ['destructive', 'safe']
                        }
                    ]
                });

                windowManager.defaultSize = 'full';
                $(document.body).append(windowManager.$element);
                windowManager.addWindows([cfdDialog]);
                windowManager.openWindow(cfdDialog);
                windowManager.on('closing', addCFD);
            });
        }
    }

    function addCFD(win, closing, data) {
        if (data == null || data === undefined || data.text == '' || !data.text) {
            return;
        }
        var wikitext = data.text;
        var text = localStorage.getItem('CFDNAClist');
        var count = localStorage.getItem('CFDNAClist-count') || 0;
        if (text == '' || text == null) {
            localStorage.setItem('CFDNAClist', wikitext);
        } else {
            localStorage.setItem('CFDNAClist', text + wikitext);
        }
        localStorage.setItem('CFDNAClist-count', Number(count) + 1);
        mw.notify('Added discussion');
        // double strike through handled sections
        if (data.sectionIndex) {
            // apply styles to show discussion has been closed (like XFDCloser)
            $($("h4")[data.sectionIndex - 2]).css({
                'text-decoration': 'line-through',
                'text-decoration-style': 'double'
            });
            var startH4 = document.querySelectorAll("h4")[data.sectionIndex - 2].parentElement;
            if (startH4) {
                var elementsBetweenH4 = [];
                var currentElement = startH4.nextElementSibling;

                while (currentElement) {
                    if (currentElement.classList.contains("mw-heading") && currentElement.classList.contains("mw-heading4")) {
                        break;
                    }

                    elementsBetweenH4.push(currentElement);
                    currentElement = currentElement.nextElementSibling;
                }

                elementsBetweenH4.forEach(function(element) {
                    $(element).css('opacity', '50%');
                });
            }
        }
    }

    function discussionListerSetup() {
    	console.log("Adding links to sections");
        function createDropdownLink(text, clickHandler) {
            var link = document.createElement("a");
            link.href = "#";
            link.innerHTML = text;
            link.onclick = function(event) {
                event.preventDefault();
                clickHandler();
            };
            return link;
        }

        var sectionHeaders = $("h4 ~ .mw-editsection");
        $('.mw-heading, h1, h2, h3, h4, h5, h6').css('overflow', 'visible'); // allow for dropdown, no idea what damage it could do
        sectionHeaders.each(function(index, element) {
        	console.log("Potential sect");
            var editLink = $(element).children("a")[0];
            if (typeof editLink !== "undefined" && /&section=[\D]*(\d+)/.exec(editLink.href)) {
            	console.log("Adding to sect");
                $(editLink).addClass("sectionEditLink");

                var discussionLister = $("<a>List discussion</a>");
                discussionLister.click(listDiscussion);

                // Create dropdown elements
                var dropdownContainer = $(`<span class='dropdown-container' style="position:relative; display:inline-block; "></span>`);


                var dropdownTrigger = $(`<a class='dropdown-trigger' style="color: #0645AD; text-decoration: none; cursor: pointer">One click close</a>`);


                var dropdownMenu = $(`<span class='dropdown-menu' style="display: none; position: absolute; background-color: #fff; border: 1px solid #ddd; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); padding: 5px; min-width: 6em; z-index: 1; left: 0px; top: 0.8em;" ></span>`);


                var actions = ['delete', 'rename', 'merge']; // needs to correspond with switch statement in quickClose()
                for (var i = 0; i < actions.length; i++) {
                    const action = actions[i];
                    var menuItem = $(`<a style="display: block; color: #0645AD; text-decoration: none; padding: 10px; margin-top: 5px; font-size: 150%" class='dropdown-item'>${action}</a>`);
                    menuItem.click(function() {
                        quickClose(action, editLink, element);
                        dropdownMenu.hide();
                    });
                    // make red on hover
                    menuItem.on("mouseenter", function() {
                        $(this).css('color', 'red');
                    }).on("mouseleave", function() {
                        $(this).css('color', 'color: #0645AD');
                    });
                    dropdownMenu.append(menuItem);
                }

                dropdownTrigger.click(function() {
                    dropdownMenu.toggle();
                });

                // Append elements to the existing element
                dropdownContainer.append(dropdownTrigger, dropdownMenu);



                // Close the dropdown if the user clicks outside of it
                $(document).click(function(event) {
                    if (!$(event.target).closest('.dropdown-container').length) {
                        dropdownMenu.hide();
                    }
                });

                let bracket = $(element).find('.mw-editsection-bracket').last()
                $(bracket).before(' | ', discussionLister, " | ", dropdownContainer);

            }
        });


    }
    if (mw.config.get("wgPageName").match('Wikipedia:Categories_for_discussion')) $(document).ready(discussionListerSetup);
});




async function listPages() {
    var text = localStorage.getItem('CFDNAClist');
    var count = localStorage.getItem('CFDNAClist-count');
    if (text == '' || text == null) {
        mw.notify('No discussions to list, aborting');
        return;
    }
    text = "\n\nPlease can an admin add the following:" + text + "\n~~~~";



    const date = new Date();
    const monthNames = [
        "January", "February", "March", "April", "May", "June",
        "July", "August", "September", "October", "November", "December"
    ];
    const day = date.getUTCDate();
    const month = monthNames[date.getUTCMonth()];
    const year = date.getUTCFullYear();

    // Return the formatted string
    let current_date = `${day} ${month} ${year}`;

    // Check if we need to make a new section
    let sectionRequestData = {
        "action": "parse",
        "format": "json",
        "page": "Wikipedia talk:Categories for discussion/Working",
        "prop": "sections",
        "formatversion": "2"
    };
    let sectionData;
    try {

        sectionData = await $.ajax({
            url: mw.util.wikiScript('api'),
            type: 'POST',
            dataType: 'json',
            data: sectionRequestData
        });
    } catch (error) {
        console.error('Error occurred:', error);
        // Handle the error or rethrow it
    }
    var requestData = {
        action: "edit",
        title: "Wikipedia talk:Categories for discussion/Working",
        format: "json",
        summary: "Add NAC request (" + count + " dicussions listed) via [[User:Qwerfjkl/scripts/CFDlister.js|script]]",
        notminor: 1,
        nocreate: 1,
        redirect: 1,
        token: mw.user.tokens.get('csrfToken')
    };

    if (sectionData.parse.sections.length != 0 && sectionData.parse.sections.slice(-1)[0].line.toLowerCase().startsWith("non-admin closure")) { // no need for a new section
        requestData.appendtext = text;
    } else { // new section

        // helper function
        const incrementSectionTitle = (sectTitle) => {
            const regex = /(.*?)(\d+)?$/; // Matches the main text and optional numeric suffix
            const match = sectTitle.match(regex);

            if (!match) return sectTitle; // If no match, return the original sectTitle

            const base = match[1].trim(); // Main text without the number
            const number = match[2] ? parseInt(match[2], 10) : 1; // Extract or default to 1

            return `${base} ${number + 1}`; // Increment the number and return
        };

        // make sure we don't duplicate titles
        var sectionTitle = `Non-admin closure request (${current_date})`;
        while (sectionData.parse.sections.some(section => section.line === sectionTitle)) {
            sectionTitle = incrementSectionTitle(sectionTitle);
        }

        requestData.section = "new";
        requestData.sectiontitle = sectionTitle;
        requestData.text = text.replace(/^\s+/, ''); // rstrip
    }


    mw.notify('Editing WT:CFDW...', {
        tag: 'CFDListerEdit'
    });
    $.ajax({
            url: mw.util.wikiScript('api'),
            type: 'POST',
            dataType: 'json',
            data: requestData
        })
        .then(function(data) {
            if (data && data.edit && data.edit.result && data.edit.result == 'Success') {
                mw.notify('Discussions listed.', {
                    tag: 'CFDListerEdit'
                });
                localStorage.setItem('CFDNAClist', '');
                localStorage.setItem('CFDNAClist-count', 0);
                window.location.href = "https://en.wikipedia.org/wiki/Wikipedia_talk:Categories_for_discussion/Working#footer"; // redirect
            } else {
                alert('The edit query returned an error. =(');
            }
        })
        .catch(function() {
            alert('The ajax request failed.');
        });
}

var listDiscussionslink = mw.util.addPortletLink('p-cactions', '#', 'List discussions', 'pt-cfdnaclist', 'List closed discussions for admins to handle');
$(listDiscussionslink).click(function(event) {
    event.preventDefault();
    listPages();
});

//</nowiki>