User:Chlod/Scripts/Deputy/InfringementAssistant.js

From Wikipedia, the free encyclopedia
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/*!
 * 
 *    INFRINGEMENT ASSISTANT
 * 
 *    Send pages to the copyright problems noticeboard and manage existing
 *    noticeboard entries
 * 
 *    ------------------------------------------------------------------------
 * 
 *    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
 * 
 */
// <nowiki>
/*!
 * @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;
    };

    /**
     * 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) {
            console.error(el);
            throw new Error('Element is not an OOUI Element!');
        }
        return el.$element[0];
    }

    /**
     * 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.
     * @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;
        }
    }

    var version = "0.7.1";
    var gitAbbrevHash = "06d1325";
    var gitBranch = "HEAD";
    var gitDate = "Wed, 24 Apr 2024 10:41:01 +0800";
    var gitVersion = "0.7.1+g06d1325";

    /**
     *
     */
    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)`;

    /**
     * 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]
            });
        });
    }

    /**
     * Handles resource fetching operations.
     */
    class DeputyResources {
        /**
         * Loads a resource from the provided resource root.
         *
         * @param path A path relative to the resource root.
         * @return A Promise that resolves to the resource's content as a UTF-8 string.
         */
        static loadResource(path) {
            return __awaiter(this, void 0, void 0, function* () {
                switch (this.root.type) {
                    case 'url': {
                        const headers = new Headers();
                        headers.set('Origin', window.location.origin);
                        return fetch((new URL(path, this.root.url)).href, {
                            method: 'GET',
                            headers
                        }).then((r) => r.text());
                    }
                    case 'wiki': {
                        this.assertApi();
                        return getPageContent(this.root.prefix.replace(/\/$/, '') + '/' + path, {}, this.api);
                    }
                }
            });
        }
        /**
         * Ensures that `this.api` is a valid ForeignApi.
         */
        static assertApi() {
            if (this.root.type !== 'wiki') {
                return;
            }
            if (!this.api) {
                this.api = new mw.ForeignApi(this.root.wiki.toString(), {
                    // Force anonymous mode. Deputy doesn't need user data anyway,
                    // so this should be fine.
                    anonymous: true
                });
            }
        }
    }
    /**
     * The root of all Deputy resources. This should serve static data that Deputy will
     * use to load resources such as language files.
     */
    DeputyResources.root = {
        type: 'url',
        url: new URL('https://zoomiebot.toolforge.org/deputy/')
    };

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

    /**
     * Handles internationalization and localization for Deputy and sub-modules.
     */
    class DeputyLanguage {
        /**
         * Loads the language for this Deputy interface.
         *
         * @param module The module to load a language pack for.
         * @param fallback A fallback language pack to load. Since this is an actual
         * `Record`, this relies on the language being bundled with the userscript. This ensures
         * that a language pack is always available, even if a language file could not be loaded.
         */
        static load(module, fallback) {
            var _a;
            return __awaiter(this, void 0, void 0, function* () {
                const lang = (_a = window.deputyLang) !== null && _a !== void 0 ? _a : 'en';
                // The loaded language resource file. Forced to `null` if using English, since English
                // is our fallback language.
                const langResource = lang === 'en' ? null :
                    yield DeputyResources.loadResource(`i18n/${module}/${lang}.json`)
                        .catch(() => {
                        // Could not find requested language file.
                        return null;
                    });
                if (!langResource) {
                    // Fall back.
                    for (const key in fallback) {
                        mw.messages.set(key, fallback[key]);
                    }
                    return;
                }
                try {
                    const langData = JSON.parse(langResource);
                    for (const key in langData) {
                        mw.messages.set(key, langData[key]);
                    }
                }
                catch (e) {
                    console.error(e);
                    mw.notify(
                    // No languages to fall back on. Do not translate this string.
                    'Deputy: Requested language page is not a valid JSON file.', { type: 'error' });
                    // Fall back.
                    for (const key in fallback) {
                        mw.messages.set(key, fallback[key]);
                    }
                }
                if (lang !== mw.config.get('wgUserLanguage')) {
                    yield DeputyLanguage.loadSecondary();
                }
            });
        }
        /**
         * Loads a specific moment.js locale. It's possible for nothing to be loaded (e.g. if the
         * locale is not supported by moment.js), in which case nothing happens and English is
         * likely used.
         *
         * @param locale The locale to load. `window.deputyLang` by default.
         */
        static loadMomentLocale(locale = window.deputyLang) {
            return __awaiter(this, void 0, void 0, function* () {
                if (locale === 'en') {
                    // Always loaded.
                    return;
                }
                if (mw.loader.getState('moment') !== 'ready') {
                    // moment.js is not yet loaded.
                    console.warn('Deputy tried loading moment.js locales but moment.js is not yet ready.');
                    return;
                }
                if (window.moment.locales().indexOf(locale) !== -1) {
                    // Already loaded.
                    return;
                }
                yield mw.loader.using('moment')
                    .then(() => true, () => null);
                yield mw.loader.getScript(new URL(`resources/lib/moment/locale/${locale}.js`, new URL(mw.util.wikiScript('index'), window.location.href)).href).then(() => true, () => null);
            });
        }
        /**
         * There are times when the user interface language do not match the wiki content
         * language. Since Deputy's edit summary and content strings are found in the
         * i18n files, however, there are cases where the wrong language would be used.
         *
         * This solves this problem by manually overriding content-specific i18n keys with
         * the correct language. By default, all keys that match `deputy.*.content.**` get
         * overridden.
         *
         * There are no fallbacks for this. If it fails, the user interface language is
         * used anyway. In the event that the user interface language is not English,
         * this will cause awkward situations. Whether or not something should be done to
         * catch this specific edge case will depend on how frequent it happens.
         *
         * @param locale
         * @param match
         */
        static loadSecondary(locale = mw.config.get('wgContentLanguage'), match = /^deputy\.(?:[^.]+)?\.content\./g) {
            return __awaiter(this, void 0, void 0, function* () {
                // The loaded language resource file. Forced to `null` if using English, since English
                // is our fallback language.
                const langResource = locale === 'en' ? null :
                    yield DeputyResources.loadResource(`i18n/${module}/${locale}.json`)
                        .catch(() => {
                        // Could not find requested language file.
                        return null;
                    });
                if (!langResource) {
                    return;
                }
                try {
                    const langData = JSON.parse(langResource);
                    for (const key in langData) {
                        if (cloneRegex(match).test(key)) {
                            mw.messages.set(key, langData[key]);
                        }
                    }
                }
                catch (e) {
                    // Silent failure.
                    console.error('Deputy: Requested language page is not a valid JSON file.', e);
                }
            });
        }
    }

    /**
     * Works like `Object.values`.
     *
     * @param obj The object to get the values of.
     * @return The values of the given object as an array
     */
    function getObjectValues(obj) {
        return Object.keys(obj).map((key) => obj[key]);
    }

    /**
     * Refers to a specific setting on the configuration. Should be initialized with
     * a raw (serialized) type and an actual (deserialized) type.
     *
     * This is used for both client and wiki-wide configuration.
     */
    class Setting {
        /**
         *
         * @param options
         * @param options.serialize Serialization function. See {@link Setting#serialize}
         * @param options.deserialize Deserialization function. See {@link Setting#deserialize}
         * @param options.alwaysSave See {@link Setting#alwaysSave}.
         * @param options.defaultValue Default value. If not supplied, `undefined` is used.
         * @param options.displayOptions See {@link Setting#displayOptions}
         * @param options.allowedValues See {@link Setting#allowedValues}
         */
        constructor(options) {
            var _a, _b;
            this.serialize = options.serialize;
            this.deserialize = options.deserialize;
            this.displayOptions = options.displayOptions;
            this.allowedValues = options.allowedValues;
            this.value = this.defaultValue = options.defaultValue;
            this.alwaysSave = options.alwaysSave;
            this.isDisabled = ((_a = options.displayOptions) === null || _a === void 0 ? void 0 : _a.disabled) != null ?
                (typeof options.displayOptions.disabled === 'function' ?
                    options.displayOptions.disabled.bind(this) :
                    () => options.displayOptions.disabled) : () => false;
            this.isHidden = ((_b = options.displayOptions) === null || _b === void 0 ? void 0 : _b.hidden) != null ?
                (typeof options.displayOptions.hidden === 'function' ?
                    options.displayOptions.hidden.bind(this) :
                    () => options.displayOptions.hidden) : () => false;
        }
        /**
         * @return `true` if `this.value` is not null or undefined.
         */
        ok() {
            return this.value != null;
        }
        /**
         * @return The current value of this setting.
         */
        get() {
            return this.value;
        }
        /**
         * Sets the value and performs validation. If the input is an invalid value, and
         * `throwOnInvalid` is false, the value will be reset to default.
         *
         * @param v
         * @param throwOnInvalid
         */
        set(v, throwOnInvalid = false) {
            if (this.locked) {
                console.warn('Attempted to modify locked setting.');
                return;
            }
            if (this.allowedValues) {
                const keys = Array.isArray(this.allowedValues) ?
                    this.allowedValues : getObjectValues(this.allowedValues);
                if (Array.isArray(v)) {
                    if (v.some((v1) => keys.indexOf(v1) === -1)) {
                        if (throwOnInvalid) {
                            throw new Error('Invalid value');
                        }
                        v = this.value;
                    }
                }
                else {
                    if (this.allowedValues && keys.indexOf(v) === -1) {
                        if (throwOnInvalid) {
                            throw new Error('Invalid value');
                        }
                        v = this.value;
                    }
                }
            }
            this.value = v;
        }
        /**
         * Resets this setting to its original value.
         */
        reset() {
            this.set(this.defaultValue);
        }
        /**
         * Parses a given raw value and mutates the setting.
         *
         * @param raw The raw value to parse.
         * @return The new value.
         */
        load(raw) {
            return (this.value = this.deserialize(raw));
        }
        /**
         * Prevents the value of the setting from being changed. Used for debugging.
         */
        lock() {
            this.locked = true;
        }
        /**
         * Allows the value of the setting to be changed. Used for debugging.
         */
        unlock() {
            this.locked = false;
        }
    }
    Setting.basicSerializers = {
        serialize: (value) => value,
        deserialize: (value) => value
    };

    /**
     * Works like `Object.fromEntries`
     *
     * @param obj The object to get the values of.
     * @return The values of the given object as an array
     */
    function fromObjectEntries(obj) {
        const i = {};
        for (const [key, value] of obj) {
            i[key] = value;
        }
        return i;
    }

    /**
     * Generates configuration properties for serialized <b>string</b> enums.
     *
     * Trying to use anything that isn't a string enum here (union enum, numeral enum)
     * will likely cause serialization/deserialization failures.
     *
     * @param _enum
     * @param defaultValue
     * @return Setting properties.
     */
    function generateEnumConfigurationProperties(_enum, defaultValue) {
        return {
            serialize: (value) => value === defaultValue ? undefined : value,
            deserialize: (value) => value,
            displayOptions: {
                type: 'radio'
            },
            allowedValues: fromObjectEntries(Array.from(new Set(Object.keys(_enum)).values())
                .map((v) => [_enum[v], _enum[v]])),
            defaultValue: defaultValue
        };
    }
    var PortletNameView;
    (function (PortletNameView) {
        PortletNameView["Full"] = "full";
        PortletNameView["Short"] = "short";
        PortletNameView["Acronym"] = "acronym";
    })(PortletNameView || (PortletNameView = {}));

    var CompletionAction;
    (function (CompletionAction) {
        CompletionAction["Nothing"] = "nothing";
        CompletionAction["Reload"] = "reload";
    })(CompletionAction || (CompletionAction = {}));
    var TripleCompletionAction;
    (function (TripleCompletionAction) {
        TripleCompletionAction["Nothing"] = "nothing";
        TripleCompletionAction["Reload"] = "reload";
        TripleCompletionAction["Redirect"] = "redirect";
    })(TripleCompletionAction || (TripleCompletionAction = {}));

    /**
     * A configuration. Defines settings and setting groups.
     */
    class ConfigurationBase {
        // eslint-disable-next-line jsdoc/require-returns-check
        /**
         * @return the configuration from the current wiki.
         */
        static load() {
            throw new Error('Unimplemented method.');
        }
        /**
         * Creates a new Configuration.
         */
        constructor() { }
        /**
         * Deserializes a JSON configuration into this configuration. This WILL overwrite
         * past settings.
         *
         * @param serializedData
         */
        deserialize(serializedData) {
            var _a;
            for (const group in this.all) {
                const groupObject = this.all[group];
                for (const key in this.all[group]) {
                    const setting = groupObject[key];
                    if (((_a = serializedData === null || serializedData === void 0 ? void 0 : serializedData[group]) === null || _a === void 0 ? void 0 : _a[key]) !== undefined) {
                        setting.set(setting.deserialize ?
                            // Type-checked upon declaration, just trust it to skip errors.
                            setting.deserialize(serializedData[group][key]) :
                            serializedData[group][key]);
                    }
                }
            }
        }
        /**
         * @return the serialized version of the configuration. All `undefined` values are stripped
         * from output. If a category remains unchanged from defaults, it is skipped. If the entire
         * configuration remains unchanged, `null` is returned.
         */
        serialize() {
            const config = {};
            for (const group of Object.keys(this.all)) {
                const groupConfig = {};
                const groupObject = this.all[group];
                for (const key in this.all[group]) {
                    const setting = groupObject[key];
                    if (setting.get() === setting.defaultValue && !setting.alwaysSave) {
                        continue;
                    }
                    const serialized = setting.serialize ?
                        // Type-checked upon declaration, just trust it to skip errors.
                        setting.serialize(setting.get()) : setting.get();
                    if (serialized !== undefined) {
                        groupConfig[key] = serialized;
                    }
                }
                if (Object.keys(groupConfig).length > 0) {
                    config[group] = groupConfig;
                }
            }
            if (Object.keys(config).length > 0) {
                return config;
            }
            else {
                return null;
            }
        }
    }

    var ContributionSurveyRowSigningBehavior;
    (function (ContributionSurveyRowSigningBehavior) {
        ContributionSurveyRowSigningBehavior["Always"] = "always";
        ContributionSurveyRowSigningBehavior["AlwaysTrace"] = "alwaysTrace";
        ContributionSurveyRowSigningBehavior["AlwaysTraceLastOnly"] = "alwaysTraceLastOnly";
        ContributionSurveyRowSigningBehavior["LastOnly"] = "lastOnly";
        ContributionSurveyRowSigningBehavior["Never"] = "never";
    })(ContributionSurveyRowSigningBehavior || (ContributionSurveyRowSigningBehavior = {}));

    /**
     * A configuration. Defines settings and setting groups.
     */
    class UserConfiguration extends ConfigurationBase {
        /**
         * Creates a new Configuration.
         *
         * @param serializedData
         */
        constructor(serializedData = {}) {
            var _a;
            super();
            this.core = {
                /**
                 * Numerical code that identifies this config version. Increments for every breaking
                 * configuration file change.
                 */
                configVersion: new Setting({
                    defaultValue: UserConfiguration.configVersion,
                    displayOptions: { hidden: true },
                    alwaysSave: true
                }),
                language: new Setting({
                    defaultValue: mw.config.get('wgUserLanguage'),
                    displayOptions: { type: 'select' }
                }),
                modules: new Setting({
                    defaultValue: ['cci', 'ante', 'ia'],
                    displayOptions: { type: 'checkboxes' },
                    allowedValues: ['cci', 'ante', 'ia']
                }),
                portletNames: new Setting(generateEnumConfigurationProperties(PortletNameView, PortletNameView.Full)),
                seenAnnouncements: new Setting({
                    defaultValue: [],
                    displayOptions: { hidden: true }
                }),
                dangerMode: new Setting({
                    defaultValue: false,
                    displayOptions: {
                        type: 'checkbox'
                    }
                })
            };
            this.cci = {
                enablePageToolbar: new Setting({
                    defaultValue: true,
                    displayOptions: {
                        type: 'checkbox'
                    }
                }),
                showCvLink: new Setting({
                    defaultValue: true,
                    displayOptions: {
                        type: 'checkbox'
                    }
                }),
                showUsername: new Setting({
                    defaultValue: false,
                    displayOptions: {
                        type: 'checkbox'
                    }
                }),
                autoCollapseRows: new Setting({
                    defaultValue: false,
                    displayOptions: {
                        type: 'checkbox'
                    }
                }),
                autoShowDiff: new Setting({
                    defaultValue: false,
                    displayOptions: {
                        type: 'checkbox'
                    }
                }),
                maxRevisionsToAutoShowDiff: new Setting({
                    defaultValue: 2,
                    displayOptions: {
                        type: 'number',
                        // Force any due to self-reference
                        disabled: (config) => !config.cci.autoShowDiff.get(),
                        extraOptions: {
                            min: 1
                        }
                    }
                }),
                maxSizeToAutoShowDiff: new Setting({
                    defaultValue: 500,
                    displayOptions: {
                        type: 'number',
                        // Force any due to self-reference
                        disabled: (config) => !config.cci.autoShowDiff.get(),
                        extraOptions: {
                            min: -1
                        }
                    }
                }),
                forceUtc: new Setting({
                    defaultValue: false,
                    displayOptions: {
                        type: 'checkbox'
                    }
                }),
                signingBehavior: new Setting(generateEnumConfigurationProperties(ContributionSurveyRowSigningBehavior, ContributionSurveyRowSigningBehavior.Always)),
                signSectionArchive: new Setting({
                    defaultValue: true,
                    displayOptions: {
                        type: 'checkbox'
                    }
                }),
                openOldOnContinue: new Setting({
                    defaultValue: false,
                    displayOptions: {
                        type: 'checkbox'
                    }
                })
            };
            this.ante = {
                enableAutoMerge: new Setting({
                    defaultValue: false,
                    displayOptions: {
                        type: 'checkbox',
                        disabled: 'unimplemented'
                    }
                }),
                onSubmit: new Setting(generateEnumConfigurationProperties(CompletionAction, CompletionAction.Reload))
            };
            this.ia = {
                responses: new Setting(Object.assign(Object.assign({}, Setting.basicSerializers), { defaultValue: null, displayOptions: {
                        disabled: 'unimplemented',
                        type: 'unimplemented'
                    } })),
                enablePageToolbar: new Setting({
                    defaultValue: true,
                    displayOptions: {
                        type: 'checkbox',
                        disabled: 'unimplemented'
                    }
                }),
                defaultEntirePage: new Setting({
                    defaultValue: true,
                    displayOptions: {
                        type: 'checkbox'
                    }
                }),
                defaultFromUrls: new Setting({
                    defaultValue: true,
                    displayOptions: {
                        type: 'checkbox'
                    }
                }),
                onHide: new Setting(generateEnumConfigurationProperties(TripleCompletionAction, TripleCompletionAction.Reload)),
                onSubmit: new Setting(generateEnumConfigurationProperties(TripleCompletionAction, TripleCompletionAction.Reload)),
                onBatchSubmit: new Setting(generateEnumConfigurationProperties(CompletionAction, CompletionAction.Reload))
            };
            this.type = 'user';
            this.all = { core: this.core, cci: this.cci, ante: this.ante, ia: this.ia };
            if (serializedData) {
                this.deserialize(serializedData);
            }
            if (mw.storage.get(`mw-${UserConfiguration.optionKey}-lastVersion`) !== version) ;
            mw.storage.set(`mw-${UserConfiguration.optionKey}-lastVersion`, version);
            if ((_a = window.deputy) === null || _a === void 0 ? void 0 : _a.comms) {
                window.deputy.comms.addEventListener('userConfigUpdate', (e) => {
                    // Update the configuration based on another tab's message.
                    this.deserialize(e.data.config);
                });
            }
        }
        /**
         * @return the configuration from the current wiki.
         */
        static load() {
            const config = new UserConfiguration();
            try {
                if (mw.user.options.get(UserConfiguration.optionKey)) {
                    const decodedOptions = JSON.parse(mw.user.options.get(UserConfiguration.optionKey));
                    config.deserialize(decodedOptions);
                }
            }
            catch (e) {
                console.error(e, mw.user.options.get(UserConfiguration.optionKey));
                mw.hook('deputy.i18nDone').add(function notifyConfigFailure() {
                    mw.notify(mw.msg('deputy.loadError.userConfig'), {
                        type: 'error'
                    });
                    mw.hook('deputy.i18nDone').remove(notifyConfigFailure);
                });
                config.save();
            }
            return config;
        }
        /**
         * Saves the configuration.
         */
        save() {
            return __awaiter(this, void 0, void 0, function* () {
                yield MwApi.action.saveOption(UserConfiguration.optionKey, JSON.stringify(this.serialize()));
            });
        }
    }
    UserConfiguration.configVersion = 1;
    UserConfiguration.optionKey = 'userjs-deputy';

    var deputySettingsStyles = ".deputy-setting {margin-bottom: 1em;}.deputy-setting > .oo-ui-fieldLayout-align-top .oo-ui-fieldLayout-header .oo-ui-labelElement-label {font-weight: bold;}.dp-mb {margin-bottom: 1em;}.deputy-about {display: flex;}.deputy-about > :first-child {flex: 0;}.deputy-about > :first-child > img {height: 5em;width: auto;}.ltr .deputy-about > :first-child {margin-right: 1em;}.rtl .deputy-about > :first-child {margin-left: 1em;}.deputy-about > :nth-child(2) {flex: 1;}.deputy-about > :nth-child(2) > :first-child > * {display: inline;}.deputy-about > :nth-child(2) > :first-child > :first-child {font-weight: bold;font-size: 2em;}.deputy-about > :nth-child(2) > :first-child > :nth-child(2) {color: gray;vertical-align: bottom;margin-left: 0.4em;}.deputy-about > :nth-child(2) > :not(:first-child) {margin-top: 0.5em;}.ltr .deputy-about + div > :not(:last-child) {margin-right: 0.5em;}.rtl .deputy-about + div > :not(:last-child) {margin-left: 0.5em;}.ltr .deputy-about + div {text-align: right;}.rtl .deputy-about + div {text-align: left;}";

    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;

    /* eslint-disable mediawiki/msg-doc */
    let InternalConfigurationGroupTabPanel$1;
    /**
     * Initializes the process element.
     */
    function initConfigurationGroupTabPanel$1() {
        InternalConfigurationGroupTabPanel$1 = class ConfigurationGroupTabPanel extends OO.ui.TabPanelLayout {
            /**
             * @param config Configuration to be passed to the element.
             */
            constructor(config) {
                super(`configurationGroupPage_${config.group}`);
                this.config = config;
                this.mode = config.config instanceof UserConfiguration ? 'user' : 'wiki';
                if (this.mode === 'wiki') {
                    this.$element.append(new OO.ui.MessageWidget({
                        classes: [
                            'deputy', 'dp-mb'
                        ],
                        type: 'warning',
                        label: mw.msg('deputy.settings.dialog.wikiConfigWarning')
                    }).$element);
                }
                for (const settingKey of Object.keys(this.settings)) {
                    const setting = this.settings[settingKey];
                    if (setting.isHidden(this.config.config)) {
                        continue;
                    }
                    switch (setting.displayOptions.type) {
                        case 'checkbox':
                            this.$element.append(this.newCheckboxField(settingKey, setting));
                            break;
                        case 'checkboxes':
                            this.$element.append(this.newCheckboxesField(settingKey, setting));
                            break;
                        case 'radio':
                            this.$element.append(this.newRadioField(settingKey, setting));
                            break;
                        case 'text':
                            this.$element.append(this.newStringField(settingKey, setting, setting.displayOptions.extraOptions));
                            break;
                        case 'number':
                            this.$element.append(this.newNumberField(settingKey, setting, setting.displayOptions.extraOptions));
                            break;
                        case 'page':
                            this.$element.append(this.newPageField(settingKey, setting, setting.displayOptions.extraOptions));
                            break;
                        case 'code':
                            this.$element.append(this.newCodeField(settingKey, setting, setting.displayOptions.extraOptions));
                            break;
                        default:
                            this.$element.append(this.newUnimplementedField(settingKey));
                            break;
                    }
                }
                this.on('change', () => {
                    console.log(this.config.config);
                    console.log(this.config.config.serialize());
                });
            }
            /**
             * @return The {@Link Setting}s for this group.
             */
            get settings() {
                return this.config.config.all[this.config.group];
            }
            /**
             * Sets up the tab item
             */
            setupTabItem() {
                this.tabItem.setLabel(this.getMsg(this.config.group));
                return this;
            }
            /**
             * @return the i18n message for this setting tab.
             *
             * @param messageKey
             */
            getMsg(messageKey) {
                return mw.msg(`deputy.setting.${this.mode}.${messageKey}`);
            }
            /**
             * Gets the i18n message for a given setting.
             *
             * @param settingKey
             * @param key
             * @return A localized string
             */
            getSettingMsg(settingKey, key) {
                return this.getMsg(`${this.config.group}.${settingKey}.${key}`);
            }
            /**
             * @param settingKey
             * @param allowedValues
             * @return a tuple array of allowed values that can be used in OOUI `items` parameters.
             */
            getAllowedValuesArray(settingKey, allowedValues) {
                const items = [];
                if (Array.isArray(allowedValues)) {
                    for (const key of allowedValues) {
                        const message = mw.message(`deputy.setting.${this.mode}.${this.config.group}.${settingKey}.${key}`);
                        items.push([key, message.exists() ? message.text() : key]);
                    }
                }
                else {
                    for (const key of Object.keys(allowedValues)) {
                        const message = mw.message(`deputy.setting.${this.mode}.${this.config.group}.${settingKey}.${key}`);
                        items.push([key, message.exists() ? message.text() : key]);
                    }
                }
                return items;
            }
            /**
             * Creates an unimplemented setting notice.
             *
             * @param settingKey
             * @return An HTMLElement of the given setting's field.
             */
            newUnimplementedField(settingKey) {
                const desc = mw.message(`deputy.setting.${this.mode}.${this.config.group}.${settingKey}.description`);
                return h_1("div", { class: "deputy-setting" },
                    h_1("b", null, this.getSettingMsg(settingKey, 'name')),
                    desc.exists() ? h_1("p", { style: { fontSize: '0.925em', color: '#54595d' } }, desc.text()) : '',
                    h_1("p", null, mw.msg('deputy.settings.dialog.unimplemented')));
            }
            /**
             * Creates a checkbox field.
             *
             * @param settingKey
             * @param setting
             * @return An HTMLElement of the given setting's field.
             */
            newCheckboxField(settingKey, setting) {
                const isDisabled = setting.isDisabled(this.config.config);
                const desc = mw.message(`deputy.setting.${this.mode}.${this.config.group}.${settingKey}.description`);
                const field = new OO.ui.CheckboxInputWidget({
                    selected: setting.get(),
                    disabled: isDisabled !== undefined && isDisabled !== false
                });
                const layout = new OO.ui.FieldLayout(field, {
                    align: 'inline',
                    label: this.getSettingMsg(settingKey, 'name'),
                    help: typeof isDisabled === 'string' ?
                        this.getSettingMsg(settingKey, isDisabled) :
                        desc.exists() ? desc.text() : undefined,
                    helpInline: true
                });
                field.on('change', () => {
                    setting.set(field.isSelected());
                    this.emit('change');
                });
                // Attach disabled re-checker
                this.on('change', () => {
                    field.setDisabled(!!setting.isDisabled(this.config.config));
                });
                return h_1("div", { class: "deputy-setting" }, unwrapWidget(layout));
            }
            /**
             * Creates a new checkbox set field.
             *
             * @param settingKey
             * @param setting
             * @return An HTMLElement of the given setting's field.
             */
            newCheckboxesField(settingKey, setting) {
                const isDisabled = setting.isDisabled(this.config.config);
                const desc = mw.message(`deputy.setting.${this.mode}.${this.config.group}.${settingKey}.description`);
                const field = new OO.ui.CheckboxMultiselectInputWidget({
                    value: setting.get(),
                    disabled: isDisabled !== undefined && isDisabled !== false,
                    options: this.getAllowedValuesArray(settingKey, setting.allowedValues)
                        .map(([key, label]) => ({ data: key, label }))
                });
                const layout = new OO.ui.FieldLayout(field, {
                    align: 'top',
                    label: this.getSettingMsg(settingKey, 'name'),
                    help: typeof isDisabled === 'string' ?
                        this.getSettingMsg(settingKey, isDisabled) :
                        desc.exists() ? desc.text() : undefined,
                    helpInline: true
                });
                // TODO: @types/oojs-ui limitation
                field.on('change', (items) => {
                    const finalData = Array.isArray(setting.allowedValues) ?
                        items :
                        field.getValue().map((v) => setting.allowedValues[v]);
                    setting.set(finalData);
                    this.emit('change');
                });
                // Attach disabled re-checker
                this.on('change', () => {
                    field.setDisabled(!!setting.isDisabled(this.config.config));
                });
                return h_1("div", { class: "deputy-setting" }, unwrapWidget(layout));
            }
            /**
             * Creates a new radio set field.
             *
             * @param settingKey
             * @param setting
             * @return An HTMLElement of the given setting's field.
             */
            newRadioField(settingKey, setting) {
                var _a;
                const isDisabled = setting.isDisabled(this.config.config);
                const desc = mw.message(`deputy.setting.${this.mode}.${this.config.group}.${settingKey}.description`);
                const field = new OO.ui.RadioSelectWidget({
                    disabled: isDisabled !== undefined && isDisabled !== false &&
                        !((_a = setting.displayOptions.readOnly) !== null && _a !== void 0 ? _a : false),
                    items: this.getAllowedValuesArray(settingKey, setting.allowedValues)
                        .map(([key, label]) => new OO.ui.RadioOptionWidget({
                        data: key,
                        label: label,
                        selected: setting.get() === key
                    })),
                    multiselect: false
                });
                const layout = new OO.ui.FieldLayout(field, {
                    align: 'top',
                    label: this.getSettingMsg(settingKey, 'name'),
                    help: typeof isDisabled === 'string' ?
                        this.getSettingMsg(settingKey, isDisabled) :
                        desc.exists() ? desc.text() : undefined,
                    helpInline: true
                });
                // OOUIRadioInputWidget
                field.on('select', (items) => {
                    const finalData = Array.isArray(setting.allowedValues) ?
                        items.data :
                        setting.allowedValues[items.data];
                    setting.set(finalData);
                    this.emit('change');
                });
                // Attach disabled re-checker
                this.on('change', () => {
                    field.setDisabled(!!setting.isDisabled(this.config.config));
                });
                return h_1("div", { class: "deputy-setting" }, unwrapWidget(layout));
            }
            /**
             * Creates a new field that acts like a string field.
             *
             * @param FieldClass
             * @param settingKey
             * @param setting
             * @param extraFieldOptions
             * @return A Deputy setting field
             */
            newStringLikeField(FieldClass, settingKey, setting, extraFieldOptions = {}) {
                var _a, _b, _c;
                const isDisabled = setting.isDisabled(this.config.config);
                const desc = mw.message(`deputy.setting.${this.mode}.${this.config.group}.${settingKey}.description`);
                const field = new FieldClass(Object.assign({ readOnly: (_a = setting.displayOptions.readOnly) !== null && _a !== void 0 ? _a : false, value: (_c = (_b = setting.serialize) === null || _b === void 0 ? void 0 : _b.call(setting, setting.get())) !== null && _c !== void 0 ? _c : setting.get(), disabled: isDisabled !== undefined && isDisabled !== false }, extraFieldOptions));
                const layout = new OO.ui.FieldLayout(field, {
                    align: 'top',
                    label: this.getSettingMsg(settingKey, 'name'),
                    help: typeof isDisabled === 'string' ?
                        this.getSettingMsg(settingKey, isDisabled) :
                        desc.exists() ? desc.text() : undefined,
                    helpInline: true
                });
                if (FieldClass === OO.ui.NumberInputWidget) {
                    field.on('change', (value) => {
                        setting.set(+value);
                        this.emit('change');
                    });
                }
                else {
                    field.on('change', (value) => {
                        setting.set(value);
                        this.emit('change');
                    });
                }
                // Attach disabled re-checker
                this.on('change', () => {
                    field.setDisabled(setting.isDisabled(this.config.config));
                });
                return h_1("div", { class: "deputy-setting" }, unwrapWidget(layout));
            }
            /**
             * Creates a new string setting field.
             *
             * @param settingKey
             * @param setting
             * @param extraFieldOptions
             * @return An HTMLElement of the given setting's field.
             */
            newStringField(settingKey, setting, extraFieldOptions) {
                return this.newStringLikeField(OO.ui.TextInputWidget, settingKey, setting, extraFieldOptions);
            }
            /**
             * Creates a new number setting field.
             *
             * @param settingKey
             * @param setting
             * @param extraFieldOptions
             * @return An HTMLElement of the given setting's field.
             */
            newNumberField(settingKey, setting, extraFieldOptions) {
                return this.newStringLikeField(OO.ui.NumberInputWidget, settingKey, setting, extraFieldOptions);
            }
            /**
             * Creates a new page title setting field.
             *
             * @param settingKey
             * @param setting
             * @param extraFieldOptions
             * @return An HTMLElement of the given setting's field.
             */
            newPageField(settingKey, setting, extraFieldOptions) {
                return this.newStringLikeField(mw.widgets.TitleInputWidget, settingKey, setting, extraFieldOptions);
            }
            /**
             * Creates a new code setting field.
             *
             * @param settingKey
             * @param setting
             * @param extraFieldOptions
             * @return An HTMLElement of the given setting's field.
             */
            newCodeField(settingKey, setting, extraFieldOptions) {
                return this.newStringLikeField(OO.ui.MultilineTextInputWidget, settingKey, setting, extraFieldOptions);
            }
        };
    }
    /**
     * Creates a new ConfigurationGroupTabPanel.
     *
     * @param config Configuration to be passed to the element.
     * @return A ConfigurationGroupTabPanel object
     */
    function ConfigurationGroupTabPanel (config) {
        if (!InternalConfigurationGroupTabPanel$1) {
            initConfigurationGroupTabPanel$1();
        }
        return new InternalConfigurationGroupTabPanel$1(config);
    }

    /**
     * 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);
    }

    /**
     * Opens a temporary window. Use this for dialogs that are immediately destroyed
     * after running. Do NOT use this for re-openable dialogs, such as the main ANTE
     * dialog.
     *
     * @param window
     * @return A promise. Resolves when the window is closed.
     */
    function openWindow(window) {
        return __awaiter(this, void 0, void 0, function* () {
            return new Promise((res) => {
                let wm = new OO.ui.WindowManager();
                document.getElementsByTagName('body')[0].appendChild(unwrapWidget(wm));
                wm.addWindows([window]);
                wm.openWindow(window);
                wm.on('closing', (win, closed) => {
                    closed.then(() => {
                        if (wm) {
                            const _wm = wm;
                            wm = null;
                            removeElement(unwrapWidget(_wm));
                            _wm.destroy();
                            res();
                        }
                    });
                });
            });
        });
    }

    var deputySettingsEnglish = {
    	"deputy.about.version": "v$1 ($2)",
    	"deputy.about": "About",
    	"deputy.about.homepage": "Homepage",
    	"deputy.about.openSource": "Source",
    	"deputy.about.contact": "Contact",
    	"deputy.about.credit": "Deputy was made with the help of the English Wikipedia Copyright Cleanup WikiProject and the Wikimedia Foundation.",
    	"deputy.about.license": "Deputy is licensed under the <a href=\"$1\">Apache License 2.0</a>. The source code for Deputy is available on <a href=\"$2\">GitHub</a>, and is free for everyone to view and suggest changes.",
    	"deputy.about.thirdParty": "Deputy is bundled with third party libraries to make development easier. All libraries have been vetted for user security and license compatibility. For more information, see the <a href=\"$1\">\"Licensing\"</a> section on Deputy's README.",
    	"deputy.about.buildInfo": "Deputy v$1 ($2), committed $3.",
    	"deputy.about.footer": "Made with love, coffee, and the tears of copyright editors.",
    	"deputy.settings.portlet": "Deputy preferences",
    	"deputy.settings.portlet.tooltip": "Opens a dialog to modify Deputy preferences",
    	"deputy.settings.wikiEditIntro.title": "This is a Deputy configuration page",
    	"deputy.settings.wikiEditIntro.current": "Deputy's active configuration comes from this page. Changing this page will affect the settings of all Deputy users on this wiki. Edit responsibly, and avoid making significant changes without prior discussion.",
    	"deputy.settings.wikiEditIntro.other": "This is a valid Deputy configuration page, but the configuration is currently being loaded from {{wikilink:$1}}. If this becomes the active configuration page, changing it will affect the settings of all Deputy users on this wiki. Edit responsibly, and avoid making significant changes without prior discussion.",
    	"deputy.settings.wikiEditIntro.edit.current": "Modify configuration",
    	"deputy.settings.wikiEditIntro.edit.other": "Modify this configuration",
    	"deputy.settings.wikiEditIntro.edit.otherCurrent": "Modify the active configuration",
    	"deputy.settings.wikiEditIntro.edit.protected": "This page's protection settings do not allow you to edit the page.",
    	"deputy.settings.wikiOutdated": "Outdated configuration",
    	"deputy.settings.wikiOutdated.help": "Deputy has detected a change in this wiki's configuration for all Deputy users. We've automatically downloaded the changes for you, but you have to reload to apply the changes.",
    	"deputy.settings.wikiOutdated.reload": "Reload",
    	"deputy.settings.dialog.title": "Deputy Preferences",
    	"deputy.settings.dialog.unimplemented": "A way to modify this setting has not yet been implemented. Check back later!",
    	"deputy.settings.saved": "Preferences saved. Please refresh the page to see changes.",
    	"deputy.settings.dialog.wikiConfigWarning": "You are currently editing a wiki-wide Deputy configuration page. Changes made to this page may affect the settings of all Deputy users on this wiki. Edit responsibly, and avoid making significant changes without prior discussion.",
    	"deputy.setting.user.core": "Deputy",
    	"deputy.setting.user.core.language.name": "Language",
    	"deputy.setting.user.core.language.description": "Deputy's interface language. English (US) is used by default, and is used as a fallback if no translations are available. If the content of the wiki you work on is in a different language from the interface language, Deputy will need to load additional data to ensure edit summaries, text, etc., saved on-wiki match the wiki's content language. For this reason, we suggest keeping the interface language the same as the wiki's content language.",
    	"deputy.setting.user.core.modules.name": "Modules",
    	"deputy.setting.user.core.modules.description": "Choose the enabled Deputy modules. By default, all modules are enabled.\nDisabling specific modules won't make Deputy load faster, but it can remove\nUI features added by Deputy which may act as clutter when unused.",
    	"deputy.setting.user.core.modules.cci": "Contributor Copyright Investigations",
    	"deputy.setting.user.core.modules.ante": "{{int:deputy.ante}}",
    	"deputy.setting.user.core.modules.ia": "{{int:deputy.ia}}",
    	"deputy.setting.user.core.portletNames.name": "Portlet names",
    	"deputy.setting.user.core.portletNames.description": "Choose which names appear in the Deputy portlet (toolbox) links.",
    	"deputy.setting.user.core.portletNames.full": "Full names (e.g. Attribution Notice Template Editor)",
    	"deputy.setting.user.core.portletNames.short": "Shortened names (e.g. Attrib. Template Editor)",
    	"deputy.setting.user.core.portletNames.acronym": "Acronyms (e.g. ANTE)",
    	"deputy.setting.user.core.dangerMode.name": "Danger mode",
    	"deputy.setting.user.core.dangerMode.description": "Live on the edge. This disables most confirmations and warnings given by Deputy, only leaving potentially catastrophic actions, such as page edits which break templates. It also adds extra buttons meant for rapid case processing. Intended for clerk use; use with extreme caution.",
    	"deputy.setting.user.cci": "CCI",
    	"deputy.setting.user.cci.enablePageToolbar.name": "Enable page toolbar",
    	"deputy.setting.user.cci.enablePageToolbar.description": "Enables the page toolbar, which is used to quickly show tools, analysis options, and related case information on a page that is the subject of a CCI investigation.",
    	"deputy.setting.user.cci.showCvLink.name": "Show \"cv\" (\"copyvios\") link for revisions",
    	"deputy.setting.user.cci.showCvLink.description": "Show a \"cv\" link next to \"cur\" and \"prev\" on revision rows. This link will only appear if this wiki is configured to use Earwig's Copyvio Detector.",
    	"deputy.setting.user.cci.showUsername.name": "Show username",
    	"deputy.setting.user.cci.showUsername.description": "Show the username of the user who made the edit on revision rows. This may be redundant for cases which only have one editor.",
    	"deputy.setting.user.cci.autoCollapseRows.name": "Automatically collapse rows",
    	"deputy.setting.user.cci.autoCollapseRows.description": "Automatically collapse rows when the page is loaded. This is useful for cases where each row has many revisions, but may be annoying for cases where each row has few revisions.",
    	"deputy.setting.user.cci.autoShowDiff.name": "Automatically show diffs",
    	"deputy.setting.user.cci.autoShowDiff.description": "Enabling automatic loading of diffs. Configurable with two additional options to avoid loading too much content.",
    	"deputy.setting.user.cci.maxRevisionsToAutoShowDiff.name": "Maximum revisions to automatically show diff",
    	"deputy.setting.user.cci.maxRevisionsToAutoShowDiff.description": "The maximum number of revisions for a given page to automatically show the diff for each revision in the main interface.",
    	"deputy.setting.user.cci.maxSizeToAutoShowDiff.name": "Maximum size to automatically show diff",
    	"deputy.setting.user.cci.maxSizeToAutoShowDiff.description": "The maximum size of a diff to be automatically shown, if the diff will be automatically shown (see \"Maximum revisions to automatically show diff\"). Prevents extremely large diffs from opening. Set to -1 to show regardless of size.",
    	"deputy.setting.user.cci.forceUtc.name": "Force UTC time",
    	"deputy.setting.user.cci.forceUtc.description": "Forces Deputy to use UTC time whenever displaying dates and times, irregardless of your system's timezone or your MediaWiki time settings.",
    	"deputy.setting.user.cci.signingBehavior.name": "Row signing behavior",
    	"deputy.setting.user.cci.signingBehavior.description": "Choose how Deputy should behave when signing rows. By default, all rows are always signed with your signature (~~~~). You may configure Deputy to only sign the last row or never sign. You can also configure Deputy to leave a hidden trace behind (<!-- User:Example|2016-05-28T14:32:12Z -->), which helps Deputy (for other users) determine who assessed a row.",
    	"deputy.setting.user.cci.signingBehavior.always": "Always sign rows",
    	"deputy.setting.user.cci.signingBehavior.alwaysTrace": "Always leave a trace",
    	"deputy.setting.user.cci.signingBehavior.alwaysTraceLastOnly": "Always leave a trace, but sign the last row modified",
    	"deputy.setting.user.cci.signingBehavior.lastOnly": "Only sign the last row modified (prevents assessor recognition)",
    	"deputy.setting.user.cci.signingBehavior.never": "Never sign rows (prevents assessor recognition)",
    	"deputy.setting.user.cci.signSectionArchive.name": "Sign by default when archiving CCI sections",
    	"deputy.setting.user.cci.signSectionArchive.description": "If enabled, Deputy will enable the \"include my signature\" checkbox by default when archiving a CCI section.",
    	"deputy.setting.user.cci.openOldOnContinue.name": "Open old versions on continue",
    	"deputy.setting.user.cci.openOldOnContinue.description": "If enabled, all previously-open sections of a given case page will also be opened alongside the section where the \"continue CCI session\" link was clicked.",
    	"deputy.setting.user.ante": "ANTE",
    	"deputy.setting.user.ante.enableAutoMerge.name": "Merge automatically on run",
    	"deputy.setting.user.ante.enableAutoMerge.description": "If enabled, templates that can be merged will automatically be merged when the dialog opens.",
    	"deputy.setting.user.ante.enableAutoMerge.unimplemented": "This feature has not yet been implemented.",
    	"deputy.setting.user.ante.onSubmit.name": "Action on submit",
    	"deputy.setting.user.ante.onSubmit.description": "Choose what to do after editing attribution notice templates.",
    	"deputy.setting.user.ante.onSubmit.nothing": "Do nothing",
    	"deputy.setting.user.ante.onSubmit.reload": "Reload the page",
    	"deputy.setting.user.ia": "IA",
    	"deputy.setting.user.ia.responses.name": "Custom responses",
    	"deputy.setting.user.ia.responses.description": "A custom set of responses, or overrides for existing responses. If an entry\nwith the same key on both the wiki-wide configuration and the user configuration\nexists, the user configuration will override the wiki-wide configuration. Wiki-wide configuration responses can also be disabled locally. If this setting is empty, no overrides are made.",
    	"deputy.setting.user.ia.enablePageToolbar.name": "Enable page toolbar",
    	"deputy.setting.user.ia.enablePageToolbar.description": "If enabled, the page toolbar will be shown when dealing with CP cases. The IA page toolbar works slightly differently from the CCI page toolbar. Namely, it shows a button for responding instead of a status dropdown.",
    	"deputy.setting.user.ia.enablePageToolbar.unimplemented": "This feature has not yet been implemented.",
    	"deputy.setting.user.ia.defaultEntirePage.name": "Hide entire page by default",
    	"deputy.setting.user.ia.defaultEntirePage.description": "If enabled, the Infringement Assistant reporting window will hide the entire page by default.",
    	"deputy.setting.user.ia.defaultFromUrls.name": "Use URLs by default",
    	"deputy.setting.user.ia.defaultFromUrls.description": "If enabled, the Infringement Assistant reporting window will use URL inputs by default.",
    	"deputy.setting.user.ia.onHide.name": "Action on hide",
    	"deputy.setting.user.ia.onHide.description": "Choose what to do after the \"Hide content only\" button is selected and the relevant content is hidden from the page.",
    	"deputy.setting.user.ia.onHide.nothing": "Do nothing",
    	"deputy.setting.user.ia.onHide.reload": "Reload the page",
    	"deputy.setting.user.ia.onHide.redirect": "Redirect to the noticeboard page",
    	"deputy.setting.user.ia.onSubmit.name": "Action on submit",
    	"deputy.setting.user.ia.onSubmit.description": "Choose what to do after the \"Submit\" button is selected, the relevant content is hidden from the page, and the page is reported.",
    	"deputy.setting.user.ia.onSubmit.nothing": "Do nothing",
    	"deputy.setting.user.ia.onSubmit.reload": "Reload the page",
    	"deputy.setting.user.ia.onSubmit.redirect": "Redirect to the noticeboard page",
    	"deputy.setting.user.ia.onBatchSubmit.name": "Action on batch listing submit",
    	"deputy.setting.user.ia.onBatchSubmit.description": "When reporting a batch of pages, choose what to do after the \"Report\" button is selected and the pages are reported.",
    	"deputy.setting.user.ia.onBatchSubmit.nothing": "Do nothing",
    	"deputy.setting.user.ia.onBatchSubmit.reload": "Reload the noticeboard page",
    	"deputy.setting.wiki.core": "Core",
    	"deputy.setting.wiki.core.lastEdited.name": "Configuration last edited",
    	"deputy.setting.wiki.core.lastEdited.description": "The last time that this configuration was edited, as a timestamp. This is a way to ensure that all users are on the correct wiki-wide configuration version before changes are made. Checks are performed on every page load with Deputy.",
    	"deputy.setting.wiki.core.dispatchRoot.name": "Deputy Dispatch root URL",
    	"deputy.setting.wiki.core.dispatchRoot.description": "The URL to a Deputy Dispatch instance that can handle this wiki. Deputy Dispatch is a webserver responsible for centralizing and optimizing data used by Deputy, and can be used to reduce load on wikis. More information can be found at https://github.com/ChlodAlejandro/deputy-dispatch.",
    	"deputy.setting.wiki.core.changeTag.name": "Change tag",
    	"deputy.setting.wiki.core.changeTag.description": "Tag to use for all Deputy edits.",
    	"deputy.setting.wiki.cci": "CCI",
    	"deputy.setting.wiki.cci.enabled.name": "Enable contributor copyright investigations assistant",
    	"deputy.setting.wiki.cci.enabled.description": "Enables the CCI workflow assistant. This allows Deputy to replace the contribution survey found on CCI case pages with a graphical interface which works with other tabs to make the CCI workflow easier.",
    	"deputy.setting.wiki.cci.rootPage.name": "Root page",
    	"deputy.setting.wiki.cci.rootPage.description": "The main page that holds all subpages containing valid contribution copyright investigation cases.",
    	"deputy.setting.wiki.cci.collapseTop.name": "Collapsible wikitext (top)",
    	"deputy.setting.wiki.cci.collapseTop.description": "Placed just below a section heading when closing a contributor survey section. Use \"$1\" to denote user comments and signature. On the English Wikipedia, this is {{Template:collapse top}}. Other wikis may have an equivalent template. This should go hand in hand with \"{{int:deputy.setting.wiki.cci.collapseBottom.name}}\", as they are used as a pair.",
    	"deputy.setting.wiki.cci.collapseBottom.name": "Collapsible wikitext (bottom)",
    	"deputy.setting.wiki.cci.collapseBottom.description": "Placed at the end of a section when closing a contributor survey section. On the English Wikipedia, this is {{Template:collapse bottom}}. Other wikis may have an equivalent template.",
    	"deputy.setting.wiki.cci.earwigRoot.name": "Earwig's Copyvio Detector root URL",
    	"deputy.setting.wiki.cci.earwigRoot.description": "The URL to an instance of Earwig's Copyvio Detector that can handle this wiki. The official copyvio detector (copyvios.toolforge.org) can only handle Wikimedia wikis — you may change this behavior by specifying a custom instance that can process this wiki here.",
    	"deputy.setting.wiki.ante": "ANTE",
    	"deputy.setting.wiki.ante.enabled.name": "Enable the Attribution Notice Template Editor",
    	"deputy.setting.wiki.ante.enabled.description": "Enables ANTE for all users. ANTE is currently the least-optimized module for localization, and may not work for all wikis.",
    	"deputy.setting.wiki.ia": "IA",
    	"deputy.setting.wiki.ia.enabled.name": "Enable the Infringement Assistant",
    	"deputy.setting.wiki.ia.enabled.description": "Enables IA for all users. IA allows users to easily and graphically report pages with suspected or complicated copyright infringements.",
    	"deputy.setting.wiki.ia.rootPage.name": "Root page",
    	"deputy.setting.wiki.ia.rootPage.description": "The root page for Infringement Assistant. This should be the copyright problems noticeboard for this specific wiki. IA will only show quick response links for the root page and its subpages.",
    	"deputy.setting.wiki.ia.subpageFormat.name": "Subpage format",
    	"deputy.setting.wiki.ia.subpageFormat.description": "The format to use for subpages of the root page. This is a moment.js format string.",
    	"deputy.setting.wiki.ia.preload.name": "Preload",
    	"deputy.setting.wiki.ia.preload.description": "Defines the page content to preload the page with if a given subpage does not exist yet. This should be an existing page on-wiki. Leave blank to avoid using a preload entirely.",
    	"deputy.setting.wiki.ia.allowPresumptive.name": "Allow presumptive deletions",
    	"deputy.setting.wiki.ia.allowPresumptive.description": "Allows users to file listings for presumptive deletions. Note that the CCI setting \"Root page\" must be set for this to work, even if the \"CCI\" module is disabled entirely.",
    	"deputy.setting.wiki.ia.listingWikitext.name": "Listing wikitext",
    	"deputy.setting.wiki.ia.listingWikitext.description": "Defines the wikitext that will be used when adding listings to a noticeboard page. You may use \"$1\" to denote the page being reported, and \"$2\" for user comments (which shouldn't contain the signature).",
    	"deputy.setting.wiki.ia.listingWikitextMatch.name": "Regular expression for listings",
    	"deputy.setting.wiki.ia.listingWikitextMatch.description": "A regular expression that will be used to parse and detect listings on a given noticeboard page. Since its usage is rather technical, this value should be edited by someone with technical knowledge of regular expressions. This regular expression must provide three captured groups: group \"$1\" will catch any bullet point, space, or prefix, \"$2\" will catch the page title ONLY if the given page matches \"{{int:deputy.setting.wiki.ia.listingWikitext.name}}\" or \"{{int:deputy.setting.wiki.ia.batchListingWikitext.name}}\", and \"$3\" will catch the page title ONLY IF the page wasn't caught in \"$2\" (such as in cases where there is only a bare link to the page).",
    	"deputy.setting.wiki.ia.batchListingWikitext.name": "Batch listing wikitext",
    	"deputy.setting.wiki.ia.batchListingWikitext.description": "Defines the wikitext that will be used when adding batch listings to a noticeboard page. You may use \"$1\" to denote the page being reported, and \"$2\" for the list of pages (as determined by \"{{int:deputy.setting.wiki.ia.batchListingPageWikitext.name}}\") and \"$3\" for user comments (which doesn't contain the signature).",
    	"deputy.setting.wiki.ia.batchListingPageWikitext.name": "Batch listing page wikitext",
    	"deputy.setting.wiki.ia.batchListingPageWikitext.description": "Wikitext to use for every row of text in \"{{int:deputy.setting.wiki.ia.batchListingWikitext.name}}\". No line breaks are automatically added; these must be added into this string.",
    	"deputy.setting.wiki.ia.hideTemplate.name": "Content hiding wikitext (top)",
    	"deputy.setting.wiki.ia.hideTemplate.description": "Wikitext to hide offending content with. On the English Wikipedia, this is a usage of {{Template:copyvio}}. Other wikis may have an equivalent template. This should go hand in hand with \"{{int:deputy.setting.wiki.ia.hideTemplateBottom.name}}\", as they are used as a pair.",
    	"deputy.setting.wiki.ia.hideTemplateBottom.name": "Content hiding wikitext (bottom)",
    	"deputy.setting.wiki.ia.hideTemplateBottom.description": "Placed at the end of hidden content to hide only part of a page. On the English Wikipedia, this is {{Template:copyvio/bottom}}. Other wikis may have an equivalent template.",
    	"deputy.setting.wiki.ia.responses.name": "Responses",
    	"deputy.setting.wiki.ia.responses.description": "Quick responses for copyright problems listings. Used by clerks to resolve specific listings or provide more information about the progress of a given listing."
    };

    let InternalConfigurationGroupTabPanel;
    /**
     * Initializes the process element.
     */
    function initConfigurationGroupTabPanel() {
        var _a;
        InternalConfigurationGroupTabPanel = (_a = class ConfigurationGroupTabPanel extends OO.ui.TabPanelLayout {
                /**
                 */
                constructor() {
                    super('configurationGroupPage_About');
                    this.$element.append(h_1("div", null,
                        h_1("div", { class: "deputy-about" },
                            h_1("div", { style: "flex: 0" },
                                h_1("img", { src: ConfigurationGroupTabPanel.logoUrl, alt: "Deputy logo" })),
                            h_1("div", { style: "flex: 1" },
                                h_1("div", null,
                                    h_1("div", null, mw.msg('deputy.name')),
                                    h_1("div", null, mw.msg('deputy.about.version', version, gitAbbrevHash))),
                                h_1("div", null, mw.msg('deputy.description')))),
                        h_1("div", null,
                            h_1("a", { href: "https://w.wiki/7NWR", target: "_blank" }, unwrapWidget(new OO.ui.ButtonWidget({
                                label: mw.msg('deputy.about.homepage'),
                                flags: ['progressive']
                            }))),
                            h_1("a", { href: "https://github.com/ChlodAlejandro/deputy", target: "_blank" }, unwrapWidget(new OO.ui.ButtonWidget({
                                label: mw.msg('deputy.about.openSource'),
                                flags: ['progressive']
                            }))),
                            h_1("a", { href: "https://w.wiki/7NWS", target: "_blank" }, unwrapWidget(new OO.ui.ButtonWidget({
                                label: mw.msg('deputy.about.contact'),
                                flags: ['progressive']
                            })))),
                        h_1("p", { dangerouslySetInnerHTML: mw.msg('deputy.about.credit') }),
                        h_1("p", { dangerouslySetInnerHTML: mw.msg('deputy.about.license', 'https://www.apache.org/licenses/LICENSE-2.0', 'https://github.com/ChlodAlejandro/deputy') }),
                        h_1("p", { dangerouslySetInnerHTML: mw.msg('deputy.about.thirdParty', 'https://github.com/ChlodAlejandro/deputy#licensing') }),
                        h_1("p", { style: { fontSize: '0.9em', color: 'darkgray' }, dangerouslySetInnerHTML: mw.msg('deputy.about.buildInfo', gitVersion, gitBranch, new Date(gitDate).toLocaleString()) }),
                        h_1("p", { style: { fontSize: '0.9em', color: 'darkgray' }, dangerouslySetInnerHTML: mw.msg('deputy.about.footer') })));
                }
                /**
                 * @return The {@Link Setting}s for this group.
                 */
                get settings() {
                    return this.config.config.all[this.config.group];
                }
                /**
                 * Sets up the tab item
                 */
                setupTabItem() {
                    this.tabItem.setLabel(mw.msg('deputy.about'));
                    return this;
                }
            },
            _a.logoUrl = 'https://upload.wikimedia.org/wikipedia/commons/2/2b/Deputy_logo.svg',
            _a);
    }
    /**
     * Creates a new ConfigurationGroupTabPanel.
     *
     * @return A ConfigurationGroupTabPanel object
     */
    function ConfigurationAboutTabPanel () {
        if (!InternalConfigurationGroupTabPanel) {
            initConfigurationGroupTabPanel();
        }
        return new InternalConfigurationGroupTabPanel();
    }

    let InternalConfigurationDialog;
    /**
     * Initializes the process element.
     */
    function initConfigurationDialog() {
        var _a;
        InternalConfigurationDialog = (_a = class ConfigurationDialog extends OO.ui.ProcessDialog {
                /**
                 *
                 * @param data
                 */
                constructor(data) {
                    super();
                    this.config = data.config;
                }
                /**
                 * @return The body height of this dialog.
                 */
                getBodyHeight() {
                    return 900;
                }
                /**
                 * Initializes the dialog.
                 */
                initialize() {
                    super.initialize();
                    this.layout = new OO.ui.IndexLayout();
                    this.layout.addTabPanels(this.generateGroupLayouts());
                    if (this.config instanceof UserConfiguration) {
                        this.layout.addTabPanels([ConfigurationAboutTabPanel()]);
                    }
                    this.$body.append(this.layout.$element);
                    return this;
                }
                /**
                 * Generate TabPanelLayouts for each configuration group.
                 *
                 * @return An array of TabPanelLayouts
                 */
                generateGroupLayouts() {
                    return Object.keys(this.config.all).map((group) => ConfigurationGroupTabPanel({
                        config: this.config,
                        group
                    }));
                }
                /**
                 *
                 * @param action
                 * @return An OOUI Process.
                 */
                getActionProcess(action) {
                    const process = super.getActionProcess();
                    if (action === 'save') {
                        process.next(this.config.save());
                        process.next(() => {
                            var _a, _b;
                            mw.notify(mw.msg('deputy.settings.saved'), {
                                type: 'success'
                            });
                            if (this.config.type === 'user') {
                                // Override local Deputy option, just in case the user wishes to
                                // change the configuration again.
                                mw.user.options.set(UserConfiguration.optionKey, this.config.serialize());
                                if ((_a = window.deputy) === null || _a === void 0 ? void 0 : _a.comms) {
                                    window.deputy.comms.send({
                                        type: 'userConfigUpdate',
                                        config: this.config.serialize()
                                    });
                                }
                            }
                            else if (this.config.type === 'wiki') {
                                // We know it is a WikiConfiguration, the instanceof check here
                                // is just for type safety.
                                if ((_b = window.deputy) === null || _b === void 0 ? void 0 : _b.comms) {
                                    window.deputy.comms.send({
                                        type: 'wikiConfigUpdate',
                                        config: {
                                            title: this.config.sourcePage.getPrefixedText(),
                                            editable: this.config.editable,
                                            wt: this.config.serialize()
                                        }
                                    });
                                }
                                // Reload the page.
                                window.location.reload();
                            }
                        });
                    }
                    process.next(() => {
                        this.close();
                    });
                    return process;
                }
            },
            _a.static = Object.assign(Object.assign({}, OO.ui.ProcessDialog.static), { name: 'configurationDialog', title: mw.msg('deputy.settings.dialog.title'), size: 'large', actions: [
                    {
                        flags: ['safe', 'close'],
                        icon: 'close',
                        label: mw.msg('deputy.ante.close'),
                        title: mw.msg('deputy.ante.close'),
                        invisibleLabel: true,
                        action: 'close'
                    },
                    {
                        action: 'save',
                        label: mw.msg('deputy.save'),
                        flags: ['progressive', 'primary']
                    }
                ] }),
            _a);
    }
    /**
     * Creates a new ConfigurationDialog.
     *
     * @param data
     * @return A ConfigurationDialog object
     */
    function ConfigurationDialogBuilder(data) {
        if (!InternalConfigurationDialog) {
            initConfigurationDialog();
        }
        return new InternalConfigurationDialog(data);
    }
    let attached = false;
    /**
     * Spawns a new configuration dialog.
     *
     * @param config
     */
    function spawnConfigurationDialog(config) {
        mw.loader.using([
            'oojs-ui-core', 'oojs-ui-windows', 'oojs-ui-widgets', 'mediawiki.widgets'
        ], () => {
            const dialog = ConfigurationDialogBuilder({ config });
            openWindow(dialog);
        });
    }
    /**
     * Attaches the "Deputy preferences" portlet link in the toolbox. Ensures that it doesn't
     * get attached twice.
     */
    function attachConfigurationDialogPortletLink() {
        return __awaiter(this, void 0, void 0, function* () {
            if (document.querySelector('#p-deputy-config') || attached) {
                return;
            }
            attached = true;
            mw.util.addCSS(deputySettingsStyles);
            yield DeputyLanguage.load('settings', deputySettingsEnglish);
            mw.util.addPortletLink('p-tb', '#', mw.msg('deputy.settings.portlet'), 'deputy-config', mw.msg('deputy.settings.portlet.tooltip')).addEventListener('click', () => {
                // Load a fresh version of the configuration - this way we can make
                // modifications live to the configuration without actually affecting
                // tool usage.
                spawnConfigurationDialog(UserConfiguration.load());
            });
        });
    }

    /**
     * Transforms the `redirects` object returned by MediaWiki's `query` action into an
     * object instead of an array.
     *
     * @param redirects
     * @param normalized
     * @return Redirects as an object
     */
    function toRedirectsObject(redirects, normalized) {
        var _a;
        if (redirects == null) {
            return {};
        }
        const out = {};
        for (const redirect of redirects) {
            out[redirect.from] = redirect.to;
        }
        // Single-level redirect-normalize loop check
        for (const normal of normalized) {
            out[normal.from] = (_a = out[normal.to]) !== null && _a !== void 0 ? _a : normal.to;
        }
        return out;
    }

    /**
     * Checks if two MediaWiki page titles are equal.
     *
     * @param title1
     * @param title2
     * @return `true` if `title1` and `title2` refer to the same page
     */
    function equalTitle(title1, title2) {
        return normalizeTitle(title1).getPrefixedDb() === normalizeTitle(title2).getPrefixedDb();
    }

    let InternalDeputyMessageWidget;
    /**
     * Initializes the process element.
     */
    function initDeputyMessageWidget() {
        InternalDeputyMessageWidget = class DeputyMessageWidget extends OO.ui.MessageWidget {
            /**
             * @param config Configuration to be passed to the element.
             */
            constructor(config) {
                var _a;
                super(config);
                this.$element.addClass('dp-messageWidget');
                const elLabel = this.$label[0];
                if (!config.label) {
                    if (config.title) {
                        elLabel.appendChild(h_1("b", { style: { display: 'block' } }, config.title));
                    }
                    if (config.message) {
                        elLabel.appendChild(h_1("p", { class: "dp-messageWidget-message" }, config.message));
                    }
                }
                if (config.actions || config.closable) {
                    const actionContainer = h_1("div", { class: "dp-messageWidget-actions" });
                    for (const action of ((_a = config.actions) !== null && _a !== void 0 ? _a : [])) {
                        if (action instanceof OO.ui.Element) {
                            actionContainer.appendChild(unwrapWidget(action));
                        }
                        else {
                            actionContainer.appendChild(action);
                        }
                    }
                    if (config.closable) {
                        const closeButton = new OO.ui.ButtonWidget({
                            label: mw.msg('deputy.dismiss')
                        });
                        closeButton.on('click', () => {
                            removeElement(unwrapWidget(this));
                            this.emit('close');
                        });
                        actionContainer.appendChild(unwrapWidget(closeButton));
                    }
                    elLabel.appendChild(actionContainer);
                }
            }
        };
    }
    /**
     * Creates a new DeputyMessageWidget. This is an extension of the default
     * OOUI MessageWidget. It includes support for a title, a message, and button
     * actions.
     *
     * @param config Configuration to be passed to the element.
     * @return A DeputyMessageWidget object
     */
    function DeputyMessageWidget (config) {
        if (!InternalDeputyMessageWidget) {
            initDeputyMessageWidget();
        }
        return new InternalDeputyMessageWidget(config);
    }

    /**
     * @param config The current configuration (actively loaded, not the one being viewed)
     * @return An HTML element consisting of an OOUI MessageWidget
     */
    function WikiConfigurationEditIntro(config) {
        const current = config.onConfigurationPage();
        let buttons;
        if (current) {
            const editCurrent = new OO.ui.ButtonWidget({
                flags: ['progressive', 'primary'],
                label: mw.msg('deputy.settings.wikiEditIntro.edit.current'),
                disabled: !mw.config.get('wgIsProbablyEditable'),
                title: mw.config.get('wgIsProbablyEditable') ?
                    undefined : mw.msg('deputy.settings.wikiEditIntro.edit.protected')
            });
            editCurrent.on('click', () => {
                spawnConfigurationDialog(config);
            });
            buttons = [editCurrent];
        }
        else {
            const editCurrent = new OO.ui.ButtonWidget({
                flags: ['progressive', 'primary'],
                label: mw.msg('deputy.settings.wikiEditIntro.edit.otherCurrent'),
                disabled: !config.editable,
                title: config.editable ?
                    undefined : mw.msg('deputy.settings.wikiEditIntro.edit.protected')
            });
            editCurrent.on('click', () => __awaiter(this, void 0, void 0, function* () {
                spawnConfigurationDialog(config);
            }));
            const editOther = new OO.ui.ButtonWidget({
                flags: ['progressive'],
                label: mw.msg('deputy.settings.wikiEditIntro.edit.other'),
                disabled: !mw.config.get('wgIsProbablyEditable'),
                title: mw.config.get('wgIsProbablyEditable') ?
                    undefined : mw.msg('deputy.settings.wikiEditIntro.edit.protected')
            });
            editOther.on('click', () => __awaiter(this, void 0, void 0, function* () {
                spawnConfigurationDialog(yield config.static.load(normalizeTitle()));
            }));
            buttons = [editCurrent, editOther];
        }
        const messageBox = DeputyMessageWidget({
            classes: [
                'deputy', 'dp-mb'
            ],
            type: 'notice',
            title: mw.msg('deputy.settings.wikiEditIntro.title'),
            message: current ?
                mw.msg('deputy.settings.wikiEditIntro.current') :
                h_1("span", { dangerouslySetInnerHTML: mw.message('deputy.settings.wikiEditIntro.other', config.sourcePage.getPrefixedText()).parse() }),
            actions: buttons
        });
        const box = unwrapWidget(messageBox);
        box.classList.add('deputy', 'deputy-wikiConfig-intro');
        return box;
    }

    /* eslint-disable max-len */
    /*
     * Replacement polyfills for wikis that have no configured templates.
     * Used in WikiConfiguration, to permit a seamless OOB experience.
     */
    /** `{{collapse top}}` equivalent */
    const collapseTop = `
{| class="mw-collapsible mw-collapsed" style="border:1px solid #C0C0C0;width:100%"
! <div style="background:#CCFFCC;">$1</div>
|-
|
`.trimStart();
    /** `{{collapse bottom}}` equivalent */
    const collapseBottom = `
|}`;
    /** `* {{subst:article-cv|1=$1}} $2 ~~~~` equivalent */
    const listingWikitext = '* [[$1]] $2 ~~~~';
    /**
     * Polyfill for the following:
     * `; {{anchor|1={{delink|$1}}}} $1
     * $2
     * $3 ~~~~`
     */
    const batchListingWikitext = `*; <span style="display: none;" id="$1"></span> $1
$2
$3`;
    /**
     * Inserted and chained as part of $2 in `batchListingWikitext`.
     * Equivalent of `* {{subst:article-cv|1=$1}}\n`. Newline is intentional.
     */
    const batchListingPageWikitext = '* [[$1]]\n';
    /**
     * `{{subst:copyvio|url=$1|fullpage=$2}}` equivalent
     */
    const copyvioTop = `<div style="padding: 8px; border: 4px solid #0298b1;">
<div style="font-size: 1.2rem"><b>{{int:deputy.ia.content.copyvio}}</b></div>
<div>{{int:deputy.ia.content.copyvio.help}}</div>
{{if:$1|<div>{{if:$presumptive|{{int:deputy.ia.content.copyvio.from.pd}} $1|{{int:deputy.ia.content.copyvio.from}} $1}}</div>}}
</div>
<!-- {{int:deputy.ia.content.copyvio.content}} -->
<div class="copyvio" style="display: none">`;
    /**
     * `{{subst:copyvio/bottom}}` equivalent.
     */
    const copyvioBottom = `
</div>`;

    /**
     * @return A MessageWidget for reloading a page with an outdated configuration.
     */
    function ConfigurationReloadBanner() {
        const reloadButton = new OO.ui.ButtonWidget({
            flags: ['progressive', 'primary'],
            label: mw.msg('deputy.settings.wikiOutdated.reload')
        });
        const messageBox = DeputyMessageWidget({
            classes: [
                'deputy', 'dp-mb', 'dp-wikiConfigUpdateMessage'
            ],
            type: 'notice',
            title: mw.msg('deputy.settings.wikiOutdated'),
            message: mw.msg('deputy.settings.wikiOutdated.help'),
            actions: [reloadButton]
        });
        reloadButton.on('click', () => __awaiter(this, void 0, void 0, function* () {
            window.location.reload();
        }));
        const box = unwrapWidget(messageBox);
        box.style.fontSize = 'calc(1em * 0.875)';
        return box;
    }

    /**
     * Automatically applies a change tag to edits made by the user if
     * a change tag was provided in the configuration.
     *
     * @param config
     * @return A spreadable Object containing the `tags` parameter for `action=edit`.
     */
    function changeTag(config) {
        return config.core.changeTag.get() ?
            { tags: config.core.changeTag.get() } :
            {};
    }

    /**
     * Wiki-wide configuration. This is applied to all users of the wiki, and has
     * the potential to break things for EVERYONE if not set to proper values.
     *
     * As much as possible, the correct configuration location should be protected
     * to avoid vandalism or bad-faith changes.
     *
     * This configuration works if specific settings are set. In other words, some
     * features of Deputy are disabled unless Deputy has been configured. This is
     * to avoid messing with existing on-wiki processes.
     */
    class WikiConfiguration extends ConfigurationBase {
        /**
         *
         * @param sourcePage
         * @param serializedData
         * @param editable Whether the configuration is editable by the current user or not.
         */
        constructor(sourcePage, serializedData, editable) {
            var _a;
            super();
            this.sourcePage = sourcePage;
            this.serializedData = serializedData;
            this.editable = editable;
            // Used to avoid circular dependencies.
            this.static = WikiConfiguration;
            this.core = {
                /**
                 * Numerical code that identifies this config version. Increments for every breaking
                 * configuration file change.
                 */
                configVersion: new Setting({
                    defaultValue: WikiConfiguration.configVersion,
                    displayOptions: { hidden: true },
                    alwaysSave: true
                }),
                lastEdited: new Setting({
                    defaultValue: 0,
                    displayOptions: { hidden: true },
                    alwaysSave: true
                }),
                dispatchRoot: new Setting({
                    serialize: (v) => v.href,
                    deserialize: (v) => new URL(v),
                    defaultValue: new URL('https://deputy.toolforge.org/'),
                    displayOptions: { type: 'text' },
                    alwaysSave: true
                }),
                changeTag: new Setting({
                    defaultValue: null,
                    displayOptions: { type: 'text' }
                })
            };
            this.cci = {
                enabled: new Setting({
                    defaultValue: false,
                    displayOptions: { type: 'checkbox' }
                }),
                rootPage: new Setting({
                    serialize: (v) => v === null || v === void 0 ? void 0 : v.getPrefixedText(),
                    deserialize: (v) => new mw.Title(v),
                    defaultValue: null,
                    displayOptions: { type: 'page' }
                }),
                headingMatch: new Setting({
                    defaultValue: '(Page|Article|Local file|File)s? \\d+ (to|through) \\d+',
                    displayOptions: { type: 'text' }
                }),
                collapseTop: new Setting({
                    defaultValue: collapseTop,
                    displayOptions: { type: 'code' }
                }),
                collapseBottom: new Setting({
                    defaultValue: collapseBottom,
                    displayOptions: { type: 'code' }
                }),
                earwigRoot: new Setting({
                    serialize: (v) => v.href,
                    deserialize: (v) => new URL(v),
                    defaultValue: new URL('https://copyvios.toolforge.org/'),
                    displayOptions: { type: 'text' },
                    alwaysSave: true
                })
            };
            this.ante = {
                enabled: new Setting({
                    defaultValue: false,
                    displayOptions: { type: 'checkbox' }
                })
            };
            this.ia = {
                enabled: new Setting({
                    defaultValue: false,
                    displayOptions: { type: 'checkbox' }
                }),
                rootPage: new Setting({
                    serialize: (v) => v === null || v === void 0 ? void 0 : v.getPrefixedText(),
                    deserialize: (v) => new mw.Title(v),
                    defaultValue: null,
                    displayOptions: { type: 'page' }
                }),
                subpageFormat: new Setting({
                    defaultValue: 'YYYY MMMM D',
                    displayOptions: { type: 'text' }
                }),
                preload: new Setting({
                    serialize: (v) => { var _a, _b; return ((_b = (_a = v === null || v === void 0 ? void 0 : v.trim()) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0) === 0 ? null : v.trim(); },
                    defaultValue: null,
                    displayOptions: { type: 'page' }
                }),
                allowPresumptive: new Setting({
                    defaultValue: true,
                    displayOptions: { type: 'checkbox' }
                }),
                listingWikitext: new Setting({
                    defaultValue: listingWikitext,
                    displayOptions: { type: 'code' }
                }),
                /**
                 * $1 - Title of the batch
                 * $2 - List of pages (newlines should be added in batchListingPageWikitext).
                 * $3 - User comment
                 */
                batchListingWikitext: new Setting({
                    defaultValue: batchListingWikitext,
                    displayOptions: { type: 'code' }
                }),
                /**
                 * $1 - Page to include
                 */
                batchListingPageWikitext: new Setting({
                    defaultValue: batchListingPageWikitext,
                    displayOptions: { type: 'code' }
                }),
                /**
                 * @see {@link CopyrightProblemsListing#articleCvRegex}
                 *
                 * This should match both normal and batch listings.
                 */
                listingWikitextMatch: new Setting({
                    defaultValue: '(\\*\\s*)?\\[\\[([^\\]]+)\\]\\]',
                    displayOptions: { type: 'code' }
                }),
                hideTemplate: new Setting({
                    defaultValue: copyvioTop,
                    displayOptions: { type: 'code' }
                }),
                hideTemplateBottom: new Setting({
                    defaultValue: copyvioBottom,
                    displayOptions: { type: 'code' }
                }),
                responses: new Setting(Object.assign(Object.assign({}, Setting.basicSerializers), { defaultValue: null, displayOptions: { type: 'unimplemented' } }))
            };
            this.type = 'wiki';
            this.all = { core: this.core, cci: this.cci, ante: this.ante, ia: this.ia };
            /**
             * Set to true when this configuration is outdated based on latest data. Usually adds banners
             * to UI interfaces saying a new version of the configuration is available, and that it should
             * be used whenever possible.
             *
             * TODO: This doesn't do what the documentations says yet.
             */
            this.outdated = false;
            if (serializedData) {
                this.deserialize(serializedData);
            }
            if ((_a = window.deputy) === null || _a === void 0 ? void 0 : _a.comms) {
                // Communications is available. Register a listener.
                window.deputy.comms.addEventListener('wikiConfigUpdate', (e) => {
                    this.update(Object.assign({}, e.data.config, {
                        title: normalizeTitle(e.data.config.title)
                    }));
                });
            }
        }
        /**
         * Loads the configuration from a set of possible sources.
         *
         * @param sourcePage The specific page to load from
         * @return A WikiConfiguration object
         */
        static load(sourcePage) {
            return __awaiter(this, void 0, void 0, function* () {
                if (sourcePage) {
                    // Explicit source given. Do not load from local cache.
                    return this.loadFromWiki(sourcePage);
                }
                else {
                    return this.loadFromLocal();
                }
            });
        }
        /**
         * Loads the wiki configuration from localStorage and/or MediaWiki
         * settings. This allows for faster loads at the expense of a (small)
         * chance of outdated configuration.
         *
         * The localStorage layer allows fast browser-based caching. If a user
         * is logging in again on another device, the user configuration
         * will automatically be sent to the client, lessening turnaround time.
         * If all else fails, the configuration will be loaded from the wiki.
         *
         * @return A WikiConfiguration object.
         */
        static loadFromLocal() {
            return __awaiter(this, void 0, void 0, function* () {
                let configInfo;
                // If `mw.storage.get` returns `false` or `null`, it'll be thrown up.
                let rawConfigInfo = mw.storage.get(WikiConfiguration.optionKey);
                // Try to grab it from user options, if it exists.
                if (!rawConfigInfo) {
                    rawConfigInfo = mw.user.options.get(WikiConfiguration.optionKey);
                }
                if (typeof rawConfigInfo === 'string') {
                    try {
                        configInfo = JSON.parse(rawConfigInfo);
                    }
                    catch (e) {
                        // Bad local! Switch to non-local.
                        console.error('Failed to get Deputy wiki configuration', e);
                        return this.loadFromWiki();
                    }
                }
                else {
                    console.log('No locally-cached Deputy configuration, pulling from wiki.');
                    return this.loadFromWiki();
                }
                if (configInfo) {
                    return new WikiConfiguration(new mw.Title(configInfo.title.title, configInfo.title.namespace), JSON.parse(configInfo.wt), configInfo.editable);
                }
                else {
                    return this.loadFromWiki();
                }
            });
        }
        /**
         * Loads the configuration from the current wiki.
         *
         * @param sourcePage The specific page to load from
         * @return A WikiConfiguration object
         */
        static loadFromWiki(sourcePage) {
            return __awaiter(this, void 0, void 0, function* () {
                const configPage = sourcePage ? Object.assign({ title: sourcePage }, yield (() => __awaiter(this, void 0, void 0, function* () {
                    const content = yield getPageContent(sourcePage, {
                        prop: 'revisions|info',
                        intestactions: 'edit',
                        fallbacktext: '{}'
                    });
                    return {
                        wt: content,
                        editable: content.page.actions.edit
                    };
                }))()) : yield this.loadConfigurationWikitext();
                try {
                    // Attempt save of configuration to local options (if not explicitly loaded)
                    if (sourcePage == null) {
                        mw.storage.set(WikiConfiguration.optionKey, JSON.stringify(configPage));
                    }
                    return new WikiConfiguration(configPage.title, JSON.parse(configPage.wt), configPage.editable);
                }
                catch (e) {
                    console.error(e, configPage);
                    mw.hook('deputy.i18nDone').add(function notifyConfigFailure() {
                        mw.notify(mw.msg('deputy.loadError.wikiConfig'), {
                            type: 'error'
                        });
                        mw.hook('deputy.i18nDone').remove(notifyConfigFailure);
                    });
                    return null;
                }
            });
        }
        /**
         * Loads the wiki-wide configuration from a set of predefined locations.
         * See {@link WikiConfiguration#configLocations} for a full list.
         *
         * @return The string text of the raw configuration, or `null` if a configuration was not found.
         */
        static loadConfigurationWikitext() {
            return __awaiter(this, void 0, void 0, function* () {
                const response = yield MwApi.action.get({
                    action: 'query',
                    prop: 'revisions|info',
                    rvprop: 'content',
                    rvslots: 'main',
                    rvlimit: 1,
                    intestactions: 'edit',
                    redirects: true,
                    titles: WikiConfiguration.configLocations.join('|')
                });
                const redirects = toRedirectsObject(response.query.redirects, response.query.normalized);
                for (const page of WikiConfiguration.configLocations) {
                    const title = normalizeTitle(redirects[page] || page).getPrefixedText();
                    const pageInfo = response.query.pages.find((p) => p.title === title);
                    if (!pageInfo.missing) {
                        return {
                            title: normalizeTitle(pageInfo.title),
                            wt: pageInfo.revisions[0].slots.main.content,
                            editable: pageInfo.actions.edit
                        };
                    }
                }
                return null;
            });
        }
        /**
         * Check if the current page being viewed is a valid configuration page.
         *
         * @param page
         * @return `true` if the current page is a valid configuration page.
         */
        static isConfigurationPage(page) {
            if (page == null) {
                page = new mw.Title(mw.config.get('wgPageName'));
            }
            return this.configLocations.some((v) => equalTitle(page, normalizeTitle(v)));
        }
        /**
         * Check for local updates, and update the local configuration as needed.
         *
         * @param sourceConfig A serialized version of the configuration based on a wiki
         * page configuration load.
         */
        update(sourceConfig) {
            return __awaiter(this, void 0, void 0, function* () {
                // Asynchronously load from the wiki.
                let fromWiki;
                if (sourceConfig) {
                    fromWiki = sourceConfig;
                }
                else {
                    // Asynchronously load from the wiki.
                    fromWiki = yield WikiConfiguration.loadConfigurationWikitext();
                    if (fromWiki == null) {
                        // No configuration found on the wiki.
                        return;
                    }
                }
                const liveWikiConfig = JSON.parse(fromWiki.wt);
                // Attempt save if on-wiki config found and doesn't match local.
                // Doesn't need to be from the same config page, since this usually means a new config
                // page was made, and we need to switch to it.
                if (this.core.lastEdited.get() < liveWikiConfig.core.lastEdited) {
                    if (liveWikiConfig.core.configVersion > WikiConfiguration.configVersion) {
                        // Don't update if the config version is higher than ours. We don't want
                        // to load in the config of a newer version, as it may break things.
                        // Deputy should load in the newer version of the script soon enough,
                        // and the config will be parsed by a version that supports it.
                        console.warn(`Deputy expects wiki configuration version ${this.core.configVersion.get()}, but found ${liveWikiConfig.core.configVersion}. New configuration will not be loaded.`);
                        return;
                    }
                    else if (liveWikiConfig.core.configVersion < WikiConfiguration.configVersion) {
                        // Version change detected.
                        // Do nothing... for now.
                        // HINT: Update configuration
                        console.warn(`Deputy expects wiki configuration version ${this.core.configVersion.get()}, but found ${liveWikiConfig.core.configVersion}. Proceeding anyway...`);
                    }
                    const onSuccess = () => {
                        var _a;
                        // Only mark outdated after saving, so we don't indirectly cause a save operation
                        // to cancel.
                        this.outdated = true;
                        // Attempt to add site notice.
                        if (document.querySelector('.dp-wikiConfigUpdateMessage') == null) {
                            (_a = document.getElementById('siteNotice')) === null || _a === void 0 ? void 0 : _a.insertAdjacentElement('afterend', ConfigurationReloadBanner());
                        }
                    };
                    // If updated from a source config (other Deputy tab), do not attempt to save
                    // to MediaWiki settings. This is most likely already saved by the original tab
                    // that sent the comms message.
                    if (!sourceConfig) {
                        // Use `liveWikiConfig`, since this contains the compressed version and is more
                        // bandwidth-friendly.
                        const rawConfigInfo = JSON.stringify({
                            title: fromWiki.title,
                            editable: fromWiki.editable,
                            wt: JSON.stringify(liveWikiConfig)
                        });
                        // Save to local storage.
                        mw.storage.set(WikiConfiguration.optionKey, rawConfigInfo);
                        // Save to user options (for faster first-load times).
                        yield MwApi.action.saveOption(WikiConfiguration.optionKey, rawConfigInfo).then(() => {
                            var _a;
                            if ((_a = window.deputy) === null || _a === void 0 ? void 0 : _a.comms) {
                                // Broadcast the update to other tabs.
                                window.deputy.comms.send({
                                    type: 'wikiConfigUpdate',
                                    config: {
                                        title: fromWiki.title.getPrefixedText(),
                                        editable: fromWiki.editable,
                                        wt: liveWikiConfig
                                    }
                                });
                            }
                            onSuccess();
                        }).catch(() => {
                            // silently fail
                        });
                    }
                    else {
                        onSuccess();
                    }
                }
            });
        }
        /**
         * Saves the configuration on-wiki. Does not automatically generate overrides.
         */
        save() {
            return __awaiter(this, void 0, void 0, function* () {
                // Update last edited number
                this.core.lastEdited.set(Date.now());
                yield MwApi.action.postWithEditToken(Object.assign(Object.assign({}, changeTag(yield window.deputy.getWikiConfig())), { action: 'edit', title: this.sourcePage.getPrefixedText(), text: JSON.stringify(this.serialize()) }));
            });
        }
        /**
         * Check if the current page being viewed is the active configuration page.
         *
         * @param page
         * @return `true` if the current page is the active configuration page.
         */
        onConfigurationPage(page) {
            return equalTitle(page !== null && page !== void 0 ? page : mw.config.get('wgPageName'), this.sourcePage);
        }
        /**
         * Actually displays the banner which allows an editor to modify the configuration.
         */
        displayEditBanner() {
            return __awaiter(this, void 0, void 0, function* () {
                mw.loader.using(['oojs', 'oojs-ui'], () => {
                    if (document.getElementsByClassName('deputy-wikiConfig-intro').length > 0) {
                        return;
                    }
                    document.getElementById('mw-content-text').insertAdjacentElement('afterbegin', WikiConfigurationEditIntro(this));
                });
            });
        }
        /**
         * Shows the configuration edit intro banner, if applicable on this page.
         *
         * @return void
         */
        prepareEditBanners() {
            return __awaiter(this, void 0, void 0, function* () {
                if (['view', 'diff'].indexOf(mw.config.get('wgAction')) === -1) {
                    return;
                }
                if (document.getElementsByClassName('deputy-wikiConfig-intro').length > 0) {
                    return;
                }
                if (this.onConfigurationPage()) {
                    return this.displayEditBanner();
                }
                else if (WikiConfiguration.isConfigurationPage()) {
                    return this.displayEditBanner();
                }
            });
        }
    }
    WikiConfiguration.configVersion = 2;
    WikiConfiguration.optionKey = 'userjs-deputy-wiki';
    WikiConfiguration.configLocations = [
        'MediaWiki:Deputy-config.json',
        // Prioritize interface protected page over Project namespace
        'User:Chlod/Scripts/Deputy/configuration.json',
        'Project:Deputy/configuration.json'
    ];

    /**
     * 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;
        }
        /**
         * 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.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.getName()]) === null || _a === void 0 ? void 0 : _a.enabled.get()) !== true) {
                    // Stop loading here.
                    console.warn(`[Deputy] 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 deputyIaEnglish = {
    	"deputy.ia.content.respond": "Responding to [[$1#$2|$2]]",
    	"deputy.ia.content.close": "-1) (Responding to [[$1#$2|$2]]",
    	"deputy.ia.content.listing": "Adding listing for [[$1#$2|$2]]",
    	"deputy.ia.content.batchListing": "Adding batch listing: \"[[$1#$2|$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 [[$1#$2|$3]] to [[$1#$4|$5]] for suspected or complicated copyright issues",
    	"deputy.ia.content.listing.pd": "Adding listing for [[$1#$2|$2]] (presumptive deletion)",
    	"deputy.ia.content.batchListing.pd": "Adding batch listing: \"[[$1#$2|$2]]\" (presumptive deletion)",
    	"deputy.ia.content.listingComment.pd": "presumptive deletion from [[$1/$2|$2]]. $3",
    	"deputy.ia.content.batchListingComment.pd": "Presumptive deletion from [[$1/$2|$2]]. $3",
    	"deputy.ia.content.hideAll.pd": "Hiding page content for presumptive deletion; see [[$1/$2]]",
    	"deputy.ia.content.hide.pd": "Hiding sections [[$1#$2|$3]] to [[$1#$4|$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 {{Template:Cup}} 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 <b>{{wikilink:$1}}</b>",
    	"deputy.ia.report.page": "Currently reporting <b>{{wikilink:$1}}</b>",
    	"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:copyvio}} 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:copyvio}} 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"
    };

    /**
     * Appends extra information to an edit summary (also known as the "advert").
     *
     * @param editSummary The edit summary
     * @param config The user's configuration. Used to get the "danger mode" setting.
     * @return The decorated edit summary (in wikitext)
     */
    function decorateEditSummary (editSummary, config) {
        var _a;
        const dangerMode = (_a = config === null || config === void 0 ? void 0 : config.core.dangerMode.get()) !== null && _a !== void 0 ? _a : false;
        return `${editSummary} ([[Wikipedia:Deputy|Deputy]] v${version}${dangerMode ? '!' : ''})`;
    }

    /**
     * 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() ? `{{subst:${
            // Only trim last newline, if any.
            config.ia.preload.get().replace(/\n$/, '')}}}\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:
         * ```
         * * {{subst:article-cv|Example}} <comment> ~~~~
         * ```
         *
         * 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:
         * ```
         * ;{{anchor|1=<title>}}<title>
         * * {{subst:article-cv|1=Page 1}}
         * * {{subst:article-cv|1=Page 2}}
         * <comment> ~~~~
         * ```
         *
         * @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 `<a>`. 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 <b>existing</b> 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) {
            let listingPage = null;
            let previousPivot = (
            // Target the ol/ul element itself if a list, target the <p> if not a list.
            el.parentElement.tagName === 'LI' ? el.parentElement.parentElement : el.parentElement).previousElementSibling;
            while (previousPivot != null && previousPivot.tagName !== 'H4') {
                previousPivot = previousPivot.previousElementSibling;
            }
            if (previousPivot == null) {
                return false;
            }
            if (previousPivot.querySelector('.mw-headline') != null) {
                // At this point, previousPivot is likely a MediaWiki level 4 heading.
                const h4Anchor = previousPivot.querySelector('.mw-headline a');
                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 (`<a>`) which
         * makes up a "listing" (a page for checking on the Copyright Problems page).
         *
         * Detection is based on the {{article-cv}} template. Changes to the template
         * must be reflected here, with backwards compatibility for older listings.
         * The {{anchor}} 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 {{anchor}} 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.
                    console.warn(`Anchor and link mismatch for "${title.getPrefixedText()}".`, title, id);
                }
                // Checks for the <span class="plainlinks"> element.
                // This ensures that the listing came from {{article-cv}} 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 <h4> 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) {
                console.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 <h4> 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) {
                console.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(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. {{CPC}} 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()
                };
            });
        }
    }

    /**
     * Renders wikitext as HTML.
     *
     * @param wikitext
     * @param title
     * @param options
     */
    function renderWikitext(wikitext, title, options = {}) {
        return __awaiter(this, void 0, void 0, function* () {
            return MwApi.action.post(Object.assign({
                action: 'parse',
                title: title,
                text: wikitext,
                preview: true,
                disableeditsection: true,
                disablelimitreport: true
            }, options)).then((data) => {
                return Object.assign(data.parse.text, {
                    summary: data.parse.parsedsummary
                });
            });
        });
    }

    /**
     *
     */
    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) {
                    console.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;
    }

    /**
     * Swaps two elements in the DOM. Element 1 will be removed from the DOM, Element 2 will
     * be added in its place.
     *
     * @param element1 The element to remove
     * @param element2 The element to insert
     * @return `element2`, for chaining
     */
    function swapElements (element1, element2) {
        try {
            element1.insertAdjacentElement('afterend', element2);
            element1.parentElement.removeChild(element1);
            return element2;
        }
        catch (e) {
            console.error(e, { element1, element2 });
            // Caught for debug only. Rethrow.
            throw e;
        }
    }

    const exitBlockList = [];
    /**
     * Used to block an impending exit.
     *
     * @param event The unload event
     * @return `false`.
     */
    const exitBlock = (event) => {
        if (exitBlockList.length > 0) {
            event.preventDefault();
            return event.returnValue = false;
        }
    };
    window.addEventListener('beforeunload', exitBlock);
    /**
     * Blocks navigation to prevent data loss. This function takes in a
     * `key` parameter to identify which parts of the tool are blocking navigation.
     * The exit block will refuse to unlatch from the document if all keys are not
     * released with `unblockExit`.
     *
     * If no key is provided, this will unconditionally set the block. Running
     * any operation that updates the block list (e.g. `unblockExit` with a key
     * not blocked) will immediately unblock the page.
     *
     * @param key The key of the exit block.
     */
    function blockExit(key) {
        if (key) {
            if (exitBlockList.indexOf(key) === -1) {
                exitBlockList.push(key);
            }
        }
    }
    /**
     * Unblocks navigation. This function takes in a `key` parameter to identify
     * which part of the tool is no longer requiring a block. If other parts of
     * the tool still require blocking, the unblock function will remain on the
     * document.
     *
     * If no key is provided, this will dump all keys and immediate unblock exit.
     *
     * @param key The key of the exit block.
     */
    function unblockExit(key) {
        if (key) {
            const keyIndex = exitBlockList.indexOf(key);
            if (keyIndex !== -1) {
                exitBlockList.splice(keyIndex, 1);
            }
        }
        else {
            exitBlockList.splice(0, exitBlockList.length);
        }
    }

    /**
     * Evaluates any string using `mw.msg`. This handles internationalization of strings
     * that are loaded outside the script or asynchronously.
     *
     * @param string The string to evaluate
     * @param {...any} parameters Parameters to pass, if any
     * @return A mw.Message
     */
    function msgEval(string, ...parameters) {
        // Named parameters
        let named = {};
        if (typeof parameters[0] === 'object') {
            named = parameters.shift();
        }
        const m = new mw.Map();
        for (const [from, to] of Object.entries(named)) {
            string = string.replace(new RegExp(`\\$${from}`, 'g'), to);
        }
        m.set('msg', string);
        return new mw.Message(m, 'msg', parameters);
    }

    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 = h_1("div", { class: "ia-report-intro", dangerouslySetInnerHTML: mw.message('deputy.ia.report.intro', CopyrightProblemsPage.getCurrentListingPage().getPrefixedText()).parse() });
                    intro.querySelector('a').setAttribute('target', '_blank');
                    const page = h_1("div", { class: "ia-report-intro", dangerouslySetInnerHTML: mw.message('deputy.ia.report.page', this.page.getPrefixedText()).parse() });
                    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(/\[\[(.+?)(?:\|.*?)?]]/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);
            }
        }
        /**
         *
         */
        addNewListingsPanel() {
            document.querySelectorAll('.mw-headline > 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())) {
                    // Crawl backwards, avoiding common inline elements, to see if this is a standalone
                    // line within the rendered text.
                    let currentPivot = el.parentElement;
                    while (currentPivot !== null &&
                        ['I', 'B', 'SPAN', 'EM', 'STRONG'].indexOf(currentPivot.tagName) !== -1) {
                        currentPivot = currentPivot.parentElement;
                    }
                    // By this point, current pivot will be a <div>, <p>, or other usable element.
                    if (!el.parentElement.classList.contains('mw-headline') &&
                        (currentPivot == null ||
                            currentPivot.children.length > 1)) {
                        return;
                    }
                    else if (el.parentElement.classList.contains('mw-headline')) {
                        // "Edit source" button of an existing section heading.
                        let headingBottom = el.parentElement.parentElement.nextElementSibling;
                        let pos = 'beforebegin';
                        while (headingBottom != null &&
                            !/^H[123456]$/.test(headingBottom.tagName)) {
                            headingBottom = headingBottom.nextElementSibling;
                        }
                        if (headingBottom == null) {
                            headingBottom = el.parentElement.parentElement.parentElement;
                            pos = 'beforeend';
                        }
                        // Add below today's section header.
                        mw.loader.using([
                            'oojs-ui-core',
                            'oojs-ui.styles.icons-interactions',
                            'mediawiki.widgets',
                            'mediawiki.widgets.TitlesMultiselectWidget'
                        ], () => {
                            // H4
                            headingBottom.insertAdjacentElement(pos, NewCopyrightProblemsListing());
                        });
                    }
                    else {
                        mw.loader.using([
                            'oojs-ui-core',
                            'oojs-ui.styles.icons-interactions',
                            'mediawiki.widgets',
                            'mediawiki.widgets.TitlesMultiselectWidget'
                        ], () => {
                            swapElements(el, 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;}";

    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": "+{{FORMATNUM:$1}}",
    	"deputy.negativeDiff": "-{{FORMATNUM:$1}}",
    	"deputy.zeroDiff": "0",
    	"deputy.brokenDiff": "?",
    	"deputy.brokenDiff.explain": "The internal parent revision ID for this diff points to a non-existent revision. [[phab:T186280]] has more information.",
    	"deputy.moreInfo": "More information",
    	"deputy.dismiss": "Dismiss",
    	"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."
    };

    /**
     *
     */
    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;
                }
                yield Promise.all([
                    DeputyLanguage.load('shared', deputySharedEnglish),
                    DeputyLanguage.loadMomentLocale()
                ]);
                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* () {
                    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]);
                    }
                    this.windowManager.openWindow(this.dialog);
                }));
            });
        }
    }
    InfringementAssistant.dependencies = [
        'moment',
        'oojs-ui-core',
        'oojs-ui-widgets',
        'oojs-ui-windows',
        'mediawiki.util',
        'mediawiki.api',
        'mediawiki.Title',
        'mediawiki.widgets'
    ];

    /**
     * Gets the namespace ID from a canonical (not localized) namespace name.
     *
     * @param namespace The namespace to get
     * @return The namespace ID
     */
    function nsId(namespace) {
        return mw.config.get('wgNamespaceIds')[namespace.toLowerCase().replace(/ /g, '_')];
    }

    /**
     * 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';

    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.<id>.title
                    title: mw.msg(`deputy.announcement.${announcementId}.title`),
                    // Messages that can be used here:
                    // * deputy.announcement.<id>.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.<id>.<action id>.message
                            label: mw.msg(`deputy.announcement.${announcementId}.${action.id}.label`),
                            // Messages that can be used here:
                            // * deputy.announcement.<id>.<action id>.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 */ }
    // 		}
    // 	]
    // }
    };

    /**
     * This function handles IA loading when Deputy isn't present. When Deputy is not
     * present, the following must be done on our own:
     * (1) Instantiate an OOUI WindowManager
     * (2) Load language strings
     *
     * `preInit` handles all of those. This function simply calls it on run.
     *
     * @param window
     */
    ((window) => __awaiter(void 0, void 0, void 0, function* () {
        Recents.save();
        window.InfringementAssistant = new InfringementAssistant();
        yield window.InfringementAssistant.preInit();
        yield DeputyAnnouncements.init(window.InfringementAssistant.config);
    }))(window);

})();
// </nowiki>
// <3