User:Chlod/Scripts/InfringementAssistant.js

/* * Infringement Assistant * * More information on the userscript itself can be found at User:Chlod/IA. */ // mw.loader.using([   "oojs-ui-core",    "oojs-ui-windows",    "oojs-ui-widgets",    "mediawiki.api",    "mediawiki.util" ], async function {

// =============================== STYLES =================================

mw.util.addCSS(`       .ia-submit {            margin-left: auto;            margin-top: 16px;        }    `);

// ============================== CONSTANTS ===============================

/**    * Debug mode will redirect CP listings to Special:MyPage/sandbox/{date} and * replace the copyvio template with a soft link (using undefined). * @type {boolean} */   const debug = false;

const advert = "(InfringementAssistant)"; /**    * Using a fixed set of months since `mw.language.months` changes depending * on `?uselang` even if we're still on the English Wikipedia. * @type {string[]} */   const months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ];

// =========================== HELPER FUNCTIONS ===========================

function getListingDate(header = false) { const today = new Date; return `${ header ? today.getUTCDate : today.getUTCFullYear } ${           months[today.getUTCMonth] } ${           header ? today.getUTCFullYear : today.getUTCDate }`;   }

/**    * Gets the title of today's copyright problems page. * @returns {string} */   function getListingPage { return (debug ? `User:${mw.config.get("wgUserName")}/sandbox/` : "Wikipedia:Copyright problems/") + getListingDate; }

/**    * Ask for confirmation before unloading. * @param {BeforeUnloadEvent} event */   function exitBlock(event) { event.preventDefault; return event.returnValue = undefined; }

// ============================== SINGLETONS ==============================

/**    * The WindowManager for this userscript. */   const windowManager = new OO.ui.WindowManager; document.body.appendChild(windowManager.$element[0]);

/**    * MediaWiki API class. * @type {mw.Api} */   const api = new mw.Api; const pageName = mw.config.get("wgPageName").replace(/_/g, " ");

// =========================== PROCESS FUNCTIONS ==========================

async function shadowPage(options) { let summary = `Hiding ${ options.fullPage ? "the page" : `/* ${options.sectionName} */` } due to a suspected/complicated copyright violation (see ${           getListingPage}) ${ advert }`;

if (options.fullPage) { return api.postWithEditToken({               action: "edit",                title: pageName,                prependtext: `\n`,                nocreate: true,                summary: summary            }); } else if (options.section) { return api.postWithEditToken({               action: "edit",                title: pageName,                section: options.section,                prependtext: `\n`,                appendtext: `\n`,                nocreate: true,                summary: summary            }); } else { throw "Illegal state."; }   }

async function addListing(options) { const listingPage = getListingPage; const pageExists = (await api.get({ "action": "query", "titles": listingPage }))["query"]["pages"]["-1"] == null;

let summary = `${ pageExists ? "A" : "Created page and a"       }dded listing for ${pageName} § ${                options.sectionName            }` : ""        } ${ advert }`;

const listingText = `\n* ${pageName} from ${ options.fromText }.${           options.additionalNotes ? ` ${options.additionalNotes}` : "" } ~`;

if (pageExists) { return api.postWithEditToken({               action: "edit",                title: listingPage,                appendtext: listingText,                recreate: true,                summary: summary            }); } else { const listingHeader = `==== ${getListingDate(true)} ====\n`; return api.postWithEditToken({               action: "edit",                title: listingPage,                text: `${listingHeader}${listingText}`,                recreate: true,                summary: summary            }); }   }

// =============================== PANELS =================================

function SuspectedInfringementPanel(config) { SuspectedInfringementPanel.super.call( this, name, config );

this.inputs = { fullPage: new OO.ui.CheckboxInputWidget({ selected: true }), section: new OO.ui.DropdownInputWidget({               disabled: true,                options: config.context["sections"].length > 0 ? [                    { data: "0", label: "0: Lead" },                    ...config.context["sections"].map( (d) => { return { data: d.index, label: `${d.number}: ${d.line}` }; } )               ] : null,                placeholder: "Select section to hide"            }), fromURL: new OO.ui.CheckboxInputWidget({ selected: true }), urls: new OO.ui.MenuTagMultiselectWidget({               allowArbitrary: true,                inputPosition: "outline",                indicators: [ "required" ],                placeholder: "Add URL",                options: config.context["externallinks"].length > 0 ? config.context["externallinks"].map( (d) => { return { data: d, label: d }; } ) : null           }), rawFrom: new OO.ui.MultilineTextInputWidget({               autosize: true,                maxRows: 2            }), additionalNotes: new OO.ui.MultilineTextInputWidget({               autosize: true,                maxRows: 2            }) };       this.fields = { fullPage: new OO.ui.FieldLayout(this.inputs.fullPage, {               align: "inline",                label: "Hide the entire page"            }), section: new OO.ui.FieldLayout(this.inputs.section, {               align: "top",                label: "Section"            }), fromURL: new OO.ui.FieldLayout(this.inputs.fromURL, {               $overlay: config.dialog.$overlay,                align: "inline",                label: "Use URLs for the origin",                help: "URLs will automatically be wrapped with brackets to shorten the external link. " +                    "Disabling this option will present the text as is."            }), urls: new OO.ui.FieldLayout(this.inputs.urls, {               align: "top",                label: "Source of copied content"            }), rawFrom: new OO.ui.FieldLayout(this.inputs.rawFrom, {               align: "top",                label: "Source of copied content"            }), additionalNotes: new OO.ui.FieldLayout(this.inputs.additionalNotes, {               align: "top",                label: "Additional notes"            }) }

this.fields.rawFrom.toggle(false);

this.inputs.fromURL.on("change", (selected) => {           this.fields.rawFrom.toggle(!selected);            this.fields.urls.toggle(selected);        });

this.inputs.fullPage.on("change", (selected) => {           this.inputs.section.setDisabled(selected);        });

this.urls = []; this.inputs.urls.on("change", (items) => {           this.urls = items.map(i => i.data);        });

for (const field of Object.values(this.fields)) { /** @var $element */ this.$element.append(field.$element); }

const submit = new OO.ui.ButtonWidget({           label: "Submit",            flags: [ "primary", "progressive" ],            classes: [ "ia-submit" ]        }); const submitContainer = document.createElement("div"); submitContainer.style.textAlign = "right"; submitContainer.appendChild(submit.$element[0]);

submit.on("click", => {            const panel = this;            config.dialog.setCompletionFunction(async  => { const options = { fullPage: panel.inputs.fullPage.isSelected, additionalNotes: panel.inputs.additionalNotes.getValue };               if (!options.fullPage) { options.section = +panel.inputs.section.getValue; options.sectionName = panel.inputs.section.dropdownWidget.label.replace(/^[0-9.]+: /g, ""); }               if (panel.inputs.fromURL.isSelected) { options.urls = panel.urls; options.fromText = panel.urls.map(u => `[${                       encodeURI(u)                    }]`).join(", ") } else { options.fromText = panel.inputs.rawFrom.getValue; }               return addListing(options).then( => shadowPage(options)); });           config.dialog.executeAction("execute");        });

/** @var $element */ this.$element.append(submitContainer); }   OO.inheritClass(SuspectedInfringementPanel, OO.ui.TabPanelLayout); // noinspection JSUnusedGlobalSymbols SuspectedInfringementPanel.prototype.setupTabItem = function { /** @var tabItem */ this.tabItem.setLabel("Suspected or complicated"); };

// =============================== DIALOGS ================================

function InfringementAssistantDialog(config) { InfringementAssistantDialog.super.call(this, config); if (config.context == null) throw "Context was not provided."; else this.context = config.context; }   OO.inheritClass(InfringementAssistantDialog, OO.ui.ProcessDialog);

InfringementAssistantDialog.static.name = "infringementAssistantDialog"; InfringementAssistantDialog.static.title = "Infringement Assistant"; InfringementAssistantDialog.static.size = "medium"; InfringementAssistantDialog.static.actions = [ {           flags: ["safe", "close"], icon: "close", label: "Close", title: "Close", invisibleLabel: true, action: "close" }   ];

// noinspection JSUnusedGlobalSymbols InfringementAssistantDialog.prototype.getBodyHeight = function { return 470; };

InfringementAssistantDialog.prototype.initialize = function { InfringementAssistantDialog.super.prototype.initialize.apply(this, arguments);

this.indexLayout = new OO.ui.IndexLayout({           expanded: true        }); this.panelLayout = new OO.ui.PanelLayout({           expanded: true,            framed: true,            content: [ this.indexLayout ]        });

this.indexLayout.addTabPanels([           new SuspectedInfringementPanel({ dialog: this, context: this.context })       ]);

/** @var $content */ this.$body.append(this.panelLayout.$element); }

InfringementAssistantDialog.prototype.setCompletionFunction = function (process) { this.completionFunction = process; }

InfringementAssistantDialog.prototype.getSetupProcess = function (data) { const process = InfringementAssistantDialog.super.prototype.getSetupProcess.call(this, data);

process.next( => {           window.addEventListener("beforeunload", exitBlock);        });

return process; }

InfringementAssistantDialog.prototype.getActionProcess = function (action) { const process = InfringementAssistantDialog.super.prototype.getActionProcess.call(this, action);

if (action === "execute") { process.first(this.completionFunction); }       process.next(function  {            this.close({ action: action });        }, this);

return process; }

InfringementAssistantDialog.prototype.getTeardownProcess = function (data) { window.removeEventListener("beforeunload", exitBlock); /** @var any */ return InfringementAssistantDialog.super.prototype.getTeardownProcess.call(this, data); }

// ============================== INITIALIZE ==============================

function openDialog { api.get({           "action": "parse",            "page": pageName,            "prop": "externallinks|sections"        }).then((data) => {            const dialog = new InfringementAssistantDialog({ context: data["parse"] });           windowManager.addWindows([ dialog ]);            windowManager.openWindow(dialog);        }).catch((error) => {            if (error === "missingtitle")                OO.ui.alert("Cannot open Infringement Assistant: The page does not exist.");            else                OO.ui.alert(`Cannot open Infringement Assistant: ${error}`);        }); }

if (document.getElementById("pt-ia") == null && mw.config.get("wgNamespaceNumber") >= 0) { mw.util.addPortletLink(           "p-tb",            "javascript:void(0)",            "Infringement Assistant",            "pt-ia"        ).addEventListener("click", function {            openDialog;        }); }

// Query parameter-based autostart if (/[?&]ia-autostart(=(1|yes|true|on)?(&|$)|$)/.test(window.location.search)) { openDialog; }

if (debug) { mw.notify("Debug mode has been enabled.", { title: "Infringement Assistant" }); }

document.dispatchEvent(new Event("ia:load"));

}); // /* * Copyright 2021 Chlod * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *    http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Also licensed under the Creative Commons Attribution-ShareAlike 3.0 * Unported License, a copy of which is available at * *     https://creativecommons.org/licenses/by-sa/3.0 * */