User:Chlod/Scripts/Deputy.js

/*! * *    DEPUTY * *    A copyright management and investigation assistance tool. * *     *  *    Copyright 2022 Chlod Aidan Alejandro * *    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. * *     *  *    NOTE TO USERS AND DEBUGGERS: This userscript is originally written in *    TypeScript. The original TypeScript code is converted to raw JavaScript *   during the build process. To view the original source code, visit * *        https://github.com/ChlodAlejandro/deputy * */ // /*! * @package idb * @version 7.1.0 * @license ISC * @author Jake Archibald * @url https://github.com/jakearchibald/idb *//*! * @package tsx-dom * @version 1.4.0 * @license MIT * @author Santo Pfingsten * @url https://github.com/Lusito/tsx-dom *//*! * @package broadcastchannel-polyfill * @version 1.0.1 * @license Unlicense * @author Joshua Bell * @url https://github.com/JSmith01/broadcastchannel-polyfill *//*! * @package @chlodalejandro/parsoid * @version 2.0.1-37ea110 * @license MIT * @author Chlod Alejandro * @url https://github.com/ChlodAlejandro/parsoid-document */ (function {    'use strict';

/******************************************************************************   Copyright (c) Microsoft Corporation.

Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR   OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR    PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */

function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) {           function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }            function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }            function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }            step((generator = generator.apply(thisArg, _arguments || [])).next);        }); }

typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;   };

const instanceOfAny = (object, constructors) => constructors.some((c) => object instanceof c);

let idbProxyableTypes; let cursorAdvanceMethods; // This is a function to prevent it throwing up in node environments. function getIdbProxyableTypes { return (idbProxyableTypes ||           (idbProxyableTypes = [ IDBDatabase, IDBObjectStore, IDBIndex, IDBCursor, IDBTransaction, ]));   }    // This is a function to prevent it throwing up in node environments. function getCursorAdvanceMethods { return (cursorAdvanceMethods ||           (cursorAdvanceMethods = [ IDBCursor.prototype.advance, IDBCursor.prototype.continue, IDBCursor.prototype.continuePrimaryKey, ]));   }    const cursorRequestMap = new WeakMap; const transactionDoneMap = new WeakMap; const transactionStoreNamesMap = new WeakMap; const transformCache = new WeakMap; const reverseTransformCache = new WeakMap; function promisifyRequest(request) { const promise = new Promise((resolve, reject) => {           const unlisten =  => {                request.removeEventListener('success', success);                request.removeEventListener('error', error);            };            const success =  => {                resolve(wrap(request.result));                unlisten;            };            const error =  => {                reject(request.error);                unlisten;            };            request.addEventListener('success', success);            request.addEventListener('error', error);        }); promise .then((value) => {           // Since cursoring reuses the IDBRequest (*sigh*), we cache it for later retrieval            // (see wrapFunction).            if (value instanceof IDBCursor) {                cursorRequestMap.set(value, request);            }            // Catching to avoid "Uncaught Promise exceptions"        }) .catch( => { }); // This mapping exists in reverseTransformCache but doesn't doesn't exist in transformCache. This // is because we create many promises from a single IDBRequest. reverseTransformCache.set(promise, request); return promise; }   function cacheDonePromiseForTransaction(tx) { // Early bail if we've already created a done promise for this transaction. if (transactionDoneMap.has(tx)) return; const done = new Promise((resolve, reject) => {           const unlisten =  => {                tx.removeEventListener('complete', complete);                tx.removeEventListener('error', error);                tx.removeEventListener('abort', error);            };            const complete =  => {                resolve;                unlisten;            };            const error =  => {                reject(tx.error || new DOMException('AbortError', 'AbortError'));                unlisten;            };            tx.addEventListener('complete', complete);            tx.addEventListener('error', error);            tx.addEventListener('abort', error);        }); // Cache it for later retrieval. transactionDoneMap.set(tx, done); }   let idbProxyTraps = { get(target, prop, receiver) { if (target instanceof IDBTransaction) { // Special handling for transaction.done. if (prop === 'done') return transactionDoneMap.get(target); // Polyfill for objectStoreNames because of Edge. if (prop === 'objectStoreNames') { return target.objectStoreNames || transactionStoreNamesMap.get(target); }               // Make tx.store return the only store in the transaction, or undefined if there are many. if (prop === 'store') { return receiver.objectStoreNames[1] ? undefined : receiver.objectStore(receiver.objectStoreNames[0]); }           }            // Else transform whatever we get back. return wrap(target[prop]); },       set(target, prop, value) { target[prop] = value; return true; },       has(target, prop) { if (target instanceof IDBTransaction &&               (prop === 'done' || prop === 'store')) { return true; }           return prop in target; },   };    function replaceTraps(callback) { idbProxyTraps = callback(idbProxyTraps); }   function wrapFunction(func) { // Due to expected object equality (which is enforced by the caching in `wrap`), we       // only create one new func per func. // Edge doesn't support objectStoreNames (booo), so we polyfill it here. if (func === IDBDatabase.prototype.transaction &&           !('objectStoreNames' in IDBTransaction.prototype)) { return function (storeNames, ...args) { const tx = func.call(unwrap(this), storeNames, ...args); transactionStoreNamesMap.set(tx, storeNames.sort ? storeNames.sort : [storeNames]); return wrap(tx); };       }        // Cursor methods are special, as the behaviour is a little more different to standard IDB. In       // IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the // cursor. It's kinda like a promise that can resolve with many values. That doesn't make sense // with real promises, so each advance methods returns a new promise for the cursor object, or       // undefined if the end of the cursor has been reached. if (getCursorAdvanceMethods.includes(func)) { return function (...args) { // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use // the original object. func.apply(unwrap(this), args); return wrap(cursorRequestMap.get(this)); };       }        return function (...args) { // Calling the original function with the proxy as 'this' causes ILLEGAL INVOCATION, so we use // the original object. return wrap(func.apply(unwrap(this), args)); };   }    function transformCachableValue(value) { if (typeof value === 'function') return wrapFunction(value); // This doesn't return, it just creates a 'done' promise for the transaction, // which is later returned for transaction.done (see idbObjectHandler). if (value instanceof IDBTransaction) cacheDonePromiseForTransaction(value); if (instanceOfAny(value, getIdbProxyableTypes)) return new Proxy(value, idbProxyTraps); // Return the same value back if we're not going to transform it. return value; }   function wrap(value) { // We sometimes generate multiple promises from a single IDBRequest (eg when cursoring), because // IDB is weird and a single IDBRequest can yield many responses, so these can't be cached. if (value instanceof IDBRequest) return promisifyRequest(value); // If we've already transformed this value before, reuse the transformed value. // This is faster, but it also provides object equality. if (transformCache.has(value)) return transformCache.get(value); const newValue = transformCachableValue(value); // Not all types are transformed. // These may be primitive types, so they can't be WeakMap keys. if (newValue !== value) { transformCache.set(value, newValue); reverseTransformCache.set(newValue, value); }       return newValue; }   const unwrap = (value) => reverseTransformCache.get(value);

/**    * Open a database. *    * @param name Name of the database. * @param version Schema version. * @param callbacks Additional callbacks. */   function openDB(name, version, { blocked, upgrade, blocking, terminated } = {}) { const request = indexedDB.open(name, version); const openPromise = wrap(request); if (upgrade) { request.addEventListener('upgradeneeded', (event) => {               upgrade(wrap(request.result), event.oldVersion, event.newVersion, wrap(request.transaction), event);            }); }       if (blocked) { request.addEventListener('blocked', (event) => blocked( // Casting due to https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1405 event.oldVersion, event.newVersion, event)); }       openPromise .then((db) => {           if (terminated)                db.addEventListener('close',  => terminated);            if (blocking) {                db.addEventListener('versionchange', (event) => blocking(event.oldVersion, event.newVersion, event));            }        }) .catch( => { }); return openPromise; }

const readMethods = ['get', 'getKey', 'getAll', 'getAllKeys', 'count']; const writeMethods = ['put', 'add', 'delete', 'clear']; const cachedMethods = new Map; function getMethod(target, prop) { if (!(target instanceof IDBDatabase && !(prop in target) && typeof prop === 'string')) { return; }       if (cachedMethods.get(prop)) return cachedMethods.get(prop); const targetFuncName = prop.replace(/FromIndex$/, ''); const useIndex = prop !== targetFuncName; const isWrite = writeMethods.includes(targetFuncName); if (       // Bail if the target doesn't exist on the target. Eg, getAll isn't in Edge.        !(targetFuncName in (useIndex ? IDBIndex : IDBObjectStore).prototype) ||            !(isWrite || readMethods.includes(targetFuncName))) { return; }       const method = async function (storeName, ...args) { // isWrite ? 'readwrite' : undefined gzipps better, but fails in Edge :(           const tx = this.transaction(storeName, isWrite ? 'readwrite' : 'readonly');           let target = tx.store;            if (useIndex)                target = target.index(args.shift);            // Must reject if op rejects.            // If it's a write operation, must reject if tx.done rejects.            // Must reject with op rejection first.            // Must resolve with op value.            // Must handle both promises (no unhandled rejections)            return (await Promise.all([                target[targetFuncName](...args),                isWrite && tx.done,            ]))[0];        };        cachedMethods.set(prop, method);        return method;    }    replaceTraps((oldTraps) => ({        ...oldTraps,        get: (target, prop, receiver) => getMethod(target, prop) || oldTraps.get(target, prop, receiver),        has: (target, prop) => !!getMethod(target, prop) || oldTraps.has(target, prop),    }));

var version = "0.8.0"; var gitAbbrevHash = "5523baa"; var gitBranch = "main"; var gitDate = "Mon, 8 Jul 2024 12:36:45 +0800"; var gitVersion = "0.8.0+g5523baa";

/**    *     */    class MwApi { /**        * @return A mw.Api for the current wiki. */       static get action { var _a; return (_a = this._action) !== null && _a !== void 0 ? _a : (this._action = new mw.Api({ ajax: { headers: { 'Api-User-Agent': `Deputy/${version} (https://w.wiki/7NWR; User:Chlod; wiki@chlod.net)` }               },                parameters: { format: 'json', formatversion: 2, utf8: true, errorformat: 'html', errorlang: mw.config.get('wgUserLanguage'), errorsuselocal: true }           }));        }        /**         * @return A mw.Rest for the current wiki. */       static get rest { var _a; return (_a = this._rest) !== null && _a !== void 0 ? _a : (this._rest = new mw.Rest); }   }    MwApi.USER_AGENT = `Deputy/${version} (https://w.wiki/7NWR; User:Chlod; wiki@chlod.net)`;

/**    * Log to the console. *    * @param {...any} data */   function log(...data) { console.log('[Deputy]', ...data); }

/**    * Handles all browser-stored data for Deputy. */   class DeputyStorage { /**        * Initialize the Deputy IndexedDB database. *        * @return {void} A promise that resolves when a database connection is established. */       init { return __awaiter(this, void 0, void 0, function* {                this.db = yield openDB('us-deputy', 1, { upgrade(db, oldVersion, newVersion) { let currentVersion = oldVersion; const upgrader = { 0: => {                                db.createObjectStore('keyval', {                                    keyPath: 'key'                                }); db.createObjectStore('casePageCache', {                                   keyPath: 'pageID'                                }); db.createObjectStore('diffCache', {                                   keyPath: 'revid'                                }); db.createObjectStore('diffStatus', {                                   keyPath: 'hash'                                }); db.createObjectStore('pageStatus', {                                   keyPath: 'hash'                                }); db.createObjectStore('tagCache', {                                   keyPath: 'key'                                }); }                       };                        while (currentVersion < newVersion) { upgrader[`${currentVersion}`]; log(`Upgraded database from ${currentVersion} to ${currentVersion + 1}`); currentVersion++; }                   }                });                yield this.getTags;            }); }       /**         * Get a value in the `keyval` store. *        * @param key The key to get */       getKV(key) { return __awaiter(this, void 0, void 0, function* {                return window.deputy.storage.db.get('keyval', key)                    .then((keyPair) => keyPair === null || keyPair === void 0 ? void 0 : keyPair.value);           }); }       /**         * Set a value in the `keyval` store. *        * @param key The key to set * @param value The value to set */       setKV(key, value) { return __awaiter(this, void 0, void 0, function* {                return window.deputy.storage.db.put('keyval', { key: key, value: value }).then( => true);           }); }       /**         * Get all MediaWiki tags and store them in the `tagCache` store. */       getTags { var _a; return __awaiter(this, void 0, void 0, function* {                this.tagCache = {};                const tagCache = yield window.deputy.storage.db.getAll('tagCache');                if (tagCache.length === 0 || // 7 days Date.now - ((_a = yield this.getKV('tagCacheAge')) !== null && _a !== void 0 ? _a : 0) > 6048e5) {                   yield MwApi.action.getMessages(['*'], { amenableparser: true, amincludelocal: true, amprefix: 'tag-' }).then((messages) => { for (const key in messages) { this.tagCache[key] = messages[key]; mw.messages.set(key, messages[key]); this.db.put('tagCache', { key, value: messages[key] }); }                       this.setKV('tagCacheAge', Date.now); });               }                else {                    for (const { key, value } of tagCache) {                        this.tagCache[key] = value;                        mw.messages.set(key, value);                    }                }            }); }   }

(function(global) {       var channels = [];

function BroadcastChannel(channel) { var $this = this; channel = String(channel);

var id = '$BroadcastChannel$' + channel + '$';

channels[id] = channels[id] || []; channels[id].push(this);

this._name = channel; this._id = id; this._closed = false; this._mc = new MessageChannel; this._mc.port1.start; this._mc.port2.start;

global.addEventListener('storage', function(e) {               if (e.storageArea !== global.localStorage) return;                if (e.newValue == null || e.newValue === '') return;                if (e.key.substring(0, id.length) !== id) return;                var data = JSON.parse(e.newValue);                $this._mc.port2.postMessage(data);            }); }

BroadcastChannel.prototype = { // BroadcastChannel API get name { return this._name; },           postMessage: function(message) { var $this = this; if (this._closed) { var e = new Error; e.name = 'InvalidStateError'; throw e;               } var value = JSON.stringify(message);

// Broadcast to other contexts via storage events... var key = this._id + String(Date.now) + '$' + String(Math.random); global.localStorage.setItem(key, value); setTimeout(function {                   global.localStorage.removeItem(key);                }, 500);

// Broadcast to current context via ports channels[this._id].forEach(function(bc) {                   if (bc === $this) return;                    bc._mc.port2.postMessage(JSON.parse(value));                }); },           close: function { if (this._closed) return; this._closed = true; this._mc.port1.close; this._mc.port2.close;

var index = channels[this._id].indexOf(this); channels[this._id].splice(index, 1); },

// EventTarget API get onmessage { return this._mc.port1.onmessage; },           set onmessage(value) { this._mc.port1.onmessage = value; },           addEventListener: function(/*type, listener, useCapture*/) { return this._mc.port1.addEventListener.apply(this._mc.port1, arguments); },           removeEventListener: function(/*type, listener, useCapture*/) { return this._mc.port1.removeEventListener.apply(this._mc.port1, arguments); },           dispatchEvent: function(/*event*/) { return this._mc.port1.dispatchEvent.apply(this._mc.port1, arguments); },       };

global.BroadcastChannel = global.BroadcastChannel || BroadcastChannel; })(self);

/**    * Generates an ID using the current time and a random number. Quick and * dirty way to generate random IDs. *    * @return A string in the format `TIMESTAMP++RANDOM_NUMBER` */   function generateId  { return `${Date.now}++${Math.random.toString.slice(2)}`; }

/**    * A constant map of specific one-way Deputy message types and their respective * response messages. */   const OneWayDeputyMessageMap = { sessionRequest: 'sessionResponse', sessionResponse: 'sessionRequest', sessionStop: 'acknowledge', pageStatusRequest: 'pageStatusResponse', pageStatusResponse: 'pageStatusRequest', pageStatusUpdate: 'acknowledge', revisionStatusUpdate: 'acknowledge', pageNextRevisionRequest: 'pageNextRevisionResponse', pageNextRevisionResponse: 'pageNextRevisionRequest', userConfigUpdate: 'userConfigUpdate', wikiConfigUpdate: 'wikiConfigUpdate' };   // TODO: debug const start = Date.now; /**    * Handles inter-tab communication and automatically broadcasts events * to listeners. */   class DeputyCommunications extends EventTarget { /**        * Initialize communications. Begins listening for messages from other tabs. */       init { // Polyfills are loaded for BroadcastChannel support on older browsers. // eslint-disable-next-line compat/compat this.broadcastChannel = new BroadcastChannel('deputy-itc'); this.broadcastChannel.addEventListener('message', (event) => {               // TODO: debug                log(Date.now - start, 'comms in: ', event.data);                if (event.data && typeof event.data === 'object' && event.data._deputy) {                    this.dispatchEvent(Object.assign(new Event(event.data.type), {                        data: event.data                    }));                }            }); }       /**         * Sends data through this broadcast channel. *        * @param data * @return The sent message object */       send(data) { const messageId = generateId; const message = Object.assign(data, { _deputy: true, _deputyMessageId: messageId }); this.broadcastChannel.postMessage(message); // TODO: debug log(Date.now - start, 'comms out:', data); return message; }       /**         *         * @param original * @param reply */       reply(original, reply) { this.send(Object.assign(reply, { _deputyRespondsTo: original._deputyMessageId }));       }        /**         * Sends a message and waits for the first response. Subsequent responses are * ignored. Returns `null` once the timeout has passed with no responses. *        * @param data * @param timeout Time to wait for a response, 500ms by default */       sendAndWait(data, timeout = 500) { return __awaiter(this, void 0, void 0, function* {                return new Promise((res) => { const message = this.send(data); const handlers = {}; const clearHandlers = => { if (handlers.listener) { this.broadcastChannel.removeEventListener('message', handlers.listener); }                       if (handlers.timeout) { clearTimeout(handlers.timeout); }                   };                    handlers.listener = ((event) => {                        log(event);                        if (event.data._deputyRespondsTo === message._deputyMessageId && event.data.type === OneWayDeputyMessageMap[data.type]) {                           res(event.data);                            clearHandlers;                        }                    }); handlers.timeout = setTimeout( => {                       res(null);                        clearHandlers;                    }, timeout); this.broadcastChannel.addEventListener('message', handlers.listener); });           });        }        /**         * @param type The type of message to send. * @param callback The callback to call when the message is received. * @param options Optional options for the event listener. * @see {@link EventTarget#addEventListener} */       addEventListener(type, callback, options) { super.addEventListener(type, callback, options); }   }

/**    * Normalizes the title into an mw.Title object based on either a given title or     * the current page. *    * @param title The title to normalize. Default is current page. * @return {mw.Title} A mw.Title object. `null` if not a valid title. * @private */   function normalizeTitle(title) { if (title instanceof mw.Title) { return title; }       else if (typeof title === 'string') { return new mw.Title(title); }       else if (title == null) { // Null check goes first to avoid accessing properties of `null`. return new mw.Title(mw.config.get('wgPageName')); }       else if (title.title != null && title.namespace != null) { return new mw.Title(title.title, title.namespace); }       else { return null; }   }

/**    * Get the content of a page on-wiki. *    * @param page The page to get * @param extraOptions Extra options to pass to the request * @param api The API object to use * @return A promise resolving to the page content. Resolves to `null` if missing page. */   function getPageContent (page, extraOptions = {}, api = MwApi.action) { return api.get(Object.assign(Object.assign(Object.assign({ action: 'query', prop: 'revisions' }, (typeof page === 'number' ? {           pageids: page        } : {            titles: normalizeTitle(page).getPrefixedText        })), { rvprop: 'ids|content', rvslots: 'main', rvlimit: '1' }), extraOptions)).then((data) => {            const fallbackText = extraOptions.fallbacktext;            if (data.query.pages[0].revisions == null) {                if (fallbackText) {                    return Object.assign(fallbackText, { page: data.query.pages[0] });               }                else {                    return null;                }            }            return Object.assign(data.query.pages[0].revisions[0].slots.main.content, { contentFormat: data.query.pages[0].revisions[0].slots.main.contentformat, revid: data.query.pages[0].revisions[0].revid, page: data.query.pages[0] });       });    }

/**    * Used by DeputyCasePage to access the page's raw wikitext, make changes, * etc.    */ class DeputyCasePageWikitext { /**        *         * @param casePage */       constructor(casePage) { this.casePage = casePage; }       /**         * Gets the wikitext for this page. */       getWikitext { var _a; return __awaiter(this, void 0, void 0, function* {                return (_a = this.content) !== null && _a !== void 0 ? _a : (this.content = yield getPageContent(this.casePage.pageId));            }); }       /**         * Removes the cached wikitext for this page. */       resetCachedWikitext { this.content = undefined; }       /**         * Gets the wikitext for a specific section. The section will be parsed using the * wikitext cache if a section title was provided. Otherwise, it will attempt to        * grab the section using API:Query for an up-to-date version. *        * @param section The section to edit * @param n If the section heading appears multiple times in the page and n is        * provided, this function extracts the nth occurrence of that section heading. */       getSectionWikitext(section, n = 1) { return __awaiter(this, void 0, void 0, function* {                if (typeof section === 'number') {                    return getPageContent(this.casePage.pageId, { rvsection: section }).then((v) => { return Object.assign(v.toString, {                           revid: v.revid                        }); });               }                else {                    const wikitext = yield this.getWikitext;                    const wikitextLines = wikitext.split('\n');                    let capturing = false;                    let captureLevel = 0;                    let currentN = 1;                    const sectionLines = [];                    for (let i = 0; i < wikitextLines.length; i++) {                        const line = wikitextLines[i];                        const headerCheck = /^(=={1,5})\s*(.+?)\s*=={1,5}$/.exec(line);                        if (!capturing && headerCheck != null && headerCheck[2] === section) {                           if (currentN < n) {                                currentN++;                            }                            else {                                sectionLines.push(line);                                capturing = true;                                captureLevel = headerCheck[1].length;                            }                        }                        else if (capturing) {                            if (headerCheck != null && headerCheck[1].length <= captureLevel) {                                capturing = false;                                break;                            }                            else {                                sectionLines.push(line);                            }                        }                    }                    return Object.assign(sectionLines.join('\n'), { revid: wikitext.revid });               }            });        }    }

/**    * Gets the page title of a given page ID. *    * @param pageID */   function getPageTitle (pageID) { var _a, _b, _c, _d; return __awaiter(this, void 0, void 0, function* {            const pageIdQuery = yield MwApi.action.get({ action: 'query', pageids: pageID });           const title = (_d = (_c = (_b = (_a = pageIdQuery === null || pageIdQuery === void 0 ? void 0 : pageIdQuery.query) === null || _a === void 0 ? void 0 : _a.pages) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.title) !== null && _d !== void 0 ? _d : null;           return title == null ? null : normalizeTitle(title);        }); }

/**    * Base class for Deputy cases. Extended into {@link DeputyCasePage} to refer to an    * active case page. Used to represent case pages in a more serializable way. */   class DeputyCase { /**        * @param pageId The page ID of the case page. * @param title The title of the case page. */       constructor(pageId, title) { this.pageId = pageId; this.title = title; }       /**         * @return the title of the case page */       static get rootPage { return window.deputy.wikiConfig.cci.rootPage.get; }       /**         * Checks if the current page (or a supplied page) is a case page (subpage of         * the root page). *        * @param title The title of the page to check. * @return `true` if the page is a case page. */       static isCasePage(title) { return normalizeTitle(title).getPrefixedDb .startsWith(this.rootPage.getPrefixedDb + '/'); }       /**         * Gets the case name by parsing the title. *        * @param title The title of the case page * @return The case name, or `null` if the title was not a valid case page */       static getCaseName(title) { const _title = normalizeTitle(title); if (!this.isCasePage(_title)) { return null; }           else { return _title.getPrefixedText.replace(this.rootPage.getPrefixedText + '/', ''); }       }        /**         * Creats a Deputy case object. *        * @param pageId The page ID of the case page. * @param title The title of the case page. */       static build(pageId, title) { return __awaiter(this, void 0, void 0, function* {                if (title == null) {                    title = yield getPageTitle(pageId);                }                return new DeputyCase(pageId, title);            }); }       /**         * Gets the case name by parsing the title. *        * @return The case name, or `null` if the title was not a valid case page */       getCaseName { return DeputyCase.getCaseName(this.title); }   }

/**    * Returns the last item of an array. *    * @param array The array to get the last element from * @return The last element of the array */   function last(array) { return array[array.length - 1]; }

/**    * Each WikiHeadingType implies specific fields in {@link WikiHeading}: *    * - `PARSOID` implies that there is no headline element, and that the `h` *  element is the root heading element. This means `h.innerText` will be    *   "Section title". * - `OLD` implies that there is a headline element and possibly an editsection *  element, and that the `h` is the root heading element. This means that *  `h.innerText` will be "Section title[edit | edit source]" or similar. * - `NEW` implies that there is a headline element and possibly an editsection *  element, and that a `div` is the root heading element. This means that *  `h.innerText` will be "Section title". */   var WikiHeadingType; (function (WikiHeadingType) {       WikiHeadingType[WikiHeadingType["PARSOID"] = 0] = "PARSOID";        WikiHeadingType[WikiHeadingType["OLD"] = 1] = "OLD";        WikiHeadingType[WikiHeadingType["NEW"] = 2] = "NEW";    })(WikiHeadingType || (WikiHeadingType = {})); /**    * Get relevant information from an H* element in a section heading. *    * @param headingElement The heading element * @return An object containing the relevant {@link WikiHeading} fields. */   function getHeadingElementInfo(headingElement) { return { h: headingElement, id: headingElement.id, title: headingElement.innerText, level: +last(headingElement.tagName) };   }    /**     * Annoyingly, there are many different ways that a heading can be parsed * into depending on the version and the parser used for given wikitext. *    * In order to properly perform such wiki heading checks, we need to identify * if a given element is part of a wiki heading, and perform a normalization * if so. *    * Since this function needs to check many things before deciding if a given * HTML element is part of a section heading or not, this also acts as an    * `isWikiHeading` check. *    * The layout for a heading differs depending on the MediaWiki version: *    * On 1.43+ (Parser) * ```html *     *     Parsed wikitext...      *     ...      * * ```    *     * On Parsoid * ```html * Parsed wikitext...     * ``` *    * On pre-1.43 * ```html *     *     Parsed wikitext...      *     ...      * * ```    *     * Worst case execution time would be if this was run with an element which was * outside a heading and deeply nested within the page. *    * Backwards-compatibility support may be removed in the future. This function does not * support Parsoid specification versions lower than 2.0. *    * @param node The node to check for * @param ceiling An element which `node` must be in to be a valid heading. *               This is set to the `.mw-parser-output` element by default. * @return The root heading element (can be an &lt;h2&gt; or &lt;div&gt;), *        or `null` if it is not a valid heading. */   function normalizeWikiHeading(node, ceiling) { var _a; if (node == null) { // Not valid input, obviously. return null; }       const rootNode = node.getRootNode; // Break out of text nodes until we hit an element node. while (node.nodeType !== node.ELEMENT_NODE) { node = node.parentNode; if (node === rootNode) { // We've gone too far and hit the root. This is not a wiki heading. return null; }       }        // node is now surely an element. let elementNode = node; // If this node is the 1.43+ heading root, return it immediately. if (elementNode.classList.contains('mw-heading')) { return Object.assign({ type: WikiHeadingType.NEW, root: elementNode }, getHeadingElementInfo(Array.from(elementNode.children) .find(v => /^H[123456]$/.test(v.tagName)))); }       // Otherwise, we're either inside or outside a mw-heading. // To determine if we are inside or outside, we keep climbing up until // we either hit an  or a given stop point. // The default stop point differs on Parsoid and standard parser: // - On Parsoid, ` ` will be `.mw-body-content.mw-parser-output`. // - On standard parser, we want `div.mw-body-content > div.mw-parser.output`. // If such an element doesn't       // exist in this document, we just stop at the root element. ceiling = (_a = ceiling !== null && ceiling !== void 0 ? ceiling : elementNode.ownerDocument.querySelector('.mw-body-content > .mw-parser-output, .mw-body-content.mw-parser-output')) !== null && _a !== void 0 ? _a : elementNode.ownerDocument.documentElement; // While we haven't hit a heading, keep going up. while (elementNode !== ceiling) { if (/^H[123456]$/.test(elementNode.tagName)) { // This element is a heading! // Now determine if this is a MediaWiki heading. if (elementNode.parentElement.classList.contains('mw-heading')) { // This element's parent is a `div.mw-heading`! return Object.assign({ type: WikiHeadingType.NEW, root: elementNode.parentElement }, getHeadingElementInfo(elementNode)); }               else { const headline = elementNode.querySelector(':scope > .mw-headline'); if (headline != null) { // This element has a `.mw-headline` child! return { type: WikiHeadingType.OLD, root: elementNode, h: elementNode, id: headline.id, title: headline.innerText, level: +last(elementNode.tagName) };                   }                    else if (elementNode.parentElement.tagName === 'SECTION' &&                        elementNode.parentElement.firstElementChild === elementNode) { // A element is directly above this element, and it is the // first element of that section! // This is a specific format followed by the 2.8.0 MediaWiki Parsoid spec. // https://www.mediawiki.org/wiki/Specs/HTML/2.8.0#Headings_and_Sections return { type: WikiHeadingType.PARSOID, root: elementNode, h: elementNode, id: elementNode.id, title: elementNode.innerText, level: +last(elementNode.tagName) };                   }                    else { // This is a heading, but we can't figure out how it works. // This usually means something inserted an into the DOM, and we                       // accidentally picked it up. // In that case, discard it. return null; }               }            }            else if (elementNode.classList.contains('mw-heading')) { // This element is the `div.mw-heading`! // This usually happens when we selected an element from inside the // `span.mw-editsection` span. return Object.assign({ type: WikiHeadingType.NEW, root: elementNode }, getHeadingElementInfo(Array.from(elementNode.children) .find(v => /^H[123456]$/.test(v.tagName)))); }           else { // Haven't reached the top part of a heading yet, or we are not // in a heading. Keep climbing up the tree until we hit the ceiling. elementNode = elementNode.parentElement; }       }        // We hit the ceiling. This is not a wiki heading. return null; }

/**    * Check if a given parameter is a wikitext heading parsed into HTML. *    * Alias for `normalizeWikiHeading( el ) != null`. *    * @param el The element to check * @return `true` if the element is a heading, `false` otherwise */   function isWikiHeading(el) { return normalizeWikiHeading(el) != null; }

/**    * Finds section elements from a given section heading (and optionally a predicate) *    * @param sectionHeading * @param sectionHeadingPredicate A function which returns `true` if the section should stop here * @return Section headings. */   function getSectionElements(sectionHeading, sectionHeadingPredicate = isWikiHeading) { const sectionMembers = []; let nextSibling = sectionHeading.nextElementSibling; while (nextSibling != null && !sectionHeadingPredicate(nextSibling)) { sectionMembers.push(nextSibling); nextSibling = nextSibling.nextElementSibling; }       return sectionMembers; }

/**    * Handles Deputy case pages, controls UI features, among other things. * This class should be able to operate both on the standard MediaWiki * parser output and the Parsoid output. */   class DeputyCasePage extends DeputyCase { /**        * @param pageId The page ID of the case page. * @param title The title of the page being accessed * @param document The document to be used as a reference. * @param parsoid Whether this is a Parsoid document or not. * @param lastActive * @param lastActiveSessions */       constructor(pageId, title, document, parsoid, lastActive, lastActiveSessions) { super(pageId !== null && pageId !== void 0 ? pageId : window.deputy.currentPageId, title !== null && title !== void 0 ? title : window.deputy.currentPage); /**            * A timestamp of when this case page was last worked on. */           this.lastActive = Date.now; /**            * The sections last worked on for this case page. */           this.lastActiveSections = []; this.document = document !== null && document !== void 0 ? document : window.document; this.parsoid = parsoid !== null && parsoid !== void 0 ? parsoid : /mw: http:\/\/mediawiki.org\/rdf\//.test(this.document.documentElement.getAttribute('prefix')); this.wikitext = new DeputyCasePageWikitext(this); this.lastActive = lastActive !== null && lastActive !== void 0 ? lastActive : Date.now; this.lastActiveSections = lastActiveSessions !== null && lastActiveSessions !== void 0 ? lastActiveSessions : []; }       /**         * @param pageId The page ID of the case page. * @param title The title of the page being accessed * @param document The document to be used as a reference. * @param parsoid Whether this is a Parsoid document or not. */       static build(pageId, title, document, parsoid) { return __awaiter(this, void 0, void 0, function* {                const cachedInfo = yield window.deputy.storage.db.get('casePageCache', pageId !== null && pageId !== void 0 ? pageId : window.deputy.currentPageId);               if (cachedInfo != null) {                    if (pageId != null) {                        // Title might be out of date. Recheck for safety.                        title = yield getPageTitle(pageId);                    }                    // Fix for old data (moved from section name to IDs as of c5251642)                    const oldSections = cachedInfo.lastActiveSections.some((v) => v.indexOf(' ') !== -1);                    if (oldSections) {                        cachedInfo.lastActiveSections =                            cachedInfo.lastActiveSections.map((v) => v.replace(/ /g, '_'));                    }                    const casePage = new DeputyCasePage(pageId, title, document, parsoid, cachedInfo.lastActive, cachedInfo.lastActiveSections);                    if (oldSections) {                        // Save to fix the data in storage yield casePage.saveToCache; }                   return casePage; }               else { return new DeputyCasePage(pageId, title, document, parsoid); }           });        }        /**         * Checks if a given element is a valid contribution survey heading.         *         * @param el The element to check for         * @return `true` if the given heading is a valid contribution survey heading.         */        isContributionSurveyHeading(el) {            if (!(el instanceof HTMLElement)) {                return false;            }            const heading = normalizeWikiHeading(el);            return heading != null &&                // Require that this heading is already normalized.                // TODO: Remove at some point.                //       This shouldn't be required if double-normalization wasn't a thing.                el === heading.h &&                // eslint-disable-next-line security/detect-non-literal-regexp                new RegExp(window.deputy.wikiConfig.cci.headingMatch.get).test(heading.title); }       /**         * Finds the first contribution survey heading. This is always an  element * with the content matching the pattern "Pages \d+ to \d+" *        * @return The  element of the heading. */       findFirstContributionSurveyHeadingElement { return this.findContributionSurveyHeadings[0]; }       /**         * Find a contribution survey heading by section name. *        * @param sectionIdentifier The section identifier to look for, usually the section * name unless `useId` is set to true. * @param useId Whether to use the section name instead of the ID        * @return The  element of the heading. */       findContributionSurveyHeading(sectionIdentifier, useId = false) { return this.findContributionSurveyHeadings .find((v) => normalizeWikiHeading(v)[useId ? 'id' : 'title'] === sectionIdentifier); }       /**         * Finds all contribution survey headings. These are  elements * with the content matching the pattern "Pages \d+ to \d+" *        * @return The  element of the heading. */       findContributionSurveyHeadings { if (!DeputyCasePage.isCasePage) { throw new Error('Current page is not a case page. Expected subpage of ' +                   DeputyCasePage.rootPage.getPrefixedText); }           else { return Array.from(this.document.querySelectorAll( // All headings (`h1, h2, h3, h4, h5, h6`) [1, 2, 3, 4, 5, 6]                   .map((i) => `.mw-parser-output h${i}`) .join(','))) .filter((h) => this.isContributionSurveyHeading(h)); }       }        /**         * Gets all elements that are part of a contribution survey "section", that is         * a set of elements including the section heading and all elements succeeding * the heading until (and exclusive of) the heading of the next section. *        * In other words, * YES: === Pages 1 to 2 === * YES: * Page 1 * YES: * Page 2 * YES: * NO : === Pages 3 to 4 === *        * @param sectionHeading The section heading to work with * @return An array of all HTMLElements covered by the section */       getContributionSurveySection(sectionHeading) { const heading = normalizeWikiHeading(sectionHeading); const ceiling = heading.root.parentElement; return getSectionElements(heading.root, (el) => {               var _a, _b;                // TODO: Avoid double normalization                const norm = normalizeWikiHeading(el, ceiling);                return (heading.level >= ((_a = norm === null || norm === void 0 ? void 0 : norm.level) !== null && _a !== void 0 ? _a : Infinity)) ||                   this.isContributionSurveyHeading((_b = norm === null || norm === void 0 ? void 0 : norm.h) !== null && _b !== void 0 ? _b : el);           }); }       /**         * Check if this page is cached. */       isCached { return __awaiter(this, void 0, void 0, function* {                return (yield window.deputy.storage.db.get('casePageCache', this.pageId)) != null;            }); }       /**         * Saves the current page to the IDB page cache. */       saveToCache { return __awaiter(this, void 0, void 0, function* {                yield window.deputy.storage.db.put('casePageCache', { pageID: this.pageId, lastActive: this.lastActive, lastActiveSections: this.lastActiveSections });           });        }        /**         * Deletes the current page from the cache. This is generally not advised, unless the * user wishes to forget the case page entirely. */       deleteFromCache { return __awaiter(this, void 0, void 0, function* {                yield window.deputy.storage.db.delete('casePageCache', this.pageId);            }); }       /**         * Bumps this page's last active timestamp. */       bumpActive { return __awaiter(this, void 0, void 0, function* {                this.lastActive = Date.now;                yield this.saveToCache;            }); }       /**         * Add a section to the list of active sessions. This is used for automatic starting * and for one-click continuation of past active sessions. *        * @param sectionId The ID of the section to add. */       addActiveSection(sectionId) { return __awaiter(this, void 0, void 0, function* {                const lastActiveSection = this.lastActiveSections.indexOf(sectionId);                if (lastActiveSection === -1) {                    this.lastActiveSections.push(sectionId);                    yield this.saveToCache;                }            }); }       /**         * Remove a section from the list of active sections. This will disable autostart * for this section. *        * @param sectionId ID of the section to remove */       removeActiveSection(sectionId) { return __awaiter(this, void 0, void 0, function* {                const lastActiveSection = this.lastActiveSections.indexOf(sectionId);                if (lastActiveSection !== -1) {                    this.lastActiveSections.splice(lastActiveSection, 1);                    yield this.saveToCache;                }            }); }   }

var dist = {};

/* eslint-disable @typescript-eslint/no-unused-vars */ /**    * License: MIT * @author Santo Pfingsten * @see https://github.com/Lusito/tsx-dom */   Object.defineProperty(dist, "__esModule", { value: true }); var h_1 = dist.h = void 0; function applyChild(element, child) { if (child instanceof Element) element.appendChild(child); else if (typeof child === "string" || typeof child === "number") element.appendChild(document.createTextNode(child.toString)); else console.warn("Unknown type to append: ", child); }   function applyChildren(element, children) { for (const child of children) { if (!child && child !== 0) continue; if (Array.isArray(child)) applyChildren(element, child); else applyChild(element, child); }   }    function transferKnownProperties(source, target) { for (const key of Object.keys(source)) { if (key in target) target[key] = source[key]; }   }    function createElement(tag, attrs) { const options = (attrs === null || attrs === void 0 ? void 0 : attrs.is) ? { is: attrs.is } : undefined; if (attrs === null || attrs === void 0 ? void 0 : attrs.xmlns) return document.createElementNS(attrs.xmlns, tag, options); return document.createElement(tag, options); }   function h(tag, attrs, ...children) { if (typeof tag === "function") return tag(Object.assign(Object.assign({}, attrs), { children })); const element = createElement(tag, attrs); if (attrs) { for (const name of Object.keys(attrs)) { // Ignore some debug props that might be added by bundlers if (name === "__source" || name === "__self" || name === "is" || name === "xmlns") continue; const value = attrs[name]; if (name.startsWith("on")) { const finalName = name.replace(/Capture$/, ""); const useCapture = name !== finalName; const eventName = finalName.toLowerCase.substring(2); element.addEventListener(eventName, value, useCapture); }               else if (name === "style" && typeof value !== "string") { // Special handler for style with a value of type CSSStyleDeclaration transferKnownProperties(value, element.style); }               else if (name === "dangerouslySetInnerHTML") element.innerHTML = value; else if (value === true) element.setAttribute(name, name); else if (value || value === 0) element.setAttribute(name, value.toString); }       }        applyChildren(element, children); return element; }   h_1 = dist.h = h;

/**    * The CCI session start link. Starts a CCI session when pressed. *    * @param heading The heading to use as a basis * @param casePage If a DeputyCasePage is provided, a "continue" button will be shown instead. * @return The link element to be displayed */   function DeputyCCISessionStartLink (heading, casePage) { return h_1("span", { class: "deputy dp-sessionStarter" },           h_1("span", { class: "dp-sessionStarter-bracket" }, "["),            h_1("a", { onClick:  => __awaiter(this, void 0, void 0, function*  {                    if (casePage && casePage.lastActiveSections.length > 0) {                        const headingId = heading.id;                        if (window.deputy.config.cci.openOldOnContinue.get) {                            if (casePage.lastActiveSections.indexOf(headingId) === -1) {                                yield casePage.addActiveSection(headingId);                            }                            yield window.deputy.session.DeputyRootSession.continueSession(casePage);                        }                        else {                            yield window.deputy.session.DeputyRootSession.continueSession(casePage, [headingId]);                        } }                   else { yield window.deputy.session.DeputyRootSession.startSession(heading.h); }               }) }, mw.message(casePage && casePage.lastActiveSections.length > 0 ? 'deputy.session.continue' : 'deputy.session.start').text), h_1("span", { class: "dp-sessionStarter-bracket" }, "]"));   }

/**    * Removes an element from its document. *    * @param element * @return The removed element */   function removeElement (element) { var _a; return (_a = element === null || element === void 0 ? void 0 : element.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(element); }

/**    * Log errors to the console. *    * @param {...any} data */   function error(...data) { console.error('[Deputy]', ...data); }

/**    * Unwraps an OOUI widget from its JQuery `$element` variable and returns it as an     * HTML element. *    * @param el The widget to unwrap. * @return The unwrapped widget. */   function unwrapWidget (el) { if (el.$element == null) { error(el); throw new Error('Element is not an OOUI Element!'); }       return el.$element[0]; }

/**    * Creates the "Start working on section" overlay over existing contribution survey * sections that are not upgraded. *    * @param props * @param props.casePage * @param props.heading * @param props.height * @return HTML element */   function DeputyCCISessionAddSection (props) { const { casePage, heading } = props; const startButton = new OO.ui.ButtonWidget({           classes: ['dp-cs-section-addButton'],            icon: 'play',            label: mw.msg('deputy.session.add'),            flags: ['primary', 'progressive']        }); const element = h_1("div", { style: { height: props.height + 'px' }, class: "dp-cs-section-add" }, unwrapWidget(startButton)); startButton.on('click', => {            // This element is automatically appended to the UL of the section, which is a no-no            // for ContributionSurveySection. This sneakily removes this element before any sort            // of activation is performed.            removeElement(element);            window.deputy.session.rootSession.activateSection(casePage, heading);        }); return element; }

/**    * Clones a regular expression. *    * @param regex The regular expression to clone. * @param options * @return A new regular expression object. */   function cloneRegex$1 (regex, options = {}) { return new RegExp(options.transformer ? options.transformer(regex.source) :           `${options.pre || }${regex.source}${options.post || }`, regex.flags); }

/**    * Contains information about a specific revision in a ContributionSurveyRow. */   class ContributionSurveyRevision { /**        * Creates a new ContributionSurveyRowRevision *        * @param row * @param revisionData */       constructor(row, revisionData) { Object.assign(this, revisionData); this.row = row; }   }

/**    * Data that constructs a raw contribution survey row. */   /**     * Parser for {@link ContributionSurveyRow}s. *    * This is used directly in unit tests. Do not import unnecessary * dependencies, as they may indirectly import the entire Deputy * codebase outside a browser environment. */   class ContributionSurveyRowParser { /**        *         * @param wikitext */       constructor(wikitext) { this.wikitext = wikitext; this.current = wikitext; }       /**         * Parses a wikitext contribution survey row into a {@link RawContributionSurveyRow}. * If invalid, an Error is thrown with relevant information. *        * @return Components of a parsed contribution survey row. */       parse { var _a, _b; this.current = this.wikitext; const bullet = this.eatUntil(/^[^*\s]/g); if (!bullet) { throw new Error('dp-malformed-row'); }           const creation = this.eatExpression(/^\s*N\s*/g) != null; const page = this.eatExpression(/\[\[([^\]|]+)(?:\|.*)?]]/g, 1); if (!page) { // Malformed or unparsable listing. throw new Error('dp-undetectable-page-name'); }           let extras = // 6789 (_a = this.eatUntil(/^(?:'''?)?\[\[Special:Diff\/\d+/, true)) !== null && _a !== void 0 ? _a : //            this.eatUntil(/^(?:'''?)?{{dif\|\d+/, true); let diffsBolded = false; // At this point, `extras` is either a string or `null`. If it's a string, // extras exist, and we should add them. If not, there's likely no more // revisions to be processed here, and can assume that the rest is user comments. const revids = []; const revidText = {}; let diffs = null, comments, diffTemplate = '($2)'; if (extras) { const starting = this.current; let diff = true; while (diff) { const diffMatch = // 6789 (_b = this.eatExpressionMatch(/\s*(?:?)?\[\[Special:Diff\/(\d+)(?:\|([^\]]*))?]](?:?)?/g)) !== null && _b !== void 0 ? _b : //                    this.eatExpressionMatch(/\s*(?:?)?{{dif\|(\d+)\|([^}]+)}}(?:?)?/g); diff = diffMatch === null || diffMatch === void 0 ? void 0 : diffMatch[1]; if (diff != null) { revids.push(+diff); revidText[+diff] = diffMatch[2].replace(/^\(|\)$/g, ''); }               }                // All diff links removed. Get diff of starting and current to get entire diff part. diffs = starting.slice(0, starting.length - this.current.length); // Bolded diffs support. if (diffs.slice(0, 3) === "" &&                   diffs.slice(-3) === "" &&                    !diffs.slice(3, -3).includes("'''")) { diffsBolded = true; }               // Pre-2014 style support. if ((diffs !== null && diffs !== void 0 ? diffs : '').includes('{{dif')) { diffsBolded = true; diffTemplate = '{{dif|$1|($2)}}'; }               // Comments should be empty, but just in case we do come across comments. comments = this.isEmpty ? null : this.eatRemaining; }           else { // Try to grab extras. This is done by detecting any form of parentheses and // matching them, including any possible included colon. If that doesn't work, // try pulling out just the colon. const maybeExtras = this.eatExpression(/\s*(?::\s*)?\(.+?\)(?:\s*:)?\s*/) || this.eatExpression(/\s*:\s*/g); if (maybeExtras) { extras = maybeExtras; }               // Only comments probably remain. Eat out whitespaces and the rest is a comment. extras = (extras || ) + (this.eatUntil(/^\S/g, true) || ); if (extras === '') { extras = null; }               comments = this.getCurrentLength > 0 ? this.eatRemaining : null; }           // "{bullet}{creation}{page}{extras}{diffs}{comments}" return { type: (extras || comments || diffs) == null ? 'pageonly' : 'detailed', bullet, creation, page, extras, diffs, comments, revids, revidText, diffTemplate, diffsTemplate: diffsBolded ? "$1" : '$1'           };        }        /**         * Returns `true` if the working string is empty. *        * @return `true` if the length of `current` is zero. `false` if otherwise. */       isEmpty { return this.current.length === 0; }       /**         * @return the length of the working string. */       getCurrentLength { return this.current.length; }       /**         * Views the next character to {@link ContributionSurveyRowParser#eat}. *        * @return The first character of the working string. */       peek { return this.current[0]; }       /**         * Pops the first character off the working string and returns it. *        * @return First character of the working string, pre-mutation. */       eat { const first = this.current[0]; this.current = this.current.slice(1); return first; }       /**         * Continue eating from the string until a string or regular expression * is matched. Unlike {@link eatExpression}, passed regular expressions * will not be re-wrapped with `^(?:)`. These must be added on your own if        * you wish to match the start of the string. *        * @param pattern The string or regular expression to match. * @param noFinish If set to `true`, `null` will be returned instead if the * pattern is never matched. The working string will be reset to its original * state if this occurs. This prevents the function from being too greedy. * @return The consumed characters. */       eatUntil(pattern, noFinish) { const starting = this.current; let consumed = ''; while (this.current.length > 0) { if (typeof pattern === 'string') { if (this.current.startsWith(pattern)) { return consumed; }               }                else { if (cloneRegex$1(pattern).test(this.current)) { return consumed; }               }                consumed += this.eat; }           if (noFinish && this.current.length === 0) { // We finished the string! Reset. this.current = starting; return null; }           else { return consumed; }       }        /**         * Eats a given expression from the start of the working string. If the working * string does not contain the given expression, `null` is returned (and not a        * blank string). Only eats once, so any expression must be greedy if different * behavior is expected. *        * The regular expression passed into this function is automatically re-wrapped * with `^(?: is enabled",    	"deputy.ante.nocat.help": "This notice has the   parameter enabled and, as an effect, is not being tracked in categories. This usually means that the template is for demonstration purposes only.",    	"deputy.ante.nocat.clear": "Restore tracking",    	"deputy.ante.demo.head": "  is enabled",    	"deputy.ante.demo.help": "This notice has the   parameter enabled. This usually means that the template is for demonstration purposes only.",    	"deputy.ante.demo.clear": "Clear demo mode",    	"deputy.ante.invalid": "Some fields are still invalid.",    	"deputy.ante.adding": "Adding content attribution notices",    	"deputy.ante.modifying": "Modifying content attribution notices",    	"deputy.ante.dirty": "This dialog did not close properly last time. Your changes will be reset.",    	"deputy.ante.empty.header": "No notices",    	"deputy.ante.empty.removed": "All notices will be removed from the page. To reset your changes and restore previous templates, press the reset button at the bottom of the dialog.",   	"deputy.ante.empty.none": "There are currently no notices on the talk page.",    	"deputy.ante.empty.add": "Add a notice",    	"deputy.ante.noSpot": "Sorry, but a notice cannot be automatically added. Please contact the developer to possibly add support for this talk page.",   	"deputy.ante.merge": "Merge",    	"deputy.ante.merge.title": "Merge notices",    	"deputy.ante.merge.from.label": "Notices to merge",    	"deputy.ante.merge.from.select": "Select a notice",    	"deputy.ante.merge.from.empty": "No notices to merge",    	"deputy.ante.merge.all": "Merge all",    	"deputy.ante.merge.all.confirm": "You are about to merge $1 'copied' {{PLURAL:$1|notice|notices}} into this notice. Continue?",   	"deputy.ante.merge.button": "Merge",    	"deputy.ante.templateOptions": "Template options",    	"deputy.ante.dateAuto": "Pull the date from the provided revision ID (`$1` parameter)",    	"deputy.ante.dateAuto.invalid": "Parameter does not appear to be a valid revision ID.",    	"deputy.ante.dateAuto.failed": "Could not pull date from revision: $1",    	"deputy.ante.dateAuto.missing": "The revision $1 could not be found. Its page may have been deleted.",   	"deputy.ante.revisionAuto": "Latest",    	"deputy.ante.revisionAuto.title": "Pull the revision ID from the latest (current) revision of the page in `$1`.",    	"deputy.ante.revisionAuto.failed": "Could not pull revision ID from page: $1",    	"deputy.ante.revisionAuto.missing": "The page $1 could not be found. It may have been deleted.",   	"deputy.ante.copied.label": "Copied $1",    	"deputy.ante.copied.remove": "Remove notice",    	"deputy.ante.copied.remove.confirm": "This will destroy $1 {{PLURAL:$1|entry|entries}}. Continue?",   	"deputy.ante.copied.add": "Add entry",    	"deputy.ante.copied.entry.label": "Template entry",    	"deputy.ante.copied.entry.short": "$1 to $2",    	"deputy.ante.copied.entry.shortTo": "To $1",    	"deputy.ante.copied.entry.shortFrom": "From $1",    	"deputy.ante.copied.entry.remove": "Remove entry",    	"deputy.ante.copied.entry.copy": "Copy attribution edit summary",    	"deputy.ante.copied.entry.copy.lacking": "Attribution edit summary copied to clipboard with lacking properties. Ensure that `from` is supplied.",   	"deputy.ante.copied.entry.copy.success": "Attribution edit summary copied to clipboard.",    	"deputy.ante.copied.collapse": "Collapse",    	"deputy.ante.copied.small": "Small",    	"deputy.ante.copied.convert": "Convert",    	"deputy.ante.copied.from.placeholder": "Page A",    	"deputy.ante.copied.from.label": "Page copied from",    	"deputy.ante.copied.from.help": "This is the page from which the content was copied from.",    	"deputy.ante.copied.from_oldid.placeholder": "from_oldid",    	"deputy.ante.copied.from_oldid.label": "Revision ID",    	"deputy.ante.copied.from_oldid.help": "The specific revision ID at the time that the content was copied, if known.",    	"deputy.ante.copied.to.placeholder": "Page B",    	"deputy.ante.copied.to.label": "Page copied to",    	"deputy.ante.copied.to.help": "This is the page where the content was copied into.", "deputy.ante.copied.to_diff.placeholder": "to_diff", "deputy.ante.copied.to_diff.label": "Revision ID", "deputy.ante.copied.to_diff.help": "The specific revision ID of the revision that copied content into the target page. If the copying spans multiple revisions, this is the ID of the last revision that copies content into the page.", "deputy.ante.copied.to_oldid.placeholder": "to_oldid", "deputy.ante.copied.to_oldid.label": "Starting revision ID", "deputy.ante.copied.to_oldid.help": "The ID of the revision before any content was copied. This can be omitted unless multiple revisions copied content into the page.", "deputy.ante.copied.diff.placeholder": "https://en.wikipedia.org/w/index.php?diff=123456", "deputy.ante.copied.diff.label": "Diff URL", "deputy.ante.copied.diff.help": "The URL of the diff. Using  and   is preferred, although supplying this parameter will override both.", "deputy.ante.copied.merge.label": "Merged?", "deputy.ante.copied.merge.help": "Whether the copying was done as a result of merging two pages.", "deputy.ante.copied.afd.placeholder": "AfD page (without Wikipedia:Articles for deletion/)", "deputy.ante.copied.afd.label": "AfD page", "deputy.ante.copied.afd.help": "The AfD page if the copy was made due to an AfD closed as \"merge\".", "deputy.ante.copied.date.placeholder": "Date (YYYY-MM-DD)", "deputy.ante.copied.advanced": "Advanced", "deputy.ante.copied.dateInvalid": "The previous date value, \"$1\", was not a valid date.", "deputy.ante.copied.diffDeprecate": "The  and   parameters are preferred in favor of the   parameter.", "deputy.ante.copied.diffDeprecate.warnHost": "The URL in this parameter is not the same as the wiki you're currently editing on. Continue?", "deputy.ante.copied.diffDeprecate.replace": "The current value of '$1', \"$2\", will be replaced with \"$3\". Continue?", "deputy.ante.copied.diffDeprecate.failed": "Cannot convert `diff` parameter to URL. See your browser console for more details.", "deputy.ante.splitArticle.label": "Split article $1", "deputy.ante.splitArticle.remove": "Remove notice", "deputy.ante.splitArticle.remove.confirm": "This will destroy $1 {{PLURAL:$1|entry|entries}}. Continue?", "deputy.ante.splitArticle.add": "Add entry", "deputy.ante.splitArticle.entry.label": "Template entry", "deputy.ante.splitArticle.entry.remove": "Remove entry", "deputy.ante.splitArticle.entry.short": "$1 on $2", "deputy.ante.splitArticle.collapse": "Collapse", "deputy.ante.splitArticle.from": "From", "deputy.ante.splitArticle.from.help": "This is the page where the content was split from. In most cases, this is the current page, and can be left blank.", "deputy.ante.splitArticle.to.placeholder": "Subpage A", "deputy.ante.splitArticle.to.label": "Page split to", "deputy.ante.splitArticle.to.help": "This is the name of page that material was copied to; the \"merge target\".", "deputy.ante.splitArticle.from_oldid.placeholder": "from_oldid", "deputy.ante.splitArticle.from_oldid.label": "As of revision ID", "deputy.ante.splitArticle.from_oldid.help": "The revision ID of the original page prior to the split. This is the revision that still contains content that will eventually become part of the split, with the following revision (or succeeding revisions) progressively transferring content to the other pages.", "deputy.ante.splitArticle.date.label": "Date of split", "deputy.ante.splitArticle.date.help": "The date that the split occurred.", "deputy.ante.splitArticle.diff.placeholder": "123456789", "deputy.ante.splitArticle.diff.label": "Diff", "deputy.ante.splitArticle.diff.help": "The diff URL or revision ID of the split.", "deputy.ante.mergedFrom.label": "Merged from $1", "deputy.ante.mergedFrom.remove": "Remove notice", "deputy.ante.mergedFrom.article.placeholder": "Page A", "deputy.ante.mergedFrom.article.label": "Original article", "deputy.ante.mergedFrom.article.help": "This is the page where the merged content is or used to be.", "deputy.ante.mergedFrom.date.label": "Date", "deputy.ante.mergedFrom.date.help": "The date (in UTC) when the content was merged into this page.", "deputy.ante.mergedFrom.talk.label": "Link to talk?", "deputy.ante.mergedFrom.talk.help": "Whether to link to the original article's talk page or not.", "deputy.ante.mergedFrom.target.placeholder": "Page B", "deputy.ante.mergedFrom.target.label": "Merge target", "deputy.ante.mergedFrom.target.help": "The page that the content was merged into. Used if the page that the content was merged into was the talk page.", "deputy.ante.mergedFrom.afd.placeholder": "Wikipedia:Articles for deletion/Page A", "deputy.ante.mergedFrom.afd.label": "AfD", "deputy.ante.mergedFrom.afd.help": "The AfD discussion that led to the merge. If this merge was not the result of an AfD discussion, leave this blank.", "deputy.ante.mergedTo.label": "Merged to $1", "deputy.ante.mergedTo.remove": "Remove notice", "deputy.ante.mergedTo.to.placeholder": "Page A", "deputy.ante.mergedTo.to.label": "Target article", "deputy.ante.mergedTo.to.help": "This is the page where content was copied into.", "deputy.ante.mergedTo.date.label": "Date", "deputy.ante.mergedTo.date.help": "The date (in UTC) when the content was merged into this page.", "deputy.ante.mergedTo.small.label": "Small", "deputy.ante.mergedTo.small.help": "If enabled, makes the banner small.", "deputy.ante.backwardsCopy.label": "Backwards copy $1", "deputy.ante.backwardsCopy.remove": "Remove notice", "deputy.ante.backwardsCopy.bot": "This notice was automatically added in by $1 (talk). Changing this template will remove this warning as it is assumed that you have properly vetted the bot-added parameters.", "deputy.ante.backwardsCopy.entry.label": "Template entry", "deputy.ante.backwardsCopy.entry.short": "Copied in '$1'", "deputy.ante.backwardsCopy.entry.remove": "Remove entry", "deputy.ante.backwardsCopy.comments.placeholder": "Additional information", "deputy.ante.backwardsCopy.comments.label": "Comments", "deputy.ante.backwardsCopy.comments.help": "Additional comments related to the backwards copies.", "deputy.ante.backwardsCopy.id.placeholder": "123456789", "deputy.ante.backwardsCopy.id.label": "Revision ID", "deputy.ante.backwardsCopy.id.help": "The last revision ID of this article that does not contain content that was duplicated by copying media.", "deputy.ante.backwardsCopy.entry.title.placeholder": "Article, journal, or medium name", "deputy.ante.backwardsCopy.entry.title.label": "Publication name", "deputy.ante.backwardsCopy.entry.title.help": "The publication title. This is the title of the medium that copied from Wikipedia", "deputy.ante.backwardsCopy.entry.date.placeholder": "12 Feburary 2022", "deputy.ante.backwardsCopy.entry.date.label": "Publishing date", "deputy.ante.backwardsCopy.entry.date.help": "This is the date on which the article was first published.", "deputy.ante.backwardsCopy.entry.author.placeholder": "Add author", "deputy.ante.backwardsCopy.entry.author.label": "Author", "deputy.ante.backwardsCopy.entry.author.help": "The article's author.", "deputy.ante.backwardsCopy.entry.url.placeholder": "https://example.com/news/a-news-article-that-copies-from-wikipedia", "deputy.ante.backwardsCopy.entry.url.label": "URL", "deputy.ante.backwardsCopy.entry.url.help": "A URL to the published media, if it exists as an online resource. If this is not an online resource (newspaper media, other printed media), leave this blank.", "deputy.ante.backwardsCopy.entry.org.placeholder": "Example Publishing", "deputy.ante.backwardsCopy.entry.org.label": "Publisher", "deputy.ante.backwardsCopy.entry.org.help": "The publisher of the media. This may be a news company or a book publishing company.", "deputy.ante.translatedPage.label": "Translated from $1:$2", "deputy.ante.translatedPage.remove": "Remove notice", "deputy.ante.translatedPage.lang.placeholder": "en, de, fr, es, etc.", "deputy.ante.translatedPage.lang.label": "Language code", "deputy.ante.translatedPage.lang.help": "The language code of the wiki that the page was translated from. This is the \"en\" of the English Wikipedia, or the \"fr\" of the French Wikipedia.", "deputy.ante.translatedPage.page.placeholder": "Page on other wiki", "deputy.ante.translatedPage.page.label": "Source page", "deputy.ante.translatedPage.page.help": "The page on the other wiki that the content was copied from. Do not translate the page title.", "deputy.ante.translatedPage.comments.placeholder": "Additional comments", "deputy.ante.translatedPage.comments.label": "Comments", "deputy.ante.translatedPage.comments.help": "Additional comments that are pertinent to translation.", "deputy.ante.translatedPage.version.placeholder": "123456789", "deputy.ante.translatedPage.version.label": "Source revision ID", "deputy.ante.translatedPage.version.help": "The revision ID of the source page at the time of translation.", "deputy.ante.translatedPage.insertversion.placeholder": "987654321", "deputy.ante.translatedPage.insertversion.label": "Insertion revision ID", "deputy.ante.translatedPage.insertversion.help": "The revision ID of the revision where the translated content was inserted into the page bearing this notice.", "deputy.ante.translatedPage.section.placeholder": "Section name (leave blank if N/A)", "deputy.ante.translatedPage.section.label": "Section", "deputy.ante.translatedPage.section.help": "The section of the page that was translated, if a specific section was translated. Leave blank if this does not apply, or if translation was performed on the entire page or more than one section.", "deputy.ante.translatedPage.small.label": "Small?", "deputy.ante.translatedPage.small.help": "Whether to render the template as a small message box or not. By default, a small box is used. If you have a good reason to use a full-sized banner, disable this option.", "deputy.ante.translatedPage.partial.label": "Partial?", "deputy.ante.translatedPage.partial.help": "Whether this translation is a partial translation or not.", "deputy.ante.translatedPage.copy": "Copy attribution edit summary", "deputy.ante.translatedPage.copy.lacking": "Attribution edit summary copied to clipboard with lacking properties. Ensure that `from` is supplied.", "deputy.ante.translatedPage.copy.success": "Attribution edit summary copied to clipboard." };

var deputySharedEnglish = { "deputy.name": "Deputy", "deputy.description": "Copyright cleanup and case processing tool for Wikipedia.", "deputy.ia": "Infringement Assistant", "deputy.ia.short": "I. Assistant", "deputy.ia.acronym": "Deputy: IA", "deputy.ante": "Attribution Notice Template Editor", "deputy.ante.short": "Attrib. Template Editor", "deputy.ante.acronym": "Deputy: ANTE", "deputy.cancel": "Cancel", "deputy.review": "Review", "deputy.review.title": "Review a diff of the changes to be made to the page", "deputy.save": "Save", "deputy.close": "Close", "deputy.positiveDiff": "+NaN", "deputy.negativeDiff": "-NaN", "deputy.zeroDiff": "0", "deputy.brokenDiff": "?", "deputy.brokenDiff.explain": "The internal parent revision ID for this diff points to a non-existent revision. T186280 has more information.", "deputy.moreInfo": "More information", "deputy.dismiss": "Dismiss", "deputy.revision.cur": "cur", "deputy.revision.prev": "prev", "deputy.revision.cur.tooltip": "Difference with latest revision", "deputy.revision.prev.tooltip": "Difference with preceding revision", "deputy.revision.talk": "talk", "deputy.revision.contribs": "contribs", "deputy.revision.bytes": "NaN bytes", "deputy.revision.byteChange": "NaN bytes after change of this size", "deputy.revision.tags": "NaN Tagss:", "deputy.revision.new": "N", "deputy.revision.new.tooltip": "This edit created a new page.", "deputy.comma-separator": ", ", "deputy.diff": "Review your changes", "deputy.diff.load": "Loading changes...", "deputy.diff.no-changes": "No difference", "deputy.diff.error": "An error occurred while trying to get the comparison.", "deputy.loadError.userConfig": "Due to an error, your Deputy configuration has been reset.", "deputy.loadError.wikiConfig": "An error occurred while loading this wiki's Deputy configuration. Please report this to the Deputy maintainers for this wiki." };

/**    * A Deputy module. Modules are parts of Deputy that can usually be removed * and turned into standalone components that can load without Deputy. */   class DeputyModule { /**        *         * @param deputy */       constructor(deputy) { this.deputy = deputy; }       /**         * @return The responsible window manager for this class. */       get windowManager { if (!this.deputy) { if (!this._windowManager) { this._windowManager = new OO.ui.WindowManager; document.body.appendChild(unwrapWidget(this._windowManager)); }               return this._windowManager; }           else { return this.deputy.windowManager; }       }        /**         * @return the configuration handler for this module. If Deputy is loaded, this reuses * the configuration handler of Deputy. */       get config { var _a; if (!this.deputy) { return (_a = this._config) !== null && _a !== void 0 ? _a : (this._config = UserConfiguration.load); }           else { return this.deputy.config; }       }        /**         * @return the wiki-wide configuration handler for this module. If Deputy is loaded, * this reuses the configuration handler of Deputy. Since the wiki config is loaded * asynchronously, this may not be populated at runtime. Only use it if you're sure * that `preInit` has already been called and finished. */       get wikiConfig { return this.deputy ? this.deputy.wikiConfig : this._wikiConfig; }       /**         * Get the module key for this module. Allows modules to be identified with a different * configuration key. *        * @return The module key. the module name by default. */       getModuleKey { return this.getName; }       /**         * Load the language pack for this module, with a fallback in case one could not be         * loaded. *        * @param fallback The fallback to use if a language pack could not be loaded. */       loadLanguages(fallback) { return __awaiter(this, void 0, void 0, function* {                yield Promise.all([ DeputyLanguage.load(this.getName, fallback), DeputyLanguage.load('shared', deputySharedEnglish), DeputyLanguage.loadMomentLocale ]);           });        }        /**         * Pre-initialize the module. This is the opportunity of the module to load language * strings, append important UI elements, add portlets, etc.        * * @param languageFallback The fallback language pack to use if one could not be loaded. */       preInit(languageFallback) { var _a; return __awaiter(this, void 0, void 0, function* {                yield this.getWikiConfig;                if (((_a = this.wikiConfig[this.getModuleKey]) === null || _a === void 0 ? void 0 : _a.enabled.get) !== true) {                    // Stop loading here.                    warn(`Preinit for ${this.getName} cancelled; module is disabled.`);                    return false;                }                yield this.loadLanguages(languageFallback);                yield attachConfigurationDialogPortletLink;                yield this.wikiConfig.prepareEditBanners;                return true;            }); }       /**         * Gets the wiki-specific configuration for Deputy. *        * @return A promise resolving to the loaded configuration */       getWikiConfig { var _a; return __awaiter(this, void 0, void 0, function* {                if (this.deputy) {                    return this.deputy.getWikiConfig;                }                else {                    return (_a = this._wikiConfig) !== null && _a !== void 0 ? _a : (this._wikiConfig = yield WikiConfiguration.load);                }            }); }   }

var cteStyles = ".copied-template-editor .oo-ui-window-frame {width: 1000px !important;}.copied-template-editor .oo-ui-menuLayout > .oo-ui-menuLayout-menu {height: 20em;width: 20em;}.copied-template-editor .oo-ui-menuLayout > .oo-ui-menuLayout-content {left: 20em;}.cte-preview .copiednotice {margin-left: 0;margin-right: 0;}.cte-merge-panel {padding: 16px;z-index: 20;border: 1px solid lightgray;margin-bottom: 8px;}.copied-template-editor .oo-ui-bookletLayout-outlinePanel {bottom: 32px;}.cte-actionPanel {height: 32px;width: 100%;position: absolute;bottom: 0;z-index: 1;background-color: white;border-top: 1px solid #c8ccd1;}.cte-actionPanel > .oo-ui-buttonElement {display: inline-block;margin: 0 !important;}.cte-templateOptions {margin: 8px;display: flex;}.cte-templateOptions > * {flex: 1;}.cte-fieldset {border: 1px solid gray;background-color: #ddf7ff;padding: 16px;min-width: 200px;clear: both;}.cte-fieldset-date {float: left;margin-top: 10px !important;}.cte-fieldset-advswitch {float: right;}.cte-fieldset-advswitch .oo-ui-fieldLayout-field,.cte-fieldset-date .oo-ui-fieldLayout-field {display: inline-block !important;}.cte-fieldset-advswitch .oo-ui-fieldLayout-header {display: inline-block !important;margin-right: 16px;}.cte-fieldset-date .oo-ui-fieldLayout-field {min-width: 18em;}.cte-fieldset .mw-widget-dateInputWidget {max-width: unset;}.cte-page-row:not(:last-child),.cte-page-template:not(:last-child) {padding-bottom: 0 !important;}.cte-page-template + .cte-page-row {padding-top: 0 !important;}.copied-template-editor .oo-ui-fieldsetLayout.oo-ui-iconElement > .oo-ui-fieldsetLayout-header {position: relative;}.oo-ui-actionFieldLayout.oo-ui-fieldLayout-align-top .oo-ui-fieldLayout-header {padding-bottom: 6px !important;}.deputy.oo-ui-window {/** Place below default window manager */z-index: 199 !important;}";

/**    * Main class for CopiedTemplateEditor. */   class CopiedTemplateEditor extends DeputyModule { constructor { super(...arguments); this.static = CopiedTemplateEditor; this.CopiedTemplate = CopiedTemplate; /**            * Whether the core has been loaded or not. Set to `true` here, since this is            * obviously the core class. */           this.loaded = true; /**            * Pencil icon buttons on  templates that open CTE. */           this.startButtons = []; }       /**         * @inheritDoc */       getName { return 'ante'; }       /**         * Perform actions that run *before* CTE starts (prior to execution). This involves * adding in necessary UI elements that serve as an entry point to CTE. */       preInit { const _super = Object.create(null, {               preInit: { get:  => super.preInit }            }); return __awaiter(this, void 0, void 0, function* {                if (!(yield _super.preInit.call(this, deputyAnteEnglish))) {                    return false;                }                if ( // Button not yet appended document.getElementById('pt-cte') == null && // Not virtual namespace mw.config.get('wgNamespaceNumber') >= 0) {                   mw.util.addPortletLink('p-tb', '#', // Messages used here: // * deputy.ante // * deputy.ante.short // * deputy.ante.acronym mw.msg({                       full: 'deputy.ante',                        short: 'deputy.ante.short',                        acronym: 'deputy.ante.acronym'                    }[this.config.core.portletNames.get]), 'pt-cte').addEventListener('click', (event) => { event.preventDefault; if (!event.currentTarget                           .hasAttribute('disabled')) { this.toggleButtons(false); this.openEditDialog; }                   });                }                mw.loader.using(['oojs-ui-core', 'oojs-ui.styles.icons-editing-core'],  => { // Only run if this script wasn't loaded using the loader. if (!window.CopiedTemplateEditor || !window.CopiedTemplateEditor.loader) { mw.hook('wikipage.content').add( => {                           // Find all  templates and append our special button.                            // This runs on the actual document, not the Parsoid document.                            document.querySelectorAll([ 'copiednotice', 'box-split-article', 'box-merged-from', 'box-merged-to', 'box-backwards-copy', 'box-translated-page' ].map((v) => `.${v} > tbody > tr`).join(', '))                               .forEach((e) => { if (e.classList.contains('cte-upgraded')) { return; }                               e.classList.add('cte-upgraded'); const startButton = new OO.ui.ButtonWidget({                                   icon: 'edit',                                    title: mw.msg('deputy.ante.edit'),                                    label: mw.msg('deputy.ante.edit')                                }).setInvisibleLabel(true); this.startButtons.push(startButton); const td = document.createElement('td'); td.style.paddingRight = '0.9em'; td.appendChild(startButton.$element[0]); e.appendChild(td); startButton.on('click', => {                                    this.toggleButtons(false);                                    this.openEditDialog;                                }); });                       });                    }                });                this.startState = true;                // Query parameter-based autostart                if (/[?&]cte-autostart(=(1|yes|true|on)?(&|$)|$)/.test(window.location.search)) {                    this.toggleButtons(false);                    this.openEditDialog;                }                return true;            }); }       /**         * Opens the Copied Template Editor dialog. */       openEditDialog { mw.loader.using(CopiedTemplateEditor.dependencies, => __awaiter(this, void 0, void 0, function*  { yield DeputyLanguage.loadMomentLocale; OO.ui.WindowManager.static.sizes.huge = { width: 1100 };               mw.util.addCSS(cteStyles); yield WikiAttributionNotices.init; if (!this.dialog) { // The following classes are used here: // * deputy // * copied-template-editor this.dialog = CopiedTemplateEditorDialog({                       main: this,                        classes: [                            // Attach "deputy" class if Deputy.                            this.deputy ? 'deputy' : null,                            'copied-template-editor'                        ].filter((v) => !!v)                    }); this.windowManager.addWindows([this.dialog]); }               yield this.windowManager.openWindow(this.dialog).opened; }));       }        /**         * Toggle the edit buttons. *        * @param state The new state. */       toggleButtons(state) { var _a; this.startState = state !== null && state !== void 0 ? state : !(this.startState || false); for (const button of this.startButtons) { button.setDisabled(state == null ? !button.isDisabled : !state); }           (_a = document.getElementById('.pt-cte a')) === null || _a === void 0 ? void 0 : _a.toggleAttribute('disabled', state); }   }    CopiedTemplateEditor.dependencies = [ 'moment', 'oojs-ui-core', 'oojs-ui-windows', 'oojs-ui-widgets', 'oojs-ui.styles.icons-accessibility', 'oojs-ui.styles.icons-editing-core', 'oojs-ui.styles.icons-editing-advanced', 'oojs-ui.styles.icons-interactions', 'ext.visualEditor.moduleIcons', 'mediawiki.util', 'mediawiki.api', 'mediawiki.Title', 'mediawiki.widgets', 'mediawiki.widgets.DateInputWidget', 'jquery.makeCollapsible' ];

var deputyStyles = "/*===============================================================================    GLOBAL DEPUTY CLASSES===============================================================================*/* > .deputy.dp-heading {position: absolute;opacity: 0;pointer-events: none;}*:hover > .deputy.dp-heading:not(.dp-heading--active) {opacity: 1;pointer-events: all;}.dp-loadingDots-1, .dp-loadingDots-2, .dp-loadingDots-3 {display: inline-block;margin: 0.1em 0.6em 0.1em 0.1em;width: 0.8em;height: 0.8em;background-color: rgba(0, 0, 0, 50%);animation: dp-loadingDots linear 3s infinite;border-radius: 50%;}@keyframes dp-loadingDots {0% {background-color: rgba(0, 0, 0, 10%);}16% {background-color: rgba(0, 0, 0, 40%);}32% {background-color: rgba(0, 0, 0, 10%);}100% {background-color: rgba(0, 0, 0, 10%);}}.dp-loadingDots-1 {animation-delay: -1s;}.dp-loadingDots-2 {animation-delay: -0.5s;}#mw-content-text.dp-reloading {opacity: 0.2;pointer-events: none;}p.dp-messageWidget-message {margin: 0 0 0.5em 0;}.dp-messageWidget-actions .oo-ui-buttonElement {margin-top: 0;}.oo-ui-image-destructive.oo-ui-icon-checkAll, .oo-ui-image-destructive.mw-ui-icon-checkAll::before {background-image: url(\"data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2220%22 height=%2220%22 viewBox=%220 0 20 20%22%3E%3Ctitle%3E check all %3C/title%3E%3Cpath fill=%22%23d73333%22 d=%22m.29 12.71 1.42-1.42 2.22 2.22 8.3-10.14 1.54 1.26-9.7 11.86zM12 10h5v2h-5zm-3 4h5v2H9zm6-8h5v2h-5z%22/%3E%3C/svg%3E\");}/*===============================================================================     DEPUTY REVIEW DIALOG (DeputyReviewDialog)===============================================================================*/.dp-review-progress {flex: 1;width: 60%;min-width: 300px;}/*===============================================================================     DEPUTY ENTRY POINTS (DeputyCCISessionStartLink, etc.)===============================================================================*/.deputy.dp-sessionStarter {font-size: small;font-weight: normal;margin-left: 0.25em;vertical-align: baseline;line-height: 1em;font-family: sans-serif;}.deputy.dp-sessionStarter::before {content: '\\200B';}.mw-content-ltr .deputy.dp-sessionStarter .dp-sessionStarter-bracket:first-of-type,.mw-content-rtl .deputy.dp-sessionStarter .dp-sessionStarter-bracket:not(:first-of-type) {margin-right: 0.25em;color: #54595d;}.client-js .deputy.dp-sessionStarter .dp-sessionStarter-bracket:first-of-type,.client-js .deputy.dp-sessionStarter .dp-sessionStarter-bracket:not(:first-of-type) {margin-left: 0.25em;color: #54595d}.dp-cs-section-add {position: absolute;top: 0;/* -1.6em derived from MediaWiki list margins. */left: -1.6em;width: calc(100% + 1.6em);background-color: rgba(255, 255, 255, 75%);display: flex;justify-content: center;align-items: center;}.dp-cs-section-add .dp-cs-section-addButton {opacity: 0;transition: opacity 0.2s ease-in-out;}.dp-cs-section-add:hover .dp-cs-section-addButton {opacity: 1;}/*===============================================================================    DEPUTY CONTRIBUTION SURVEY SECTION===============================================================================*/.dp-cs-section-archived .dp-cs-row-content {background-color: rgba(255, 0, 0, 6%);}.dp-cs-session-notice {margin-top: 8px;position: sticky;top: 8px;z-index: 50;}.skin-vector-2022.vector-sticky-header-visible .dp-cs-session-notice {top: calc(3.125rem + 8px);}.dp-cs-section-footer {position: relative;padding: 8px;}.dp-cs-section-danger--separator {flex-basis: 100%;margin: 8px 0;border-bottom: 1px solid #d73333;color: #d73333;font-weight: bold;font-size: 0.7em;text-align: right;text-transform: uppercase;line-height: 0.7em;padding-bottom: 0.2em;}.dp-cs-section-closing {margin: 1em 1.75em;}.dp-cs-section-progress {margin-top: 8px;max-height: 0;transition: max-height 0.2s ease-in-out;display: flex;justify-content: center;align-items: center;overflow: hidden;}.dp-cs-section-progress.active {max-height: 50px;}.dp-cs-section-progress .oo-ui-progressBarWidget {flex: 1}.dp-cs-section-closingCommentsField {margin-top: 8px;}.dp-cs-extraneous {border: 1px solid rgba(0, 159, 255, 40%);background-color: rgba(0, 159, 255, 10%);margin-bottom: 8px;padding: 16px;}.dp-cs-extraneous > dl {margin-left: -1.6em;}.dp-cs-extraneous > :first-child {margin-top: 0 !important;}.dp-cs-extraneous > :last-child {margin-bottom: 0 !important;}.dp-cs-section-archived-warn, .dp-cs-row, .dp-cs-extraneous {margin-bottom: 8px;}.dp-cs-row .dp--loadingDots {display: flex;align-items: center;justify-content: center;padding: 0.4em;}.dp-cs-row-status {max-width: 5.4em;}.dp-cs-row-status .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {width: 0;opacity: 0;}.dp-cs-row-status .dp-cs-row-status--unknown:not(.oo-ui-optionWidget-selected) {display: none;}.dp-cs-row-head > * {vertical-align: middle;}.dp-cs-row-comments {padding: 16px;background-color: rgba(0, 159, 255, 10%);margin: 4px 0;}.dp-cs-row-comments > b {letter-spacing: 0.1em;font-weight: bold;text-transform: uppercase;color: rgba(0, 0, 0, 0.5);}.dp-cs-row-comments hr {border-color: rgb(0, 31, 51);}body.mediawiki.ltr .dp-cs-row-head > :not(:first-child):not(:last-child),body.mediawiki.ltr .dp-cs-row-head > :not(:first-child):not(:last-child) {margin-right: 16px;}body.mediawiki.rtl .dp-cs-row-head > :not(:first-child):not(:last-child),body.mediawiki.rtl .dp-cs-row-head > :not(:first-child):not(:last-child) {margin-left: 16px;}.dp-cs-row-links {margin-right: 0 !important;}.dp-cs-row-links > :not(:last-child) {margin-right: 8px !important;}.dp-cs-row-title {font-weight: bold;font-size: 1.2em;vertical-align: middle;}.dp-cs-row-details {color: #4a5054;font-weight: bold;}.dp-cs-row-toggle .oo-ui-iconElement-icon {background-size: 1em;}.dp-cs-row-toggle .oo-ui-buttonElement-button {border-radius: 50%;}.dp-cs-row .history-user,.dp-cs-row :not(.newpage) + .mw-changeslist-date {margin-left: 0.4em;margin-right: 0.2em;}.dp-cs-row .newpage {margin-left: 0.4em;}.dp-cs-row-content {padding: 16px;background-color: rgba(0, 0, 0, 6%);margin: 4px 0;}.dp-cs-row-content.dp-cs-row-content-empty {display: none !important;}.dp-cs-row-unfinishedWarning {margin-bottom: 8px;}.dp-cs-section-unfinishedWarning {margin-top: 8px;}.dp-cs-row-closeComments {font-family: monospace, monospace;font-size: small;}.dp-cs-row-closeComments:not(:last-child) {margin-bottom: 8px;}.dp-cs-row-finished .oo-ui-fieldLayout:first-child {margin-top: 0;}.dp-cs-row-finished .oo-ui-fieldLayout {margin-top: 8px;}.dp-cs-row-revisions .mw-tag-markers .mw-tag-marker:not(:first-child),.dp-cs-row-detail:not(:first-child) {margin-left: 0.2em;}.dp-cs-rev-checkbox {margin-right: 4px;}.dp-cs-rev-toggleDiff {vertical-align: baseline;margin-right: 4px;}.dp-cs-rev-diff {background-color: white;position: relative;}.dp-cs-rev-diff--loaded {margin: 4px 0;padding: 8px 14px;}.dp-cs-rev-diff--hidden {display: none;}.dp-cs-rev-toggleDiff > .oo-ui-buttonElement-button {padding: 0;min-height: 1em;background-color: unset !important;}.dp-cs-rev-toggleDiff .oo-ui-indicatorElement-indicator {top: -1px;}/*===============================================================================     DEPUTY PAGE TOOLBAR===============================================================================*/.dp-pageToolbar {position: fixed;bottom: 8px;left: 8px;z-index: 100;padding: 8px;background-color: #fff;border: 1px solid gray;font-size: 0.9rem;display: flex;align-items: center;}@media only screen and (max-width: 768px) {.dp-pageToolbar {flex-wrap: wrap;bottom: 0;left: 0;border-left: 0;border-bottom: 0;border-right: 0;width: 100%;}}.dp-pt-section {display: inline-block;white-space: nowrap;}.dp-pt-section .oo-ui-popupWidget-popup {/** Avoid preventing line breaks in popups */white-space: initial;}.dp-pt-section + .dp-pt-section {/* TODO: Recheck RTL compatibility */margin-left: 16px;padding-left: 16px;border-left: 1px solid gray;}.dp-pt-section:last-child {/* TODO: Recheck RTL compatibility */margin-right: 8px;}.dp-pt-section-label {font-weight: bold;font-size: 0.6rem;color: #4a5054;text-transform: uppercase;}.dp-pt-section-content .oo-ui-buttonElement:last-child {margin-right: 0;}.dp-pt-caseInfo {font-weight: bold;font-size: 1.3rem;pointer-events: none;}.dp-pt-missingRevision {white-space: normal;}.dp-pageToolbar .dp-cs-row-status {width: 5.4em;}.dp-pt-menu .oo-ui-menuSelectWidget {min-width: 300px;}.dp-pt-menu .oo-ui-menuOptionWidget {padding-top: 8px;padding-bottom: 8px;}";

var deputyCoreEnglish = { "deputy.content.summary": "/* $1 */ -$2) (", "deputy.content.summary.partial": "/* $1 */ - partial) (", "deputy.content.summary.sectionClosed": "/* $1 */ -$2, section done) (", "deputy.content.assessed": "Assessed $1 NaN revisionss across $2 pages", "deputy.content.assessed.comma": ", ", "deputy.content.assessed.finished": "$1 finished", "deputy.content.assessed.reworked": "$1 reworked", "deputy.content.assessed.sectionClosed": "section closed", "deputy.content.reformat": "Reformatting section", "deputy.session.start": "start CCI session", "deputy.session.continue": "continue CCI session", "deputy.session.continue.button": "Continue session", "deputy.session.continue.head": "You last worked on this page on $1.", "deputy.session.continue.help": "Continue working on \"$1\" and pick up where you left off.", "deputy.session.continue.help.fromStart": "The section \"$1\" might have been archived already. Not to worry, you can being working on \"$2\".", "deputy.session.tabActive.head": "You are working on this case page from another tab.", "deputy.session.tabActive.help": "Deputy can only run on one case page and tab at a time. Navigate to the other tab to continue working.", "deputy.session.otherActive.head": "Deputy is currently working on a different case page.", "deputy.session.otherActive.help": "Deputy can only run on one case page and tab at a time. You may have also forgotten to close a prior Deputy session. You can force-stop the earlier session, but progress or data on the previously-open case may be lost.", "deputy.session.otherActive.button": "Stop session", "deputy.session.add": "Start working on this section", "deputy.session.section.close": "Archive section", "deputy.session.section.closeComments": "Archiving comments", "deputy.session.section.closeCommentsSign": "Include my signature", "deputy.session.section.closeError": "Some revisions remain unassessed. You must mark these revisions as assessed before archiving this section.", "deputy.session.section.closeError.danger": "Some revisions remain unassessed, but Deputy will allow archiving while danger mode is enabled.", "deputy.session.section.closeWarn": "You have unsaved changes. Close the section without saving?", "deputy.session.section.closed": "This section has been archived. You can edit its contents, but you cannot un-archive it.", "deputy.session.section.stop": "Stop session", "deputy.session.section.stop.title": "Stop then current session, closing all sections and saving changes for later.", "deputy.session.section.saved": "Section saved", "deputy.session.section.failed": "Failed to save section", "deputy.session.section.missingSection": "The target section is missing from the case page.", "deputy.session.section.sectionIncomplete": "The target section still has unreviewed rows.", "deputy.session.section.conflict.title": "Edit conflict", "deputy.session.section.conflict.help": "Someone else edited the page before you. Deputy will restart to load the new case content. Your changes will be preserved.", "deputy.session.section.danger": "Danger mode", "deputy.session.section.markAllFinished": "Mark all revisions in all sections as finished", "deputy.session.section.instantArchive": "Archive", "deputy.session.section.instantArchive.title": "Archive and save this section immediately. Revisions will not be marked as finished.", "deputy.session.row.status": "Current page status", "deputy.session.row.status.unfinished": "Unfinished", "deputy.session.row.status.unknown": "Unknown", "deputy.session.row.status.withViolations": "Violations found", "deputy.session.row.status.withoutViolations": "No violations found", "deputy.session.row.status.missing": "Missing", "deputy.session.row.status.presumptiveRemoval": "Presumptively removed", "deputy.session.row.details.new": "created", "deputy.session.row.details.edits": "NaN NaN editss", "deputy.session.row.content.open": "Expand additional details", "deputy.session.row.content.close": "Collapse additional details", "deputy.session.row.unfinishedWarning": "A assessment was given but not all revisions of the page have been assessed. The final assessment will not be saved on the case page until all revisions are marked as assessed, but will be saved locally (on your browser) for later.", "deputy.session.row.talk": "Open the talk page", "deputy.session.row.edit": "Edit this page", "deputy.session.row.history": "Open page history", "deputy.session.row.checkAll": "Mark all revisions as finished", "deputy.session.row.checkAll.confirm": "Mark all revisions as finished?", "deputy.session.row.additionalComments": "Discussion", "deputy.session.row.closeComments": "Closing comments", "deputy.session.row.close.sigFound": "The closing comment had a signature. It will not be automatically removed when saved.", "deputy.session.row.close.sigFound.maybe": "The closing comment might have had a signature. It will not be automatically removed when saved.", "deputy.session.row.error": "An error occurred while trying to get revision information: $1", "deputy.session.row.checked": "Checked by $1", "deputy.session.row.checkedComplete": "Checked by $1 on $2 ($3 ago)", "deputy.session.row.checked.talk": "talk", "deputy.session.row.checked.contribs": "contribs", "deputy.session.row.pageonly": "This row does not contain any diffs. Please assess the page history manually.", "deputy.session.revision.assessed": "Mark as assessed", "deputy.session.revision.diff.toggle": "Toggle comparison (diff) view", "deputy.session.revision.diff.error": "Failed to load comparison: $1", "deputy.session.revision.cur": "cur", "deputy.session.revision.prev": "prev", "deputy.session.revision.cv": "cv", "deputy.session.revision.cur.tooltip": "Difference with latest revision", "deputy.session.revision.prev.tooltip": "Difference with preceding revision", "deputy.session.revision.cv.tooltip": "Run through Earwig's Copyvio Detector", "deputy.session.revision.talk": "talk", "deputy.session.revision.contribs": "contribs", "deputy.session.revision.bytes": "NaN bytes", "deputy.session.revision.byteChange": "NaN bytes after change of this size", "deputy.session.revision.tags": "NaN Tagss:", "deputy.session.revision.new": "N", "deputy.session.revision.new.tooltip": "This edit created a new page.", "deputy.session.revision.missing": "The revision $1 could not be found. It may have been deleted or suppressed.", "deputy.session.page.diff.previous": "Navigate to the previous unassessed revision", "deputy.session.page.diff.next": "Navigate to the next unassessed revision", "deputy.session.page.diff.loadFail": "Failed to load diff. Please check your internet connection and try again.", "deputy.session.page.incommunicable": "Cannot find an active Deputy session. Please ensure that Deputy is running on a contributor investigation case page.", "deputy.session.page.caseInfo.label": "Current case", "deputy.session.page.caseInfo.revision": "Revision #$1", "deputy.session.page.caseInfo.revision.none": "Revision out of scope", "deputy.session.page.caseInfo.revision.help": "The requested revision was out of scope for the current Deputy case page. It was likely made by a user who is not a subject of the case page, or has already been removed or assessed by another user. Tools will still be available, but you will not be able to mark this revision as \"assessed\".", "deputy.session.page.caseInfo.assessed": "Assessed?", "deputy.session.page.caseInfo.next": "Navigate to the next unassessed revision", "deputy.session.page.pageonly.title": "No revisions", "deputy.session.page.pageonly.help": "This row does not contain any revisions with it. Please assess the page history manually before making an assessment.", "deputy.session.page.analysis": "Analysis", "deputy.session.page.earwigLatest": "Earwig's Copyvio Detector (latest)", "deputy.session.page.earwigRevision": "Earwig's Copyvio Detector (revision)", "deputy.session.page.earwigUnsupported": "Earwig's Copyvio Detector does not support this wiki.", "deputy.session.page.iabot": "Add archives to a page (IABot)", "deputy.session.page.iabot.reason": "Possible copyright violation investigation", "deputy.session.page.tools": "Tools" };

var deputyIaEnglish = { "deputy.ia.content.respond": "Responding to $2", "deputy.ia.content.close": "-1) (Responding to $2", "deputy.ia.content.listing": "Adding listing for $2", "deputy.ia.content.batchListing": "Adding batch listing: \"$2\"", "deputy.ia.content.listingComment": "from $1. $2", "deputy.ia.content.hideAll": "Hiding page content due to a suspected or complicated copyright issue", "deputy.ia.content.hide": "Hiding sections $3 to $5 for suspected or complicated copyright issues", "deputy.ia.content.listing.pd": "Adding listing for $2 (presumptive deletion)", "deputy.ia.content.batchListing.pd": "Adding batch listing: \"$2\" (presumptive deletion)", "deputy.ia.content.listingComment.pd": "presumptive deletion from $2. $3", "deputy.ia.content.batchListingComment.pd": "Presumptive deletion from $2. $3", "deputy.ia.content.hideAll.pd": "Hiding page content for presumptive deletion; see $1/$2", "deputy.ia.content.hide.pd": "Hiding sections $3 to $5 for presumptive deletion; see $6/$7", "deputy.ia.content.copyvio": "⛔ Content on this page has been temporarily hidden due to a suspected copyright violation", "deputy.ia.content.copyvio.help": "Please see this wiki's noticeboard for copyright problems for more information.", "deputy.ia.content.copyvio.from": "The following reason/source was provided:", "deputy.ia.content.copyvio.from.pd": "The content was presumptively removed based on the following contributor copyright investigation:", "deputy.ia.content.copyvio.content": "The following content may be a copyright violation. Please do not unhide it unless you have determined that it is compatible with this wiki's copyright license.", "deputy.ia.listing.new": "New listing", "deputy.ia.listing.new.batch": "New batch listing", "deputy.ia.listing.new.report": "Report", "deputy.ia.listing.new.page.label": "Page to report", "deputy.ia.listing.new.pages.label": "Pages to report", "deputy.ia.listing.new.source.label": "Source of copied content", "deputy.ia.listing.new.source.placeholder": "This page contains copyrighted content from ...", "deputy.ia.listing.new.additionalNotes.label": "Additional notes", "deputy.ia.listing.new.additionalNotes.placeholder": "Additional comments, context, or requests", "deputy.ia.listing.new.title.label": "Batch title", "deputy.ia.listing.new.title.placeholder": "Articles from ...", "deputy.ia.listing.new.presumptive.label": "This is for presumptive deletion", "deputy.ia.listing.new.presumptive.help": "Presumptive deletions are content removals where the actual source of copied content cannot be determined, but due to the history of the user, it is most likely a copyright violation. Enabling this will change related edit summaries and listing text.", "deputy.ia.listing.new.presumptiveCase.label": "Case title", "deputy.ia.listing.new.presumptiveCase.help": "The title of the case on a list of contributor copyright investigations. This is used to link to the case from the listing.", "deputy.ia.listing.new.comments.label": "Batch listing comments", "deputy.ia.listing.new.comments.placeholder": "Comments for each article", "deputy.ia.listing.new.preview": "Preview", "deputy.ia.listing.new.batchListed": "Batch listing posted", "deputy.ia.listing.new.batchEr": "An error occurred while posting the batch listing: $1", "deputy.ia.listing.respondPre": "[", "deputy.ia.listing.respond": "respond", "deputy.ia.listing.respondPost": "]", "deputy.ia.listing.re.label": "Select a response...", "deputy.ia.listing.re.title": "Prefilled listing comment", "deputy.ia.listing.re.extras": "Additional comments", "deputy.ia.listing.re.preview": "Preview", "deputy.ia.listing.re.close": "Cancel", "deputy.ia.listing.re.submit": "Respond", "deputy.ia.listing.re.error": "An error occurred while attempting to respond to a listing: $1", "deputy.ia.listing.re.published": "Your response has been published.", "deputy.ia.listing.re.cleaned": "Article cleaned by investigator or others. No remaining infringement.", "deputy.ia.listing.re.deletedcv": "Article deleted due to copyright concerns.", "deputy.ia.listing.re.user": "User was not notified, relisting under today's entry.", "deputy.ia.listing.re.where": "No vio found, claim cannot be validated. Tag removed from article.", "deputy.ia.listing.re.unsure": "No source found; copy-paste tag removed and cv-unsure tag placed at article talk.", "deputy.ia.listing.re.deletedcup": "Copyright concerns remain. Article deleted, left notice.", "deputy.ia.listing.re.relist": "Permission plausible. Article relisted under today.", "deputy.ia.listing.re.resolved": "Issue resolved.", "deputy.ia.listing.re.redirect": "Article redirected to a non-infringing target.", "deputy.ia.listing.re.deletedother": "Article deleted for a reason other than copyright concerns.", "deputy.ia.listing.re.move": "Rewrite moved into place.", "deputy.ia.listing.re.backwardsattributed": "Backwardscopy. Attributes Wikipedia.", "deputy.ia.listing.re.blanked": "Blanked and relisted under today.", "deputy.ia.listing.re.deferred": "Deferred to old issues.", "deputy.ia.listing.re.ticket": "VRT Ticket received, article now licensed and compatible with CC BY-SA 3.0.", "deputy.ia.listing.re.backwards": "Backwardscopy. Tag placed at talk page.", "deputy.ia.listing.re.no": "No copyright concern. Material is PD, license compatible, or ineligible for copyright protection.", "deputy.ia.listing.re.histpurge": "Article cleaned, revision deletion requested.", "deputy.ia.listing.re.OTRS": "VRT pending but not yet verified, relisting under today's entry.", "deputy.ia.listing.re.purged": "Revision deletion completed. Copyright problem removed from history.", "deputy.ia.listing.re.unverified": "Permission unverified as of this tagging; article will need to be deleted if that does not change.", "deputy.ia.listing.re.viable": "Viable rewrite proposed; rewrite on temp page can be merged into the article.", "deputy.ia.report.intro": "You are reporting to $1", "deputy.ia.report.page": "Currently reporting $1", "deputy.ia.report.lead": "Lead section", "deputy.ia.report.end": "End of page", "deputy.ia.report.section": "$1: $2", "deputy.ia.report.transcludedSection": "This section is transcluded from another page, \"$1\".", "deputy.ia.report.entirePage.label": "Hide the entire page", "deputy.ia.report.startSection.placeholder": "Select starting section to hide", "deputy.ia.report.startSection.label": "Starting section", "deputy.ia.report.endSection.placeholder": "Select ending section to hide", "deputy.ia.report.endSection.label": "Ending section", "deputy.ia.report.endSection.help": "This setting is inclusive, meaning it will also hide the section indicated.", "deputy.ia.report.presumptive.label": "This is for presumptive deletion", "deputy.ia.report.presumptive.help": "Presumptive deletions are content removals where the actual source of copied content cannot be determined, but due to the history of the user, it is most likely a copyright violation. Enabling this will change related edit summaries and listing text.", "deputy.ia.report.presumptiveCase.label": "Case title", "deputy.ia.report.presumptiveCase.help": "The title of the case on a list of contributor copyright investigations. This is used to link to the case from the listing.", "deputy.ia.report.fromUrls.label": "Content copied from online sources", "deputy.ia.report.fromUrls.help": "URLs will automatically be wrapped with brackets to shorten the external link. Disabling this option will present the text as is.", "deputy.ia.report.source.label": "Source of copied content", "deputy.ia.report.sourceUrls.placeholder": "Add URLs", "deputy.ia.report.sourceText.placeholder": "This page contains copyrighted content from ...", "deputy.ia.report.additionalNotes.label": "Additional notes", "deputy.ia.report.additionalNotes.placeholder": "Additional comments, context, or requests", "deputy.ia.report.submit": "Submit", "deputy.ia.report.hide": "Hide content only", "deputy.ia.report.hide.confirm": "This will insert the template and hide page content as set, but will not post a listing for this page on the noticeboard. Are you sure you don't want to list this page on the noticeboard?", "deputy.ia.report.success": "Page content hidden and reported", "deputy.ia.report.success.hide": "Page content hidden", "deputy.ia.report.success.report": "Page reported", "deputy.ia.report.error.report": "An error occurred while trying to save the entry to today's noticeboard listings. Please visit the noticeboard page and select \"Add listing\" or file the listing manually.", "deputy.ia.report.error.shadow": "An error occurred while trying to append the template on the page. Please manually insert the template.", "deputy.ia.hiddenVio": "A user has marked content on this page as a suspected copyright violation. It is currently hidden from normal viewers of this page while awaiting further action.", "deputy.ia.hiddenVio.show": "Show hidden content", "deputy.ia.hiddenVio.hide": "Hide hidden content" };

/**    * A class that represents a `Wikipedia:Copyright problems` page, a page that lists * a collection of accumulated copyright problems found on Wikipedia. Users who are * not well-versed in copyright can submit listings there to be reviewed by more- * knowledgeable editors. *    * This page can refer to any Copyright problems page, and not necessarily one that * is running on the current tab. For that, CopyrightProblemsSession is used. */   class CopyrightProblemsPage { /**        * Private constructor. Use `get` instead to avoid cache misses. *        * @param listingPage * @param revid */       constructor(listingPage, revid) { this.title = listingPage; this.main = CopyrightProblemsPage.rootPage.getPrefixedText === listingPage.getPrefixedText; this.revid = revid; }       /**         * @return See {@link WikiConfiguration#ia}.rootPage. */       static get rootPage { return window.InfringementAssistant.wikiConfig.ia.rootPage.get; }       /**         * @return The title of the current copyright problems subpage. */       static getCurrentListingPage { return normalizeTitle(CopyrightProblemsPage.rootPage.getPrefixedText + '/' +               window.moment.utc.format(window.InfringementAssistant.wikiConfig.ia.subpageFormat.get)); }       /**         * @param title The title to check * @return `true` if the given page is a valid listing page. */       static isListingPage(title = mw.config.get('wgPageName')) { return normalizeTitle(title) .getPrefixedText .startsWith(CopyrightProblemsPage.rootPage.getPrefixedText); }       /**         * Gets the current CopyrightProblemsPage (on Copyright Problems listing pages) *        * @return A CopyrightProblemsPage for the current page. */       static getCurrent { const listingPage = this.getCurrentListingPage; return new CopyrightProblemsPage(listingPage); }       /**         * Gets a listing page from the cache, if available. If a cached page is not available, * it will be created for you. *        * @param listingPage * @param revid * @return The page requested */       static get(listingPage, revid) { const key = listingPage.getPrefixedDb + '##' + (revid !== null && revid !== void 0 ? revid : 0); if (CopyrightProblemsPage.pageCache.has(key)) { return CopyrightProblemsPage.pageCache.get(key); }           else { const page = new CopyrightProblemsPage(listingPage, revid); CopyrightProblemsPage.pageCache.set(key, page); return page; }       }        /**         * @param force * @return the current wikitext of the page */       getWikitext(force = false) { return __awaiter(this, void 0, void 0, function* {                if (this.wikitext && !force) {                    return this.wikitext;                }                const content = yield getPageContent(this.title);                if (content == null) {                    return null;                }                this.revid = content.revid;                this.wikitext = content;                return content;            }); }       /**         * Handles appends to new listings. Also handles cases where the listing * page is missing. If the listing is today's listing page, but the page is missing, * the page will automatically be created with the proper header. If the listing is        * NOT today's page and is missing, this will throw an error. *        * If the page was not edited since the page was missing, and the page was created * in the time it took for us to find out that the page was missing (i.e., race        * condition), it will attempt to proceed with the original appending. If the edit * still fails, an error is thrown. *        * @param content The content to append * @param summary The edit summary to use when appending * @param appendMode */       tryListingAppend(content, summary, appendMode = true) { return __awaiter(this, void 0, void 0, function* {                const listingPage = this.main ? CopyrightProblemsPage.getCurrentListingPage : this.title;                if ( // Current listing page is automatically used for this.main, so this can be               // an exception. !this.main && // If the listing page is today's listing page. CopyrightProblemsPage.getCurrentListingPage.getPrefixedText !== listingPage.getPrefixedText && // Not on append mode (will create page) !appendMode) {                   // It's impossible to guess the header for the page at this given moment in time,                    // so simply throw an error. In any case, this likely isn't the right place to                    // post the listing in the first place.                    throw new Error('Attempted to post listing on non-current page');                }                const config = yield window.InfringementAssistant.getWikiConfig;                const preloadText = config.ia.preload.get ? `\n` : '';                const textParameters = appendMode ? {                    appendtext: '\n' + content,                    nocreate: true                } : {                    text: preloadText + content,                    createonly: true                };                // The `catch` statement here can theoretically create an infinite loop given // enough race conditions. Don't worry about it too much, though. yield MwApi.action.postWithEditToken(Object.assign(Object.assign(Object.assign(Object.assign({}, changeTag(yield window.InfringementAssistant.getWikiConfig)), { action: 'edit', title: listingPage.getPrefixedText }), textParameters), { summary })).then( => {                   // Purge the main listing page.                    return MwApi.action.post({ action: 'purge', titles: CopyrightProblemsPage.rootPage.getPrefixedText });               }).catch((code) => {                    if (code === 'articleexists') {                        // Article exists on non-append mode. Attempt a normal append.                        this.tryListingAppend(content, summary, true);                    }                    else if (code === 'missingtitle') {                        // Article doesn't exist on append mode. Attempt a page creation.                        this.tryListingAppend(content, summary, false);                    }                    else {                        // wat.                        throw code;                    }                }); yield this.getWikitext(true); });       }        /**         * Posts a single page listing to this page, or (if on the root page), the page for         * the current date. Listings are posted in the following format:         * ```         * * Example ~         * ```         *         * For posting multiple pages, use `postListings`.         *         * @param page         * @param comments         * @param presumptive         */        postListing(page, comments, presumptive) {            return __awaiter(this, void 0, void 0, function*  { const listingPage = this.main ? CopyrightProblemsPage.getCurrentListingPage : this.title; yield this.tryListingAppend(this.getListingWikitext(page, comments), decorateEditSummary(mw.msg(presumptive ?                   'deputy.ia.content.listing.pd' :                    'deputy.ia.content.listing', listingPage.getPrefixedText, page.getPrefixedText), window.InfringementAssistant.config)); });       }        /**         * Generates the listing wikitext using wiki configuration values.         *         * @param page         * @param comments         * @return Wikitext         */        getListingWikitext(page, comments) {            return mw.format(window.InfringementAssistant.wikiConfig.ia.listingWikitext.get, page.getPrefixedText, comments || '').replace(/(\s){2,}/g, '$1');        }        /**         * Posts multiple pages under a collective listing. Used for cases where the same         * comment can be applied to a set of pages. Listings are posted in the following         * format:         * ```         * ;          * * Page 1         * * Page 2         * ~         * ```         *         * @param page         * @param title         * @param comments         * @param presumptive         */        postListings(page, title, comments, presumptive) {            return __awaiter(this, void 0, void 0, function*  { const listingPage = this.main ? CopyrightProblemsPage.getCurrentListingPage : this.title; yield this.tryListingAppend(this.getBatchListingWikitext(page, title, comments), decorateEditSummary(mw.msg(presumptive ?                   'deputy.ia.content.batchListing.pd' :                    'deputy.ia.content.batchListing', listingPage.getPrefixedText, title), window.InfringementAssistant.config)); });       }        /**         * Generates the batch listing wikitext using wiki configuration values.         *         * @param page         * @param title         * @param comments         * @return Wikitext         */        getBatchListingWikitext(page, title, comments) {            const pages = page                .map((p) => mw.format(window.InfringementAssistant.wikiConfig.ia.batchListingPageWikitext.get, p.getPrefixedText))                .join();            return mw.format(window.InfringementAssistant.wikiConfig.ia.batchListingWikitext.get, title, pages, comments || ).replace(/^\s+~$/gm, '~');        }    }    CopyrightProblemsPage.pageCache = new Map;

/**    * Extracts a page title from a MediaWiki ``. If the link does not validly point * to a MediaWiki page, `false` is returned. *    * The part of the link used to determine the page title depends on how trustworthy * the data is in telling the correct title. If the link does not have an `href`, only * two routes are available: the selflink check and the `title` attribute check. *    * The following methods are used, in order. * - `title` parameter from anchor href * - `/wiki/$1` path from anchor href * - `./$1` path from Parsoid document anchor href * - selflinks (not run on Parsoid) * - `title` attribute from anchor *    * @param el     * @return the page linked to     */ function pagelinkToTitle(el) { const href = el.getAttribute('href'); const articlePathRegex = new RegExp(mw.util.getUrl('(.*)')); if (href && href.startsWith(mw.util.wikiScript('index'))) { // The link matches the script path (`/w/index.php`). // This is the branch used in cases where the page does not exist. The section is always // dropped from the link, so no section filtering needs to be done. // Attempt to extract page title from `title` parameter. const titleRegex = /[?&]title=(.*?)(?:&|$)/; if (titleRegex.test(href)) { return new mw.Title(titleRegex.exec(href)[1]); }           else { // Not a valid link. return false; }       }        if (href && articlePathRegex.test(href)) { // The link matches the article path (`/wiki/$1`) RegExp. return new mw.Title(decodeURIComponent(articlePathRegex.exec(href)[1])); }       if (el.getAttribute('rel') === 'mw:WikiLink') { // Checks for Parsoid documents. if (href) { const parsoidHrefMatch = articlePathRegex.exec(href.replace(/^\.\/([^#]+).*$/, mw.config.get('wgArticlePath'))); if (parsoidHrefMatch != null) { // The link matches the Parsoid link format (`./$1`). return new mw.Title(decodeURIComponent(href.slice(2))); }           }        }        else { // Checks for non-Parsoid documents if (el.classList.contains('mw-selflink')) { // Self link. Return current page name. return new mw.Title(el.ownerDocument.defaultView.mw.config.get('wgPageName')); }       }        // If we still can't find a title by this point, rely on the `title` attribute. // This is unstable, since the title may be set or modified by other userscripts, so it       // is only used as a last resort. if (el.hasAttribute('title') &&           // Not a redlink            !el.classList.contains('new') &&            // Not an external link            !el.classList.contains('external')) { return new mw.Title(el.getAttribute('title')); }       // Not a valid link. return false; }

/**    * Check if a given copyright problems listing is full. *    * @param data * @return `true` if the listing is a {@link FullCopyrightProblemsListingData} */   function isFullCopyrightProblemsListing(data) { return data.basic === false; }   /**     * Represents an existing copyright problems listing. To add or create new * listings, use the associated functions in {@link CopyrightProblemsPage}. */   class CopyrightProblemsListing { /**        * Creates a new listing object. *        * @param data Additional data about the page * @param listingPage The page that this listing is on. This is not necessarily the page that *                   the listing's wikitext is on, nor is it necessarily the root page. * @param i A discriminator used to avoid collisions when a page is listed multiple times. */       constructor(data, listingPage, i = 1) { this.listingPage = listingPage !== null && listingPage !== void 0 ? listingPage : CopyrightProblemsPage.get(data.listingPage); this.i = Math.max(1, i); // Ensures no value below 1. this.basic = data.basic; this.title = data.title; this.element = data.element; if (data.basic === false) { this.id = data.id; this.anchor = data.anchor; this.plainlinks = data.plainlinks; }       }        /**         * Responsible for determining listings on a page. This method allows for full-metadata * listing detection, and makes the process of detecting a given listing much more precise. *        * This regular expression must catch three groups: * - $1 - The initial `* `, used to keep the correct number of whitespace between parts. * - $2 - The page title in the `id="..."`, ONLY IF the page is listed with an        *        `article-cv`-like template. * - $3 - The page title in the wikilink, ONLY IF the page is listed with an        *        `article-cv`-like template. * - $4 - The page title, ONLY IF the page is a bare link to another page and does not use *       `article-cv`. *        * @return A regular expression. */       static get articleCvRegex { // Acceptable level of danger; global configuration is found only in trusted // places (see WikiConfiguration documentation). // eslint-disable-next-line security/detect-non-literal-regexp return new RegExp(window.InfringementAssistant.wikiConfig.ia.listingWikitextMatch.get); }       /**         * Gets the page title of the listing page. This is used in `getListing` and * `getBasicListing` to identify which page the listings are on. *        * This makes the assumption that all listings have a prior H4 header that * links to the proper listing page. If that assumption is not met, this * returns `null`. *        * @param el         * @return The page title, or `false` if none was found. * @private */       static getListingHeader(el) { var _a; let listingPage = null; let previousPivot = (           // Target the ol/ul element itself if a list, target the if not a list.            el.parentElement.tagName === 'LI' ? el.parentElement.parentElement : el.parentElement).previousElementSibling; let heading; // Search for a level 4 heading backwards. while (previousPivot != null &&               // Set the ceiling to be immediately above for efficiency.                ((_a = (heading = normalizeWikiHeading(previousPivot, previousPivot.parentElement))) === null || _a === void 0 ? void 0 : _a.level) !== 4) { previousPivot = previousPivot.previousElementSibling; }           if (previousPivot == null) { return false; }           // At this point, previousPivot is likely a MediaWiki level 4 heading. const h4Anchor = heading.h.querySelector('a'); if (h4Anchor) { listingPage = pagelinkToTitle(h4Anchor); // Identify if the page is a proper listing page (within the root page's               // pagespace) if (!listingPage ||                   !listingPage.getPrefixedText                        .startsWith(CopyrightProblemsPage.rootPage.getPrefixedText)) { return false; }           }            return listingPage !== null && listingPage !== void 0 ? listingPage : false; }       /**         * Determines if a given element is a valid anchor element (``) which * makes up a "listing" (a page for checking on the Copyright Problems page). *        * Detection is based on the  template. Changes to the template * must be reflected here, with backwards compatibility for older listings. * The is not the tracked element here, since it remains invisible * to the user. *        * @param el         * @return Data related to the listing, for use in instantiation; `false` if not a listing. */       static getListing(el) { try { if (el.tagName !== 'A' || el.getAttribute('href') === null) { // Not a valid anchor element. return false; }               // Check for  before the link. const anchor = el.previousElementSibling; if (anchor == null || anchor.tagName !== 'SPAN') { return false; }               // Get the page title based on the anchor, verified by the link. // This ensures we're always using the prefixedDb version of the title (as               // provided by the anchor) for stability. const id = anchor.getAttribute('id'); const title = pagelinkToTitle(el); if (title === false || id == null) { // Not a valid link. return false; }               else if (title.getPrefixedText !== new mw.Title(id).getPrefixedText) { // Anchor and link mismatch. Someone tampered with the template? // In this case, rely on the link instead, as the anchor is merely invisible. warn(`Anchor and link mismatch for "${title.getPrefixedText}".`, title, id); }               // Checks for the element. // This ensures that the listing came from and isn't just a                // link with an anchor. const elSiblings = Array.from(el.parentElement.children); const elIndex = elSiblings.indexOf(el); const plainlinks = el.parentElement.querySelector(`:nth-child(${elIndex}) ~ span.plainlinks`); if (plainlinks == null ||                   // `~` never gets an earlier element, so just check if it's more than 2 elements                    // away.                    elSiblings.indexOf(plainlinks) - elIndex > 2) { return false; }               // Attempts to look for a prior tag. Used for determining the listing, if on a               // root page. const listingPage = this.getListingHeader(el); if (!listingPage) { // Can't find a proper listing page for this. In some cases, this // should be fine, however we don't want the [respond] button to                   // appear if we don't know where a page is actually listed. return false; }               return { basic: false, id, title, listingPage, element: el, anchor: anchor, plainlinks: plainlinks };           }            catch (e) { warn("Couldn't parse listing. Might be malformed?", e, el); return false; }       }        /**         * A much more loose version of {@link CopyrightProblemsListing#getListing}, * which only checks if a given page is a link at the start of a paragraph or        * `<[uo]l>` list. Metadata is unavailable with this method. *        * @param el         * @return Data related to the listing, for use in instantiation; `false` if not a listing. */       static getBasicListing(el) { try { if (el.tagName !== 'A' || el.getAttribute('href') == null) { // Not a valid anchor element. return false; }               // Check if this is the first node in the container element. if (el.previousSibling != null) { return false; }               // Check if the container is a paragraph or a top-level ul/ol list item. if (el.parentElement.tagName !== 'P' &&                   (el.parentElement.tagName !== 'LI' && (el.parentElement.parentElement.tagName !== 'UL' &&                        el.parentElement.parentElement.tagName !== 'OL'))) { return false; }               // Attempt to extract page title. const title = pagelinkToTitle(el); if (!title) { return false; }               // Attempts to look for a prior tag. Used for determining the listing, if on a               // root page. const listingPage = this.getListingHeader(el); if (!listingPage) { // Can't find a proper listing page for this. In some cases, this // should be fine, however we don't want the [respond] button to                   // appear if we don't know where a page is actually listed. return false; }               return { basic: true, title, listingPage, element: el               }; }           catch (e) { warn("Couldn't parse listing. Might be malformed?", e, el); return false; }       }        /**         * @return an ID representation of this listing. Helps in finding it inside of        * wikitext. */       get anchorId { return this.id + (this.i > 1 ? `-${this.i}` : ''); }       /**         * Gets the line number of a listing based on the page's wikitext. * This is further used when attempting to insert comments to listings. *        * This provides an object with `start` and `end` keys. The `start` denotes * the line on which the listing appears, the `end` denotes the last line * where there is a comment on that specific listing. *        * Use in conjunction with `listingPage.getWikitext` to get the lines in wikitext. *        * @return See documentation body. */       getListingWikitextLines { var _a; return __awaiter(this, void 0, void 0, function* {                const lines = (yield this.listingPage.getWikitext).split('\n');                let skipCounter = 1;                let startLine = null;                let endLine = null;                let bulletList;                const normalizedId = normalizeTitle((_a = this.id) !== null && _a !== void 0 ? _a : this.title).getPrefixedText;               const idMalformed = normalizedId !== this.title.getPrefixedText;                for (let line = 0; line < lines.length; line++) {                    const lineText = lines[line];                    // Check if this denotes the end of a listing.                    // Matches: `*:`, `**`                    // Does not match: `*`, ``, ` `                    if (startLine != null) {                        if (bulletList ? !/^(\*[*:]+|:)/g.test(lineText) : /^[^:*]/.test(lineText)) {                           return { start: startLine, end: endLine !== null && endLine !== void 0 ? endLine : startLine };                        }                        else {                            endLine = line;                        }                    }                    else {                        const match = cloneRegex$1(CopyrightProblemsListing.articleCvRegex)                            .exec(lineText);                        if (match != null) {                            if (normalizeTitle(match[2] || match[4]).getPrefixedText !== normalizedId) {                               continue;                            }                            // Check if this should be skipped.                            if (skipCounter < this.i) {                                // Skip if we haven't skipped enough.                                skipCounter++;                                continue;                            }                            if (idMalformed && match[2] === match[3]) {                                throw new Error(`Expected malformed listing with ID "${normalizedId}" and title "${this.title.getPrefixedText}" but got normal listing.`);                            }                            bulletList = /[*:]/.test((match[1] || '').trim);                            startLine = line;                        }                    }                }                // We've reached the end of the document. // `startLine` is only ever set if the IDs match, so we can safely assume // that if `startLine` and `endLine` is set or if `startLine` is the last line // in the page, then we've found the listing (and it is the last listing on the               // page, where `endLine` would have been set if it had comments). if ((startLine != null && endLine != null) ||                   (startLine != null && startLine === lines.length - 1)) { return { start: startLine, end: endLine !== null && endLine !== void 0 ? endLine : startLine }; }               // Couldn't find an ending. Malformed listing? // It should be nearly impossible to hit this condition. // Gracefully handle this. throw new Error("Couldn't detect listing from wikitext (edit conflict/is it missing?)"); });       }        /**         * Adds a comment to an existing listing.         *         * @param message         * @param indent         * @return the modified page wikitext.         */        addComment(message, indent = false) {            return __awaiter(this, void 0, void 0, function*  { const lines = (yield this.listingPage.getWikitext).split('\n'); const range = yield this.getListingWikitextLines; if (indent) { // This usually isn't needed. handles the bullet. message = (this.element.parentElement.tagName === 'LI' ?                       '*:' :                        ':') + message; }               lines.splice(range.end + 1, 0, message); return lines.join('\n'); });       }        /**         * Adds a comment to an existing listing AND saves the page. To avoid saving the page,         * use `addComment` instead.         *         * @param message         * @param summary         * @param indent         */        respond(message, summary, indent = false) {            return __awaiter(this, void 0, void 0, function*  { const newWikitext = yield this.addComment(message, indent); yield MwApi.action.postWithEditToken(Object.assign(Object.assign({}, changeTag(yield window.InfringementAssistant.getWikiConfig)), { action: 'edit', format: 'json', formatversion: '2', utf8: 'true', title: this.listingPage.title.getPrefixedText, text: newWikitext, summary: decorateEditSummary(summary !== null && summary !== void 0 ? summary : mw.msg('deputy.ia.content.respond', this.listingPage.title.getPrefixedText, this.title.getPrefixedText), window.InfringementAssistant.config) })); yield this.listingPage.getWikitext(true); });       }        /**         * Serialize this listing. Used for tests.         */        serialize {            return __awaiter(this, void 0, void 0, function*  { return { basic: this.basic, i: this.i,                   id: this.id, title: { namespace: this.title.namespace, title: this.title.title, fragment: this.title.getFragment },                   listingPage: { namespace: this.listingPage.title.namespace, title: this.listingPage.title.title, fragment: this.listingPage.title.getFragment },                   lines: yield this.getListingWikitextLines };           });        }    }

/**    *     */    class ListingResponsePanel extends EventTarget { /**        *         * @param originLink * @param listing */       constructor(originLink, listing) { super; // TODO: types-mediawiki limitation this.reloadPreviewThrottled = mw.util.throttle(this.reloadPreview, 500); this.originLink = originLink; this.listing = listing; }       /**         * @return A set of possible copyright problems responses. */       static get responses { return window.InfringementAssistant.wikiConfig.ia.responses.get; }       /**         *         * @param response * @param locale * @return The given response for the given locale */       static getResponseLabel(response, locale) { var _a, _b, _c; if (!locale) { locale = (_a = window.deputyLang) !== null && _a !== void 0 ? _a : mw.config.get('wgUserLanguage'); }           const locale1 = locale.replace(/-.*$/g, ''); return typeof response.label === 'string' ? response.label : ((_c = (_b = response.label[locale]) !== null && _b !== void 0 ? _b : response.label[locale1]) !== null && _c !== void 0 ? _c : response.label[0]); }       /**         * @return The edit summary for this edit. */       getEditSummary { var _a; return ((_a = this.prefill) === null || _a === void 0 ? void 0 : _a.closing) === false ? mw.msg('deputy.ia.content.respond', this.listing.listingPage.title.getPrefixedText, this.listing.title.getPrefixedText) : mw.msg('deputy.ia.content.close', this.listing.listingPage.title.getPrefixedText, this.listing.title.getPrefixedText); }       /**         * Renders the response dropdown. *        * @return An unwrapped OOUI DropdownInputWidget. */       renderPrefillDropdown { const options = [{ data: null, label: mw.msg('deputy.ia.listing.re.label'), disabled: true }];           for (const responseId in ListingResponsePanel.responses) { const response = ListingResponsePanel.responses[responseId]; options.push({                   data: `${responseId}`,                    label: ListingResponsePanel.getResponseLabel(response)                }); }           this.dropdown = new OO.ui.DropdownInputWidget({                options,                dropdown: {                    label: mw.msg('deputy.ia.listing.re.label'),                    title: mw.msg('deputy.ia.listing.re.title')                }            }); this.dropdown.on('change', (value) => {               this.prefill = ListingResponsePanel.responses[+value];                this.reloadPreviewThrottled;            }); return unwrapWidget(this.dropdown); }       /**         * @return An unwrapped OOUI TextInputWidget */       renderAdditionalCommentsField { this.commentsField = new OO.ui.MultilineTextInputWidget({               placeholder: mw.msg('deputy.ia.listing.re.extras'),                autosize: true,                rows: 1            }); this.commentsField.on('change', (text) => {               this.comments = text;                this.reloadPreviewThrottled;            }); return unwrapWidget(this.commentsField); }       /**         * @return An unwrapped OOUI ButtonWidget. */       renderCloseButton { const closeButton = new OO.ui.ButtonWidget({               flags: ['destructive'],                label: mw.msg('deputy.ia.listing.re.close'),                framed: true            }); closeButton.on('click', => {                this.close;            }); return unwrapWidget(closeButton); }       /**         * @return An unwrapped OOUI ButtonWidget. */       renderSubmitButton { this.submitButton = new OO.ui.ButtonWidget({               flags: ['progressive', 'primary'],                label: mw.msg('deputy.ia.listing.re.submit'),                disabled: true            }); this.submitButton.on('click', => __awaiter(this, void 0, void 0, function*  { this.dropdown.setDisabled(true); this.commentsField.setDisabled(true); this.submitButton.setDisabled(true); try { yield this.listing.respond(this.toWikitext, this.getEditSummary, false); const dd = h_1("dd", { dangerouslySetInnerHTML: this.previewPanel.innerHTML }); dd.querySelectorAll('.deputy') .forEach((v) => removeElement(v)); // Try to insert at an existing list for better spacing. if (this.element.previousElementSibling.tagName === 'DL') { this.element.previousElementSibling.appendChild(dd); }                   else { this.element.insertAdjacentElement('afterend', h_1("dl", { class: "ia-newResponse" }, dd)); }                   this.close; mw.notify(mw.msg('deputy.ia.listing.re.published'), {                       type: 'success'                    }); }               catch (e) { error(e); OO.ui.alert(mw.msg('deputy.ia.listing.re.error', e.message)); this.dropdown.setDisabled(false); this.commentsField.setDisabled(false); this.submitButton.setDisabled(false); }           }));            return unwrapWidget(this.submitButton); }       /**         * Reloads the preview. */       reloadPreview { const wikitext = this.toWikitext; if (wikitext == null) { this.previewPanel.style.display = 'none'; }           else { this.previewPanel.style.display = ''; }           renderWikitext(wikitext, this.listing.listingPage.title.getPrefixedText, {                pst: true,                summary: this.getEditSummary            }).then((data) => {                var _a, _b;                this.previewPanel.innerHTML = data;                const cpcContent = this.previewPanel.querySelector('ul > li > dl > dd');                if (cpcContent) {                    // Extract ONLY the actual text.                    this.previewPanel.innerHTML = cpcContent.innerHTML;                }                // Infuse collapsibles                (_b = (_a = $(this.previewPanel).find('.mw-collapsible')).makeCollapsible) === null || _b === void 0 ? void 0 : _b.call(_a);                $(this.previewPanel).find('.collapsible')                    .each((i, e) => { var _a, _b; (_b = (_a = $(e)).makeCollapsible) === null || _b === void 0 ? void 0 : _b.call(_a, {                       collapsed: e.classList.contains('collapsed')                    }); });               // Add in "summary" row.                this.previewPanel.insertAdjacentElement('afterbegin', h_1("div", { class: "deputy", style: {                        fontSize: '0.9em',                        borderBottom: '1px solid #c6c6c6',                        marginBottom: '0.5em',                        paddingBottom: '0.5em'                    } },                    "Summary: ",                    h_1("i", null, "(",                       h_1("span", { class: "mw-content-text", dangerouslySetInnerHTML: data.summary }),                        ")")));                // Make all anchor links open in a new tab (prevents exit navigation)                this.previewPanel.querySelectorAll('a')                    .forEach((el) => { if (el.hasAttribute('href')) { el.setAttribute('target', '_blank'); el.setAttribute('rel', 'noopener'); }               });            });        }        /**         * @return A wikitext representation of the response generated by this panel. */       toWikitext { var _a, _b, _c; if (this.prefill == null && this.comments == null) { (_a = this.submitButton) === null || _a === void 0 ? void 0 : _a.setDisabled(true); return null; }           else { (_b = this.submitButton) === null || _b === void 0 ? void 0 : _b.setDisabled(false); }           return this.prefill ? mw.format(this.prefill.template, this.listing.title, (_c = this.comments) !== null && _c !== void 0 ? _c : '') : this.comments; }       /**         * @return The listing panel */       render { return this.element = h_1("div", { class: "ia-listing-response" },               h_1("div", { class: "ia-listing-response--dropdown" }, this.renderPrefillDropdown),                h_1("div", { class: "ia-listing-response--comments" }, this.renderAdditionalCommentsField),                this.previewPanel = h_1("div", { class: "ia-listing--preview", "data-label": mw.msg('deputy.ia.listing.re.preview'), style: 'display: none' }),                h_1("div", { class: "ia-listing-response--submit" }, this.renderCloseButton, this.renderSubmitButton)); }       /**         * Announce closure of this panel and remove it from the DOM. */       close { this.dispatchEvent(new Event('close')); removeElement(this.element); }   }

/**    *     * @param session * @param listing * @return An HTML element */   function ListingActionLink(session, listing) { const element = h_1("div", { class: "ia-listing-action" },           h_1("span", { class: "ia-listing-action--bracket" }, mw.msg('deputy.ia.listing.respondPre')),            h_1("a", { class: "ia-listing-action--link", role: "button", href: "", onClick: (event) => __awaiter(this, void 0, void 0, function*  {                    const target = event.currentTarget;                    target.toggleAttribute('disabled', true);                    mw.loader.using(window.InfringementAssistant.static.dependencies,  => { const panel = new ListingResponsePanel(element, listing); listing.element.parentElement.appendChild(panel.render); element.style.display = 'none'; panel.addEventListener('close', => {                            element.style.display = '';                        }); target.toggleAttribute('disabled', false); });               }) }, mw.msg('deputy.ia.listing.respond')),            h_1("span", { class: "ia-listing-action--bracket" }, mw.msg('deputy.ia.listing.respondPost'))); return element; }

let InternalCCICaseInputWidget; /**    * Initializes the process element. */   function initCCICaseInputWidget { InternalCCICaseInputWidget = class CCICaseInputWidget extends mw.widgets.TitleInputWidget { /**            *             * @param config */           constructor(config) { super(Object.assign(Object.assign({}, config), { inputFilter: (value) => { const prefix = window.InfringementAssistant.wikiConfig .cci.rootPage.get.getPrefixedText + '/'; // Simple replace, only 1 replacement made anyway. const trimmed = value.replace(prefix, '').trimStart; if (config.inputFilter) { return config.inputFilter(trimmed); }                       else { return trimmed; }                   } }));                this.getQueryValue = function  { return `${window.InfringementAssistant.wikiConfig.cci.rootPage.get .getPrefixedText}/${this.getValue.trimEnd}`; };           }        };    }    /**     * Creates a new CCICaseInputWidget. *    * @param config Configuration to be passed to the element. * @return A CCICaseInputWidget object */   function CCICaseInputWidget (config) { if (!InternalCCICaseInputWidget) { initCCICaseInputWidget; }       return new InternalCCICaseInputWidget(config); }

let InternalSinglePageWorkflowDialog; /**    * Initializes the process element. */   function initSinglePageWorkflowDialog { var _a; InternalSinglePageWorkflowDialog = (_a = class SinglePageWorkflowDialog extends OO.ui.ProcessDialog {               /**                 * @param config Configuration to be passed to the element.                 */                constructor(config) {                    var _a;                    super;                    this.page = normalizeTitle(config.page);                    this.revid = config.revid;                    this.shadow = (_a = config.shadow) !== null && _a !== void 0 ? _a : true;                    const userConfig = window.InfringementAssistant.config;                    this.data = {                        entirePage: userConfig.ia.defaultEntirePage.get,                        fromUrls: userConfig.ia.defaultFromUrls.get                    };                }                /**                 * @return The body height of this dialog.                 */                getBodyHeight { return 500; }               /**                 * Initializes the dialog. */               initialize { super.initialize; const intro = unwrapJQ(h_1("div", { class: "ia-report-intro" }), mw.message('deputy.ia.report.intro', CopyrightProblemsPage.getCurrentListingPage.getPrefixedText).parseDom); intro.querySelector('a').setAttribute('target', '_blank'); const page = unwrapJQ(h_1("div", { class: "ia-report-intro" }), mw.message('deputy.ia.report.page', this.page.getPrefixedText).parseDom); page.querySelector('a').setAttribute('target', '_blank'); this.fieldsetLayout = new OO.ui.FieldsetLayout({                       items: this.renderFields                    }); this.$body.append(new OO.ui.PanelLayout({ expanded: false, framed: false, padded: true, content: [ equalTitle(null, this.page) ? '' : page, intro, this.fieldsetLayout, this.renderSubmitButton ]                   }).$element); return this; }               /**                 * @return A JSX.Element */               renderSubmitButton { const hideButton = new OO.ui.ButtonWidget({                       label: mw.msg('deputy.ia.report.hide'),                        title: mw.msg('deputy.ia.report.hide'),                        flags: ['progressive']                    }); hideButton.on('click', => {                        this.executeAction('hide');                    }); const submitButton = new OO.ui.ButtonWidget({                       label: mw.msg('deputy.ia.report.submit'),                        title: mw.msg('deputy.ia.report.submit'),                        flags: ['primary', 'progressive']                    }); submitButton.on('click', => {                        this.executeAction('submit');                    }); return h_1("div", { class: "ia-report-submit" },                       this.shadow && unwrapWidget(hideButton),                        unwrapWidget(submitButton)); }               /**                 * Render OOUI FieldLayouts to be appended to the fieldset layout. *                * @return An array of OOUI `FieldLayout`s */               renderFields { const entirePageByDefault = this.data.entirePage; this.inputs = { entirePage: new OO.ui.CheckboxInputWidget({                           selected: entirePageByDefault                        }), startSection: new OO.ui.DropdownInputWidget({                           $overlay: this.$overlay,                            disabled: entirePageByDefault,                            title: mw.msg('deputy.ia.report.startSection.placeholder')                        }), endSection: new OO.ui.DropdownInputWidget({                           $overlay: this.$overlay,                            disabled: entirePageByDefault,                            title: mw.msg('deputy.ia.report.endSection.placeholder')                        }), presumptive: new OO.ui.CheckboxInputWidget({                           selected: false                        }), presumptiveCase: CCICaseInputWidget({                           allowArbitrary: false,                            required: true,                            showMissing: false,                            validateTitle: true,                            excludeDynamicNamespaces: true                        }), fromUrls: new OO.ui.CheckboxInputWidget({                           selected: this.data.fromUrls                        }), sourceUrls: new OO.ui.MenuTagMultiselectWidget({                           $overlay: this.$overlay,                            allowArbitrary: true,                            inputPosition: 'outline',                            indicator: 'required',                            placeholder: mw.msg('deputy.ia.report.sourceUrls.placeholder')                        }), sourceText: new OO.ui.MultilineTextInputWidget({                           autosize: true,                            maxRows: 2,                            placeholder: mw.msg('deputy.ia.report.sourceText.placeholder')                        }), additionalNotes: new OO.ui.MultilineTextInputWidget({                           autosize: true,                            maxRows: 2,                            placeholder: mw.msg('deputy.ia.report.additionalNotes.placeholder')                        }) };                   const fields = { entirePage: new OO.ui.FieldLayout(this.inputs.entirePage, {                           align: 'inline',                            label: mw.msg('deputy.ia.report.entirePage.label')                        }), startSection: new OO.ui.FieldLayout(this.inputs.startSection, {                           align: 'top',                            label: mw.msg('deputy.ia.report.startSection.label')                        }), // Create FieldLayouts for all fields in this.inputs endSection: new OO.ui.FieldLayout(this.inputs.endSection, {                           align: 'top',                            label: mw.msg('deputy.ia.report.endSection.label'),                            help: mw.msg('deputy.ia.report.endSection.help')                        }), presumptive: new OO.ui.FieldLayout(this.inputs.presumptive, {                           align: 'inline',                            label: mw.msg('deputy.ia.report.presumptive.label'),                            help: mw.msg('deputy.ia.report.presumptive.help')                        }), presumptiveCase: new OO.ui.FieldLayout(this.inputs.presumptiveCase, {                           align: 'top',                            label: mw.msg('deputy.ia.report.presumptiveCase.label'),                            help: mw.msg('deputy.ia.report.presumptiveCase.help')                        }), fromUrls: new OO.ui.FieldLayout(this.inputs.fromUrls, {                           align: 'inline',                            label: mw.msg('deputy.ia.report.fromUrls.label'),                            help: mw.msg('deputy.ia.report.fromUrls.help')                        }), sourceUrls: new OO.ui.FieldLayout(this.inputs.sourceUrls, {                           align: 'top',                            label: mw.msg('deputy.ia.report.source.label')                        }), sourceText: new OO.ui.FieldLayout(this.inputs.sourceText, {                           align: 'top',                            label: mw.msg('deputy.ia.report.source.label')                        }), additionalNotes: new OO.ui.FieldLayout(this.inputs.additionalNotes, {                           align: 'top',                            label: mw.msg('deputy.ia.report.additionalNotes.label')                        }) };                   this.inputs.entirePage.on('change', (selected) => {                        if (selected === undefined) {                            // Bad firing.                            return;                        }                        this.data.entirePage = selected;                        this.inputs.startSection.setDisabled(selected);                        this.inputs.endSection.setDisabled(selected);                    }); const entirePageHiddenCheck = => { if (this.inputs.startSection.getValue === '-1' &&                           this.inputs.endSection.getValue === `${this.sections.length - 1}`) { this.inputs.entirePage.setSelected(true); }                   };                    const thisTitle = this.page.getPrefixedDb; this.inputs.startSection.on('change', (value) => {                       const section = value === '-1' ? null : this.sections[+value];                        this.data.startSection = section;                        this.data.startOffset = section == null ? 0 : section.byteoffset;                        // Automatically lock out sections before the start in the end dropdown                        for (const item of this.inputs.endSection.dropdownWidget.menu.items) {                            if (item.data === '-1') {                                item.setDisabled(value !== '-1');                            }                            else if (this.sections[item.data].fromtitle === thisTitle) {                                if (this.sections[item.data].i < +value) {                                    item.setDisabled(true);                                }                                else { item.setDisabled(false); }                           }                        }                        entirePageHiddenCheck; });                   this.inputs.endSection.on('change', (value) => { var _a, _b; const section = value === '-1' ? null : this.sections[+value]; // Ensure sections exist first. if (this.sections.length > 0) { this.data.endSection = section; // Find the section directly after this one, or if null (or last section), use // the end of the page for it. this.data.endOffset = section == null ? this.sections[0].byteoffset : ((_b = (_a = this.sections[section.i + 1]) === null || _a === void 0 ? void 0 : _a.byteoffset) !== null && _b !== void 0 ? _b : this.wikitext.length); // Automatically lock out sections before the end in the start dropdown for (const item of this.inputs.startSection.dropdownWidget.menu.items) { if (item.data === '-1') { item.setDisabled(value === '-1'); }                               else if (this.sections[item.data].fromtitle === thisTitle) { if (this.sections[item.data].i > +value) { item.setDisabled(true); }                                   else { item.setDisabled(false); }                               }                            }                        }                        entirePageHiddenCheck; });                   const enablePresumptive = window.InfringementAssistant.wikiConfig.ia.allowPresumptive.get &&                        !!window.InfringementAssistant.wikiConfig.cci.rootPage.get;                    fields.presumptive.toggle(enablePresumptive);                    fields.presumptiveCase.toggle(false);                    this.inputs.presumptive.on('change', (selected) => { var _a; this.data.presumptive = selected; fields.presumptiveCase.toggle(selected); fields.fromUrls.toggle(!selected); if (!selected) { if ((_a = this.data.fromUrls) !== null && _a !== void 0 ? _a : window.InfringementAssistant.config.ia.defaultFromUrls.get) { fields.sourceUrls.toggle(true); // No need to toggle sourceText, assume it is already hidden. }                           else { fields.sourceText.toggle(true); // No need to toggle sourceText, assume it is already hidden. }                       }                        else { fields.sourceUrls.toggle(false); fields.sourceText.toggle(false); }                   });                    this.inputs.presumptiveCase.on('change', (text) => { this.data.presumptiveCase = text.replace(window.InfringementAssistant.wikiConfig.cci.rootPage.get.getPrefixedText, ''); });                   this.inputs.fromUrls.on('change', (selected = this.data.fromUrls) => { if (selected === undefined) { // Bad firing. return; }                       this.data.fromUrls = selected; fields.sourceUrls.toggle(selected); fields.sourceText.toggle(!selected); });                   this.inputs.sourceUrls.on('change', (items) => { this.data.sourceUrls = items.map((item) => item.data); });                   this.inputs.sourceText.on('change', (text) => { this.data.sourceText = text.replace(/\.\s*$/, ''); });                   // Presumptive deletion is default false, so no need to check for its state here.                    if (window.InfringementAssistant.config.ia.defaultFromUrls.get) {                        fields.sourceText.toggle(false);                    }                    else {                        fields.sourceUrls.toggle(false);                    }                    this.inputs.additionalNotes.on('change', (text) => { this.data.notes = text; });                   return this.shadow ? getObjectValues(fields) : [                        fields.presumptive, fields.presumptiveCase,                        fields.fromUrls, fields.sourceUrls, fields.sourceText,                        fields.additionalNotes                    ];                }                /**                 * Generate options from the section set.                 *                 * @return An array of DropdownInputWidget options                 */                generateSectionOptions {                    const thisTitle = this.page.getPrefixedDb;                    const options = [];                    if (this.sections.length > 0) {                        this.sections.forEach((section) => { options.push(Object.assign({ data: section.i, label: mw.message('deputy.ia.report.section', section.number, section.line).text }, (section.fromtitle !== thisTitle ? {                               disabled: true,                                title: mw.message('deputy.ia.report.transcludedSection', section.fromtitle).text                            } : {}))); });                   }                    else {                        this.inputs.entirePage.setDisabled(true);                    }                    return options;                }                /**                 * @param data                 * @return An OOUI Process                 */                getSetupProcess(data) {                    const process = super.getSetupProcess.call(this, data);                    process.next(MwApi.action.get(Object.assign(Object.assign({ action: 'parse' }, (this.revid ? { oldid: this.revid } : { page: this.page.getPrefixedText })), { prop: 'externallinks|sections|wikitext' })).then((res) => {                       var _a, _b, _c;                        this.externalLinks = (_a = res.parse.externallinks) !== null && _a !== void 0 ? _a : [];                        this.sections = (_c = (_b = res.parse.sections) === null || _b === void 0 ? void 0 : _b.map((v, k) => Object.assign(v, { i: k }))) !== null && _c !== void 0 ? _c : [];                       this.wikitext = res.parse.wikitext;                        if (this.sections.length === 0) {                            // No sections. Automatically use full page.                            this.data.entirePage = true;                        }                        const options = [                            {                                data: '-1',                                label: mw.msg('deputy.ia.report.lead'),                                selected: true                            },                            ...this.generateSectionOptions                        ];                        this.inputs.startSection.setOptions(options);                        this.inputs.endSection.setOptions(options);                        this.inputs.sourceUrls.menu.clearItems; this.inputs.sourceUrls.addOptions(this.externalLinks.map((v) => ({ data: v, label: v }))); }));                   process.next( => {                        blockExit('ia-spwd');                    }); return process; }               /**                 * Hides the page content. */               hideContent { var _a, _b, _c, _d, _e, _f; return __awaiter(this, void 0, void 0, function* {                        let finalPageContent;                        const wikiConfig = (yield window.InfringementAssistant.getWikiConfig).ia;                        const copyvioWikitext = msgEval(wikiConfig.hideTemplate.get, { presumptive: this.data.presumptive ? 'true' : '', presumptiveCase: this.data.presumptiveCase ? 'true' : '', fromUrls: this.data.fromUrls ? 'true' : '', sourceUrls: this.data.sourceUrls ? 'true' : '', sourceText: this.data.sourceText ? 'true' : '', entirePage: this.data.entirePage ? 'true' : '' }, this.data.presumptive ? `${window.deputy.wikiConfig.cci.rootPage.get.getPrefixedText}/${this.data.presumptiveCase}` : (this.data.fromUrls ?                           (_b = ((_a = this.data.sourceUrls) !== null && _a !== void 0 ? _a : [])[0]) !== null && _b !== void 0 ? _b : '' :                            this.data.sourceText), this.data.entirePage ? 'true' : 'false').text;                       if (this.data.entirePage) {                            finalPageContent = copyvioWikitext + '\n' + this.wikitext;                        }                        else {                            finalPageContent =                                this.wikitext.slice(0, this.data.startOffset) +                                    copyvioWikitext + '\n' +                                    this.wikitext.slice(this.data.startOffset, this.data.endOffset) +                                    wikiConfig.hideTemplateBottom.get + '\n' +                                    this.wikitext.slice(this.data.endOffset);                        }                        yield MwApi.action.postWithEditToken(Object.assign(Object.assign({}, changeTag(yield window.InfringementAssistant.getWikiConfig)), { action: 'edit', title: this.page.getPrefixedText, text: finalPageContent, summary: decorateEditSummary(this.data.entirePage ? mw.msg(this.data.presumptive ?                                   'deputy.ia.content.hideAll.pd' :                                    'deputy.ia.content.hideAll',                                 // Only ever used if presumptive is set.                                ...(this.data.presumptive ? [                                   window.InfringementAssistant.wikiConfig .cci.rootPage.get.getPrefixedText, this.data.presumptiveCase ] : [])) :                               mw.msg(this.data.presumptive ?                                    'deputy.ia.content.hideAll.pd' :                                    'deputy.ia.content.hide', this.page.getPrefixedText, (_c = this.data.startSection) === null || _c === void 0 ? void 0 : _c.anchor, (_d = this.data.startSection) === null || _d === void 0 ? void 0 : _d.line, (_e = this.data.endSection) === null || _e === void 0 ? void 0 : _e.anchor, (_f = this.data.endSection) === null || _f === void 0 ? void 0 : _f.line, ...(this.data.presumptive ? [                                   window.InfringementAssistant.wikiConfig .cci.rootPage.get.getPrefixedText, this.data.presumptiveCase ] : [])), window.InfringementAssistant.config) }));                   }); }               /**                 * Posts a listing to the current copyright problems listing page. */               postListing { var _a, _b, _c; return __awaiter(this, void 0, void 0, function* {                        const sourceUrls = (_a = this.data.sourceUrls) !== null && _a !== void 0 ? _a : [];                        const from = this.data.fromUrls ?                            sourceUrls                                .map((v) => `[${v}]`)                                .join(sourceUrls.length > 2 ? mw.msg('deputy.comma-separator') : ' ') :                           this.data.sourceText;                        const comments = (from || '').trim.length !== 0 || this.data.presumptive ?                            mw.format(mw.msg(this.data.presumptive ?                                'deputy.ia.content.listingComment.pd' :                                'deputy.ia.content.listingComment', ...(this.data.presumptive ? [                               window.InfringementAssistant.wikiConfig .cci.rootPage.get.getPrefixedText, this.data.presumptiveCase ] : [from]), (_b = this.data.notes) !== null && _b !== void 0 ? _b : )) :                           (_c = this.data.notes) !== null && _c !== void 0 ? _c : ;                        yield CopyrightProblemsPage.getCurrent                            .postListing(this.page, comments, this.data.presumptive);                    }); }               /**                 * @param action * @return An OOUI Process */               getActionProcess(action) { const process = super.getActionProcess.call(this, action); if (action === 'submit') { process.next(this.postListing); }                   if (action === 'submit' || action === 'hide') { if (this.shadow) { process.next(this.hideContent); }                       process.next( => {                            mw.notify(!this.shadow ? mw.msg('deputy.ia.report.success.report') : (action === 'hide' ?                                   mw.msg('deputy.ia.report.success.hide') :                                    mw.msg('deputy.ia.report.success')), { type: 'success' });                        }); switch (window.InfringementAssistant.config.ia['on' + (action === 'hide' ? 'Hide' : 'Submit')].get) { case TripleCompletionAction.Reload: process.next( => {                                   unblockExit('ia-spwd');                                    window.location.reload;                                }); break; case TripleCompletionAction.Redirect: process.next( => {                                   unblockExit('ia-spwd');                                    window.location.href = mw.util.getUrl(CopyrightProblemsPage.getCurrent.title.getPrefixedText);                                }); break; }                   }                    process.next(function  {                        unblockExit('ia-spwd');                        this.close({ action: action });                    }, this); return process; }           },            // For dialogs. Remove if not a dialog. _a.static = Object.assign(Object.assign({}, OO.ui.ProcessDialog.static), { name: 'iaSinglePageWorkflowDialog', title: mw.msg('deputy.ia'), actions: [                   {                        flags: ['safe', 'close'],                        icon: 'close',                        label: mw.msg('deputy.ante.close'),                        title: mw.msg('deputy.ante.close'),                        invisibleLabel: true,                        action: 'close'                    }                ] }), _a);   }    /**     * Creates a new SinglePageWorkflowDialog.     *     * @param config Configuration to be passed to the element.     * @return A SinglePageWorkflowDialog object     */    function SinglePageWorkflowDialog (config) {        if (!InternalSinglePageWorkflowDialog) {            initSinglePageWorkflowDialog;        }        return new InternalSinglePageWorkflowDialog(config);    }

/**    * Delinks wikitext. Does not handle templates. Only does dumb delinking (RegExp    * replacement; does not parse and handle link nesting, etc.). *    * @param string * @return delinked wikitext */   function delink(string) { return string.replace(cloneRegex$1(/\[\[(.+?)(?:\|.*?)?]]/g), '$1'); }

/**    * Purges a page. *    * @param title The title of the page to purge */   function purge(title) { return __awaiter(this, void 0, void 0, function* {            yield MwApi.action.post({ action: 'purge', titles: normalizeTitle(title).getPrefixedText, redirects: true });       });    }

/**    *     * @param props * @param props.button * @return A panel for opening a single page workflow dialog */   function NewCopyrightProblemsListingPanel(props) { const titleSearch = new mw.widgets.TitleInputWidget({           required: true,            showMissing: false,            validateTitle: true,            excludeDynamicNamespaces: true        }); const cancelButton = new OO.ui.ButtonWidget({           classes: ['ia-listing-new--cancel'],            label: mw.msg('deputy.cancel'),            flags: ['destructive']        }); const openButton = new OO.ui.ButtonWidget({           label: mw.msg('deputy.ia.listing.new.report'),            flags: ['progressive']        }); const field = new OO.ui.ActionFieldLayout(titleSearch, openButton, {           classes: ['ia-listing-new--field'],            align: 'top',            label: mw.msg('deputy.ia.listing.new.page.label')        }); const el = h_1("div", { class: "ia-listing-new" },           unwrapWidget(field),            unwrapWidget(cancelButton)); openButton.on('click', => {            if (titleSearch.isQueryValid) {                mw.loader.using(window.InfringementAssistant.static.dependencies,  => __awaiter(this, void 0, void 0, function*  {                    props.button.setDisabled(false);                    removeElement(el);                    const spwd = SinglePageWorkflowDialog({ page: titleSearch.getMWTitle, shadow: false });                   yield openWindow(spwd);                }));            }        }); cancelButton.on('click', => {            props.button.setDisabled(false);            removeElement(el);        }); return el; }   /**     *     * @param props * @param props.button * @return A panel for reporting multiple pages */   function NewCopyrightProblemsBatchListingPanel(props) { blockExit('ia-ncpbl'); const cancelButton = new OO.ui.ButtonWidget({           label: mw.msg('deputy.cancel'),            flags: ['destructive']        }); const openButton = new OO.ui.ButtonWidget({           label: mw.msg('deputy.ia.listing.new.report'),            flags: ['progressive', 'primary']        }); const inputs = { title: new OO.ui.TextInputWidget({               required: true,                placeholder: mw.msg('deputy.ia.listing.new.title.placeholder')            }), titleMultiselect: new mw.widgets.TitlesMultiselectWidget({               inputPosition: 'outline',                allowArbitrary: false,                required: true,                showMissing: false,                validateTitle: true,                excludeDynamicNamespaces: true            }), presumptive: new OO.ui.CheckboxInputWidget({               selected: false            }), presumptiveCase: CCICaseInputWidget({               allowArbitrary: false,                required: true,                showMissing: false,                validateTitle: true,                excludeDynamicNamespaces: true            }), comments: new OO.ui.TextInputWidget({               placeholder: mw.msg('deputy.ia.listing.new.comments.placeholder')            }) };       const field = { title: new OO.ui.FieldLayout(inputs.title, {               align: 'top',                label: mw.msg('deputy.ia.listing.new.title.label')            }), titleSearch: new OO.ui.FieldLayout(inputs.titleMultiselect, {               align: 'top',                label: mw.msg('deputy.ia.listing.new.pages.label')            }), comments: new OO.ui.FieldLayout(inputs.comments, {               align: 'top',                label: mw.msg('deputy.ia.listing.new.comments.label')            }), presumptive: new OO.ui.FieldLayout(inputs.presumptive, {               align: 'inline',                label: mw.msg('deputy.ia.listing.new.presumptive.label'),                help: mw.msg('deputy.ia.listing.new.presumptive.help')            }), presumptiveCase: new OO.ui.FieldLayout(inputs.presumptiveCase, {               align: 'top',                label: mw.msg('deputy.ia.listing.new.presumptiveCase.label'),                help: mw.msg('deputy.ia.listing.new.presumptiveCase.help')            }) };       const getData = (listingPage) => { return { wikitext: listingPage.getBatchListingWikitext(inputs.titleMultiselect.items.map((v) => new mw.Title(v.data)), inputs.title.getValue, inputs.presumptive.getValue ?                   mw.msg('deputy.ia.content.batchListingComment.pd', window.InfringementAssistant.wikiConfig .cci.rootPage.get.getPrefixedText, inputs.presumptiveCase.getValue, inputs.comments.getValue) :                   inputs.comments.getValue), summary: mw.msg(inputs.presumptive.getValue ?                   'deputy.ia.content.batchListing.pd' :                    'deputy.ia.content.batchListing', listingPage.title.getPrefixedText, delink(inputs.title.getValue)) };       };        const currentListingPage = CopyrightProblemsPage.getCurrent; const previewPanel = h_1("div", { class: "ia-listing--preview", "data-label": mw.msg('deputy.ia.listing.new.preview') }); // TODO: types-mediawiki limitation const reloadPreview = mw.util.throttle( => __awaiter(this, void 0, void 0, function* { const data = getData(currentListingPage); yield renderWikitext(data.wikitext, currentListingPage.title.getPrefixedText, {               pst: true,                summary: data.summary            }).then((renderedWikitext) => {                var _a, _b;                previewPanel.innerHTML = renderedWikitext;                // Infuse collapsibles                (_b = (_a = $(previewPanel).find('.mw-collapsible')).makeCollapsible) === null || _b === void 0 ? void 0 : _b.call(_a);                $(previewPanel).find('.collapsible')                    .each((i, e) => { var _a, _b; (_b = (_a = $(e)).makeCollapsible) === null || _b === void 0 ? void 0 : _b.call(_a, {                       collapsed: e.classList.contains('collapsed')                    }); });               // Add in "summary" row.                previewPanel.insertAdjacentElement('afterbegin', h_1("div", { class: "deputy", style: {                        fontSize: '0.9em',                        borderBottom: '1px solid #c6c6c6',                        marginBottom: '0.5em',                        paddingBottom: '0.5em'                    } },                    "Summary: ",                    h_1("i", null, "(",                       h_1("span", { class: "mw-content-text", dangerouslySetInnerHTML: renderedWikitext.summary }),                        ")")));                // Make all anchor links open in a new tab (prevents exit navigation)                previewPanel.querySelectorAll('a')                    .forEach((el) => { if (el.hasAttribute('href')) { el.setAttribute('target', '_blank'); el.setAttribute('rel', 'noopener'); }               });            });        }), 500);        getObjectValues(inputs).forEach((a) => {            a.on('change', reloadPreview);        }); const el = h_1("div", { class: "ia-batchListing-new" },           unwrapWidget(field.titleSearch),            unwrapWidget(field.title),            unwrapWidget(field.comments),            previewPanel,            h_1("div", { class: "ia-batchListing-new--buttons" }, unwrapWidget(cancelButton), unwrapWidget(openButton))); let disabled = false; const setDisabled = (_disabled = !disabled) => { cancelButton.setDisabled(_disabled); openButton.setDisabled(_disabled); inputs.title.setDisabled(_disabled); inputs.titleMultiselect.setDisabled(_disabled); inputs.comments.setDisabled(_disabled); disabled = _disabled; };       openButton.on('click',  => __awaiter(this, void 0, void 0, function*  { setDisabled(true); yield reloadPreview; if (inputs.titleMultiselect.items.length > 0 &&               (inputs.title.getValue || '').trim.length > 0) { yield currentListingPage.postListings(inputs.titleMultiselect.items.map((v) => new mw.Title(v.data)), inputs.title.getValue, inputs.comments.getValue).then( => __awaiter(this, void 0, void 0, function* { yield purge(currentListingPage.title).catch( => { }); mw.notify(mw.msg('deputy.ia.listing.new.batchListed'), {                       type: 'success'                    }); unblockExit('ia-ncpbl'); removeElement(el); props.button.setDisabled(false); switch (window.InfringementAssistant.config.ia.onBatchSubmit.get) { case CompletionAction.Nothing: break; default: window.location.reload; }               }), (e) => {                    mw.notify(mw.msg('deputy.ia.listing.new.batchError', e.message), { type: 'error' });                   setDisabled(false);                }); }       }));        cancelButton.on('click',  => {            props.button.setDisabled(false);            unblockExit('ia-ncpbl');            removeElement(el);        }); return el; }   /**     * @return The HTML button set and panel container */   function NewCopyrightProblemsListing { const root = h_1("div", { class: "deputy ia-listing-newPanel" }); const addListingButton = new OO.ui.ButtonWidget({           icon: 'add',            label: mw.msg('deputy.ia.listing.new'),            flags: 'progressive'        }); const addBatchListingButton = new OO.ui.ButtonWidget({           icon: 'add',            label: mw.msg('deputy.ia.listing.new.batch'),            flags: 'progressive'        }); addListingButton.on('click', => {            addListingButton.setDisabled(true);            root.appendChild(h_1(NewCopyrightProblemsListingPanel, { button: addListingButton }));        }); addBatchListingButton.on('click', => {            addBatchListingButton.setDisabled(true);            root.appendChild(h_1(NewCopyrightProblemsBatchListingPanel, { button: addBatchListingButton }));        }); root.appendChild(unwrapWidget(addListingButton)); root.appendChild(unwrapWidget(addBatchListingButton)); return root; }

/**    * A CopyrightProblemsPage that represents a page that currently exists on a document. * This document must be a MediaWiki page, and does not accept Parsoid-based documents * (due to the lack of `mw.config`), used to get canonical data about the current page. *    * To ensure that only an active document (either a current tab or a document within an     * IFrame) can be used, the constructor only takes in a `Document`. *    * This class runs on: * - The main `Wikipedia:Copyright problems` page * - `Wikipedia:Copyright problems` subpages (which may/may not be date-specific entries) */   class CopyrightProblemsSession extends CopyrightProblemsPage { /**        *         * @param document */       constructor(document = window.document) { const title = new mw.Title(document.defaultView.mw.config.get('wgPageName')); const revid = +document.defaultView.mw.config.get('wgCurRevisionId'); super(title, revid); this.listingMap = new Map; this.document = document; }       /**         * @param root * @return all copyright problem listings on the page. */       getListings(root = this.document) { const links = []; /**            * Avoids collisions by assigning an `i` number when a page appears as a listing twice. */           const headingSets = {}; root.querySelectorAll('#mw-content-text .mw-parser-output a:not(.external)').forEach((link) => {               if (this.listingMap.has(link)) {                    links.push(link);                    return;                }                const listingData = CopyrightProblemsListing.getListing(link) ||                    CopyrightProblemsListing.getBasicListing(link);                if (listingData) {                    const listingPageTitle = listingData.listingPage.getPrefixedDb;                    if (headingSets[listingPageTitle] == null) {                        headingSets[listingPageTitle] = {};                    }                    const id = normalizeTitle(isFullCopyrightProblemsListing(listingData) ? listingData.id : listingData.title).getPrefixedDb;                   const pageSet = headingSets[listingPageTitle];                    if (pageSet[id] != null) {                        pageSet[id]++;                    }                    else {                        pageSet[id] = 1;                    }                    this.listingMap.set(link, new CopyrightProblemsListing(listingData, this.main ? null : this, pageSet[id]));                    links.push(link);                }            }); return links.map((link) => this.listingMap.get(link)); }       /**         * Adds an action link to a copyright problem listing. *        * @param listing */       addListingActionLink(listing) { const baseElement = listing.element.parentElement; let beforeChild; for (const child of Array.from(baseElement.children)) { if (['OL', 'UL', 'DL'].indexOf(child.tagName) !== -1) { beforeChild = child; break; }           }            const link = ListingActionLink(this, listing); if (beforeChild) { beforeChild.insertAdjacentElement('beforebegin', link); }           else { baseElement.appendChild(link); }       }        /**         * Adds a panel containing the "new listing" buttons (single and multiple) * and the panel container (when filing a multiple-page listing) to the proper * location: either at the end of the copyright problems section or replacing * the redlink to the blank copyright problems page. */       addNewListingsPanel { document.querySelectorAll('.mw-headline a, .mw-heading a, a.external, a.redlink').forEach((el) => {               const href = el.getAttribute('href');                const url = new URL(href, window.location.href);                if (equalTitle(url.searchParams.get('title'), CopyrightProblemsPage.getCurrentListingPage) || url.pathname === mw.util.getUrl(CopyrightProblemsPage.getCurrentListingPage.getPrefixedText)) {                   if (el.classList.contains('external') || el.classList.contains('redlink')) {                        // Keep crawling up and find the parent of this element that is directly                        // below the parser root or the current section.                        let currentPivot = el;                        while (currentPivot != null && !currentPivot.classList.contains('mw-parser-output') && ['A', 'I', 'B', 'SPAN', 'EM', 'STRONG'] .indexOf(currentPivot.tagName) !== -1) {                           currentPivot = currentPivot.parentElement;                        }                        // We're now at the or or whatever.                        // Check if it only has one child (the tree that contains this element)                        // and if so, replace the links.                        if (currentPivot.children.length > 1) {                            return;                        }                        mw.loader.using([ 'oojs-ui-core', 'oojs-ui.styles.icons-interactions', 'mediawiki.widgets', 'mediawiki.widgets.TitlesMultiselectWidget' ], => {                            swapElements(currentPivot, NewCopyrightProblemsListing); });                   }                    else {                        // This is in a heading. Let's place it after the section heading.                        const heading = normalizeWikiHeading(el);                        if (heading.root.classList.contains('dp-ia-upgraded')) {                            return;                        }                        heading.root.classList.add('dp-ia-upgraded');                        mw.loader.using([ 'oojs-ui-core', 'oojs-ui.styles.icons-interactions', 'mediawiki.widgets', 'mediawiki.widgets.TitlesMultiselectWidget' ], => {                            heading.root.insertAdjacentElement('afterend', NewCopyrightProblemsListing); });                   }                }            });        }    }

var iaStyles = ".ia-listing-action {display: inline-block;}body.ltr .ia-listing-action {margin-left: 0.5em;}body.ltr .ia-listing-action--bracket:first-child,body.rtl .ia-listing-action--bracket:first-child {margin-right: 0.2em;}body.rtl .ia-listing-action {margin-right: 0.5em;}body.ltr .ia-listing-action--bracket:last-child,body.rtl .ia-listing-action--bracket:last-child {margin-left: 0.2em;}.ia-listing-action--link[disabled] {color: gray;pointer-events: none;}@keyframes ia-newResponse {from { background-color: #ffe29e }to { background-color: rgba( 0, 0, 0, 0 ); }}.ia-newResponse {animation: ia-newResponse 2s ease-out;}.ia-listing-response, .ia-listing-new {max-width: 50em;}.ia-listing-response {margin-top: 0.4em;margin-bottom: 0.4em;}.mw-content-ltr .ia-listing-response, .mw-content-rtl .mw-content-ltr .ia-listing-response {margin-left: 1.6em;margin-right: 0;}.mw-content-rtl .ia-listing-response, .mw-content-ltr .mw-content-rtl .ia-listing-response {margin-left: 0;margin-right: 1.6em;}.ia-listing-response > div {margin-bottom: 8px;}.ia-listing--preview {box-sizing: border-box;background: #f6f6f6;padding: 0.5em 1em;overflow: hidden;}/** \"Preview\" */.ia-listing--preview::before {content: attr(data-label);color: #808080;display: block;margin-bottom: 0.2em;}.ia-listing-response--submit {text-align: right;}/** * NEW LISTINGS */.ia-listing-newPanel {margin-top: 0.5em;}.ia-listing-new {display: flex;align-items: end;margin-top: 0.5em;padding: 1em;}.ia-listing-new--field {flex: 1;}.ia-listing-new--cancel {margin-left: 0.5em;}.ia-batchListing-new {padding: 1em;max-width: 50em;}.ia-batchListing-new--buttons {display: flex;justify-content: end;margin-top: 12px;}.ia-batchListing-new .ia-listing--preview {margin-top: 12px;}/** * REPORTING DIALOG */.ia-report-intro {font-size: 0.8rem;padding-bottom: 12px;border-bottom: 1px solid gray;margin-bottom: 12px;}.ia-report-intro b {display: block;font-size: 1rem;}.ia-report-submit {padding-top: 12px;display: flex;justify-content: flex-end;}/** * COPYVIO PREVIEWS */.copyvio.deputy-show {display: inherit !important;border: 0.2em solid #f88;padding: 1em;}.dp-hiddenVio {display: flex;flex-direction: row;margin: 1em 0;}.dp-hiddenVio-actions {flex: 0;margin-left: 1em;display: flex;flex-direction: column;justify-content: center;}";

/**    *     */    class HiddenViolationUI { /**        *         * @param el         */ constructor(el) { if (!el.classList.contains('copyvio') && !el.hasAttribute('data-copyvio')) { throw new Error('Attempted to create HiddenViolationUI on non-copyvio element.'); }           this.vioElement = el; }       /**         *         */        attach { this.vioElement.insertAdjacentElement('beforebegin', h_1("div", { class: "deputy dp-hiddenVio" }, h_1("div", null, this.renderMessage), h_1("div", { class: "dp-hiddenVio-actions" }, this.renderButton))); this.vioElement.classList.add('deputy-upgraded'); }       /**         * @return A message widget. */       renderMessage { return unwrapWidget(DeputyMessageWidget({ type: 'warning', label: mw.msg('deputy.ia.hiddenVio') }));       }        /**         * @return A button. */       renderButton { const button = new OO.ui.ToggleButtonWidget({               icon: 'eye',                label: mw.msg('deputy.ia.hiddenVio.show')            }); button.on('change', (shown) => {               button.setLabel(shown ? mw.msg('deputy.ia.hiddenVio.hide') : mw.msg('deputy.ia.hiddenVio.show'));               button.setIcon(shown ? 'eyeClosed' : 'eye');               this.vioElement.appendChild(h_1("div", { style: "clear: both;" }));                this.vioElement.classList.toggle('deputy-show', shown);            }); return unwrapWidget(button); }   }

/**    *     */    class InfringementAssistant extends DeputyModule { constructor { super(...arguments); this.static = InfringementAssistant; this.CopyrightProblemsPage = CopyrightProblemsPage; this.SinglePageWorkflowDialog = SinglePageWorkflowDialog; }       /**         * @inheritDoc */       getName { return 'ia'; }       /**         * Perform actions that run *before* IA starts (prior to execution). This involves * adding in necessary UI elements that serve as an entry point to IA.        */ preInit { const _super = Object.create(null, {               preInit: { get:  => super.preInit }            }); return __awaiter(this, void 0, void 0, function* {                if (!(yield _super.preInit.call(this, deputyIaEnglish))) {                    return false;                }                if (this.wikiConfig.ia.rootPage.get == null) {                    // Root page is invalid. Don't run.                    return false;                }                mw.hook('ia.preload').fire;                mw.util.addCSS(iaStyles);                if ( // Button not yet appended document.getElementById('pt-ia') == null && // Not virtual namespace mw.config.get('wgNamespaceNumber') >= 0) {                   mw.util.addPortletLink('p-tb', '#', // Messages used here: // * deputy.ia                   // * deputy.ia.short // * deputy.ia.acronym mw.msg({                       full: 'deputy.ia',                        short: 'deputy.ia.short',                        acronym: 'deputy.ia.acronym'                    }[this.config.core.portletNames.get]), 'pt-ia').addEventListener('click', (event) => { event.preventDefault; if (!event.currentTarget                           .hasAttribute('disabled')) { this.openWorkflowDialog; }                   });                }                // Autostart                // Query parameter-based autostart disable (i.e. don't start if param exists)                if (!/[?&]ia-autostart(=(0|no|false|off)?(&|$)|$)/.test(window.location.search)) {                    return mw.loader.using(InfringementAssistant.dependencies,  => __awaiter(this, void 0, void 0, function*  {                        yield this.init;                    }));                }                return true;            }); }       /**         *         */        init { return __awaiter(this, void 0, void 0, function* {                if (CopyrightProblemsPage.isListingPage && ['view', 'diff'].indexOf(mw.config.get('wgAction')) !== -1 && // Configured this.wikiConfig.ia.listingWikitextMatch.get != null && this.wikiConfig.ia.responses.get != null) {                   yield DeputyLanguage.loadMomentLocale;                    this.session = new CopyrightProblemsSession;                    mw.hook('wikipage.content').add((el) => { if (el[0].classList.contains('ia-upgraded')) { return; }                       el[0].classList.add('ia-upgraded'); this.session.getListings(el[0]).forEach((listing) => {                           this.session.addListingActionLink(listing);                        }); this.session.addNewListingsPanel; });               }                mw.hook('wikipage.content').add( => { mw.loader.using([                       'oojs-ui-core',                        'oojs-ui-widgets',                        'oojs-ui.styles.icons-alerts',                        'oojs-ui.styles.icons-accessibility'                    ],  => {                        document.querySelectorAll('.copyvio:not(.deputy-upgraded), [data-copyvio]:not(.deputy-upgraded)').forEach((el) => { new HiddenViolationUI(el).attach; });                   });                });            });        }        /**         *         */        openWorkflowDialog { return __awaiter(this, void 0, void 0, function* {                return mw.loader.using(InfringementAssistant.dependencies,  => __awaiter(this, void 0, void 0, function*  {                    yield DeputyLanguage.loadMomentLocale;                    if (!this.dialog) {                        yield DeputyLanguage.loadMomentLocale;                        this.dialog = SinglePageWorkflowDialog({ page: new mw.Title(mw.config.get('wgPageName')), revid: mw.config.get('wgCurRevisionId') });                       this.windowManager.addWindows([this.dialog]);                    }                    yield this.windowManager.openWindow(this.dialog).opened;                }));            }); }   }    InfringementAssistant.dependencies = [ 'moment', 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows', 'mediawiki.util', 'mediawiki.api', 'mediawiki.Title', 'mediawiki.widgets' ];

/**    * Handles most recent page visits. */   /**     *     */    class Recents { /**        * Saves the current page to the local list of most recently visited pages. */       static save { var _a; const page = normalizeTitle; if (page.namespace === nsId('special') ||               page.namespace === nsId('media')) { // Don't save virtual namespaces. return; }           const pageName = page.getPrefixedText; const recentsArray = (_a = JSON.parse(window.localStorage.getItem(Recents.key))) !== null && _a !== void 0 ? _a : []; if (recentsArray[0] === pageName) { // Avoid needless operations. return; }           while (recentsArray.indexOf(pageName) !== -1) { recentsArray.splice(recentsArray.indexOf(pageName), 1); }           if (recentsArray.length > 0) { recentsArray.pop; }           recentsArray.splice(0, 0, pageName); window.localStorage.setItem(Recents.key, JSON.stringify(recentsArray)); }       /**         * @return The most recently visited pages. */       static get { return JSON.parse(window.localStorage.getItem(Recents.key)); }   }    Recents.key = 'mw-userjs-recents';

/**    * Applies configuration overrides. This takes two objects, A and B.    * A's keys will be respected and will remain unchanged. Object * values of A that also exist in B will be overwritten with its * values in B.    * * @param data * @param overrides * @param logger */   function applyOverrides(data, overrides, logger) { if (overrides) { for (const category of Object.keys(data)) { if (!overrides[category]) { continue; // Category does not exist. }               for (const categoryKey of Object.keys(overrides[category])) { if (logger) { logger(`${category}.${categoryKey}`, data[category][categoryKey], overrides[category][categoryKey]); }                   data[category][categoryKey] = overrides[category][categoryKey]; }           }            for (const category of Object.keys(overrides)) { if (!data[category]) { data[category] = overrides[category]; if (logger) { logger(`${category}`, data[category], overrides[category]); }               }            }        }    }

/**    * Runs a clean operation. If `option` is false or null, the operation will not be run. *    * @param obj * @param option * @param callback */   function onOption(obj, option, callback) { if (option == null || option === false) { return; }       for (const key of Object.keys(obj)) { if (option === true ||               option === key ||                (Array.isArray(option) && option.indexOf(key) !== -1)) { const result = callback(obj[key]); if (result === undefined) { delete obj[key]; }               else { obj[key] = result; }           }        }    }    /**     * Cleans a parameter list. By default, this performs the following: * - Removes all undefined, null, or empty values * - Trims all strings * - Removes newly undefined, null, or empty values *    * This mutates the original object and also returns it for chaining. *    * @param obj * @param _options * @return The cleaned parameter list. */   function cleanParams(obj, _options = {}) { const defaultOptions = { trim: true, filter: true, removeYes: false, removeNo: false, filter2: true };       const options = Object.assign({}, defaultOptions, _options); // First clean pass onOption(obj, options.filter, (v) => !v || v.length === 0 ? undefined : v); onOption(obj, options.trim, (v) => v.trim ? v.trim : v); onOption(obj, options.removeYes, (v) => yesNo(v, false) ? undefined : v); onOption(obj, options.removeNo, (v) => yesNo(v, true) ? undefined : v); // Second clean pass onOption(obj, options.filter, (v) => !v || v.length === 0 ? undefined : v); return obj; }

/**    * Iterates over an array and returns an Iterator which checks each element * of the array sequentially for a given condition (predicated by `condition`) * and returns another array, containing an element where `true` was returned, * and every subsequent element where the check returns `false`. *    * @param arr * @param condition * @yield The found sequence */   function* pickSequence(arr, condition) { let currentValues = null; let shouldReturnValues = false; for (const val of arr) { if (condition(val)) { shouldReturnValues = true; if (currentValues != null) { yield currentValues; }               currentValues = [val]; continue; }           if (shouldReturnValues) { currentValues.push(val); }       }        if (currentValues.length > 0) { yield currentValues; }   }

/**    * Unwraps an element into its child elements. This entirely discards * the parent element. *    * @param el The element to unwrap. * @return The unwrapped element. */   function unwrapElement (el) { return Array.from(el.childNodes).map(v => v instanceof HTMLElement ? v :           (v instanceof Text ? v.textContent : undefined)).filter(v => v !== undefined); }

var util = { applyOverrides: applyOverrides, blockExit: blockExit$1, classMix: classMix, cleanParams: cleanParams, cloneRegex: cloneRegex$1, copyToClipboard: copyToClipboard, dangerModeConfirm: dangerModeConfirm, equalTitle: equalTitle, error: error, findNextSiblingElement: findNextSiblingElement, fromObjectEntries: fromObjectEntries, generateId: generateId, getObjectValues: getObjectValues, last: last, log: log, matchAll: matchAll, moveToStart: moveToStart, organize: organize, pickSequence: pickSequence, removeElement: removeElement, Requester: Requester, sleep: sleep, swapElements: swapElements, unwrapElement: unwrapElement, unwrapJQ: unwrapJQ, unwrapWidget: unwrapWidget, warn: warn, yesNo: yesNo };

/**    * Finds a MediaWiki section heading from the current DOM using its title. *    * @param sectionHeadingName The name of the section to find. * @param n The `n` of the section. Starts at 1. * @return The found section heading. `null` if not found. */   function findSectionHeading(sectionHeadingName, n = 1) { let currentN = 1; const headlines = Array.from(document.querySelectorAll( // Old style headings [1, 2, 3, 4, 5, 6].map(v => `h${v} > .mw-headline`).join(',') + ',' +           // New style headings [1, 2, 3, 4, 5, 6].map(v => `mw-heading > h${v}`).join(','))); for (const el of headlines) { if (el instanceof HTMLElement && el.innerText === sectionHeadingName) { if (currentN >= n) { return el.parentElement; }               else { currentN++; }           }        }        return null; }

/**    * Converts a range-like Object into a native Range object. *    * @param rangeLike The range to convert * @param rangeLike.startContainer * @param rangeLike.startOffset * @param rangeLike.endContainer * @param rangeLike.endOffset * @return A {@link Range} object. */   function getNativeRange (rangeLike) { const doc = rangeLike.startContainer.ownerDocument; const nativeRange = doc.createRange; nativeRange.setStart(rangeLike.startContainer, rangeLike.startOffset); nativeRange.setEnd(rangeLike.endContainer, rangeLike.endOffset); return nativeRange; }

/**    * From a list of page titles, get which pages exist. *    * @param pages The pages to search for * @return An array of pages which exist, ordered by input order. */   function getPageExists (pages) { var _a; return __awaiter(this, void 0, void 0, function* {            if (!Array.isArray(pages)) {                pages = [pages];            }            const pageNames = pages                .map(p => p instanceof mw.Title ? p.getPrefixedText : p);           const pageRequest = (yield MwApi.action.get({                action: 'query',                titles: pageNames.join('|')            })).query;            const existingPages = [];            if (pageRequest.pages.length > 0) {                const redirects = toRedirectsObject(pageRequest.redirects, pageRequest.normalized);                const pageMap = Object.fromEntries(pageRequest.pages.map((v) => [v.title, !v.missing]));                // Use `pages` to retain client order (assume MW response can be tampered with)                for (const loc of pageNames) {                    const actualLocation = (_a = redirects[loc]) !== null && _a !== void 0 ? _a : loc;                    if (pageMap[actualLocation]) {                        existingPages.push(actualLocation);                    }                }            }            return existingPages; });   }

/**    * Get the content of a revision on-wiki. *    * @param revision The revision ID of the revision to get the content of     * @param extraOptions Extra options to pass to the request * @param api The API object to use * @return A promise resolving to the page content */   function getRevisionContent (revision, extraOptions = {}, api = MwApi.action) { return api.get(Object.assign({ action: 'query', revids: revision, rvprop: 'content', rvslots: 'main', rvlimit: '1' }, extraOptions)).then((data) => {           return Object.assign(data.query.pages[0].revisions[0].slots.main.content, { contentFormat: data.query.pages[0].revisions[0].slots.main.contentformat });        }); }

var wikiUtil = { decorateEditSummary: decorateEditSummary, delink: delink, errorToOO: errorToOO, findSectionHeading: findSectionHeading, getApiErrorText: getApiErrorText, getNativeRange: getNativeRange, getPageContent: getPageContent, getPageExists: getPageExists, getPageTitle: getPageTitle, getRevisionContent: getRevisionContent, getRevisionDiffURL: getRevisionDiffURL, getRevisionURL: getRevisionURL, getSectionElements: getSectionElements, getSectionHTML: getSectionHTML, getSectionId: getSectionId, guessAuthor: guessAuthor, isWikiHeading: isWikiHeading, msgEval: msgEval, normalizeTitle: normalizeTitle, normalizeWikiHeading: normalizeWikiHeading, nsId: nsId, openWindow: openWindow, pagelinkToTitle: pagelinkToTitle, parseDiffUrl: parseDiffUrl, performHacks: performHacks, purge: purge, renderWikitext: renderWikitext, sectionHeadingN: sectionHeadingN, toRedirectsObject: toRedirectsObject };

var deputyAnnouncementsEnglish = { "deputy.announcement.template.title": "Announcement title", "deputy.announcement.template.message": "Announcement message", "deputy.announcement.template.actionButton.label": "Button label", "deputy.announcement.template.actionButton.title": "Button title" };

/**    *     * Deputy announcements *    * This will be loaded on all standalone modules and on main Deputy. * Be conservative with what you load! *    */    class DeputyAnnouncements { /**        * Initialize announcements. * @param config */       static init(config) { return __awaiter(this, void 0, void 0, function* {                yield Promise.all([ DeputyLanguage.load('shared', deputySharedEnglish), DeputyLanguage.load('announcements', deputyAnnouncementsEnglish) ]);               mw.util.addCSS('#siteNotice .deputy { text-align: left; }');                for (const [id, announcements] of Object.entries(this.knownAnnouncements)) {                    if (config.core.seenAnnouncements.get.includes(id)) {                        continue;                    }                    if (announcements.expiry && (announcements.expiry < new Date)) {                        // Announcement has expired. Skip it.                        continue;                    }                    this.showAnnouncement(config, id, announcements);                }            }); }       /**         *         * @param config * @param announcementId * @param announcement */       static showAnnouncement(config, announcementId, announcement) { mw.loader.using([               'oojs-ui-core',                'oojs-ui.styles.icons-interactions'            ],  => {                const messageWidget = DeputyMessageWidget({ classes: ['deputy'], icon: 'feedback', // Messages that can be used here: // * deputy.announcement..title title: mw.msg(`deputy.announcement.${announcementId}.title`), // Messages that can be used here: // * deputy.announcement..message message: mw.msg(`deputy.announcement.${announcementId}.message`), closable: true, actions: announcement.actions.map(action => {                       var _a;                        const button = new OO.ui.ButtonWidget({ // Messages that can be used here: // * deputy.announcement.. .message label: mw.msg(`deputy.announcement.${announcementId}.${action.id}.label`), // Messages that can be used here: // * deputy.announcement.. .title title: mw.msg(`deputy.announcement.${announcementId}.${action.id}.title`), flags: (_a = action.flags) !== null && _a !== void 0 ? _a : [] });                       button.on('click', action.action);                        return button;                    }) });               messageWidget.on('close',  => { config.core.seenAnnouncements.set([...config.core.seenAnnouncements.get, announcementId]); config.save; });               document.getElementById('siteNotice').appendChild(unwrapWidget(messageWidget));            }); }   }    DeputyAnnouncements.knownAnnouncements = { // No active announcements // 'announcementId': { // 	actions: [ // 		{   // 			id: 'actionButton', // 			flags: [ 'primary', 'progressive' ], // 			action: => { /* do something */ } // 		}   // 	]    // }    };

/**    * The main class for Deputy. Entry point for execution. *    * This class is not exported to avoid circular references and extraneous * export code in the Rollup bundle (unnecessary for a userscript). */   class Deputy { /**        * Private constructor. To access Deputy, use `window.deputy` or Deputy.instance. */       constructor { this.DeputyDispatch = Dispatch; this.DeputyStorage = DeputyStorage; this.DeputySession = DeputySession; this.DeputyCommunications = DeputyCommunications; this.DeputyCase = DeputyCase; this.DeputyCasePage = DeputyCasePage; this.models = { ContributionSurveyRow: ContributionSurveyRow };           this.util = util; this.wikiUtil = wikiUtil; this.modules = { CopiedTemplateEditor: CopiedTemplateEditor, InfringementAssistant: InfringementAssistant };           /**             * This version of Deputy. */           this.version = version; /**            * The current page as an mw.Title. */           this.currentPage = new mw.Title(mw.config.get('wgPageName')); /**            * The current page ID. */           this.currentPageId = mw.config.get('wgArticleId'); // Modules /**            * CopiedTemplateEditor instance. */           this.ante = new CopiedTemplateEditor(this); this.ia = new InfringementAssistant(this); /* ignored */ }       /**         * @return An OOUI window manager */       get windowManager { if (!this._windowManager) { this._windowManager = new OO.ui.WindowManager; document.body.appendChild(unwrapWidget(this._windowManager)); }           return this._windowManager; }       /**         * Initialize Deputy. This static function attaches Deputy to the `window.deputy` * object and initializes that instance. */       static init { return __awaiter(this, void 0, void 0, function* {                Deputy.instance = new Deputy;                window.deputy = Deputy.instance;                return window.deputy.init;            }); }       /**         * Initializes Deputy. By this point, the loader should have succeeded in loading * all dependencies required for Deputy to work. It's only a matter of initializing * sub-components as well. */       init { return __awaiter(this, void 0, void 0, function* {                // Attach modules to respective names                window.CopiedTemplateEditor = this.ante;                window.InfringementAssistant = this.ia;                mw.hook('deputy.preload').fire(this);                // Initialize the configuration                this.config = UserConfiguration.load;                window.deputyLang = this.config.core.language.get;                // Inject CSS                mw.util.addCSS(deputyStyles);                // Load strings                yield Promise.all([ DeputyLanguage.load('core', deputyCoreEnglish), DeputyLanguage.load('shared', deputySharedEnglish), DeputyLanguage.loadMomentLocale ]);               mw.hook('deputy.i18nDone').fire(this);                yield attachConfigurationDialogPortletLink;                // Initialize the storage.                this.storage = new DeputyStorage;                yield this.storage.init;                // Initialize the Deputy API interface                this.dispatch = Dispatch.i;                // Initialize communications                this.comms = new DeputyCommunications;                this.comms.init;                // Initialize session                this.session = new DeputySession;                if (this.config.core.modules.get.indexOf('cci') !== -1) {                    yield this.session.init;                }                // Load modules                if (this.config.core.modules.get.indexOf('ante') !== -1) {                    yield this.ante.preInit;                }                if (this.config.core.modules.get.indexOf('ia') !== -1) { yield this.ia.preInit; }               yield this.wikiConfig.prepareEditBanners; log('Loaded!'); mw.hook('deputy.load').fire(this); // Perform post-load tasks. yield Promise.all([                   // Show announcements (if any)                    yield DeputyAnnouncements.init(this.config),                    // Asynchronously reload wiki configuration.                    this.wikiConfig.update.catch( => { })                ]); });       }        /**         * Gets the wiki-specific configuration for Deputy.         *         * @return A promise resolving to the loaded configuration         */        getWikiConfig {            var _a;            return __awaiter(this, void 0, void 0, function*  { return (_a = this.wikiConfig) !== null && _a !== void 0 ? _a : (this.wikiConfig = yield WikiConfiguration.load); });       }    }    mw.loader.using([ 'mediawiki.api', 'mediawiki.jqueryMsg', 'mediawiki.Title', 'mediawiki.util', 'moment', 'oojs' ], function { Recents.save; performHacks; Deputy.init; });

}); // // <3