Jump to content

User:Chlod/Scripts/Deputy/AttributionNoticeTemplateEditor.js

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Chlod (talk | contribs) at 23:47, 7 August 2022 (Uploading standalone version of ANTE for testing (as of [d3674f95] "0.0.4")). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
(diff) ← Previous revision | Latest revision (diff) | Newer revision → (diff)
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.
/*!
 * 
 *    ATTRIBUTION NOTICE TEMPLATE EDITOR
 * 
 *    Graphically edit {{copied}} templates on a page's talk page.
 * 
 *    ------------------------------------------------------------------------
 * 
 *    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
 *    the build process. To view the original source code, visit
 * 
 *        https://github.com/ChlodAlejandro/deputy
 * 
 */
// <nowiki>
/*!
 * @package tslib
 * @version 2.4.0
 * @license 0BSD
 * @author Microsoft Corp.
 * @url https://github.com/Microsoft/tslib
 *//*!
 * @package tsx-dom
 * @version 1.4.0
 * @license MIT
 * @author Santo Pfingsten
 * @url https://github.com/Lusito/tsx-dom
 *//*!
 * @package @chlodalejandro/parsoid
 * @version 2.0.0-9ffc1cc
 * @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());
        });
    }

    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;

    /**
     * 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 a OOUI Widget!');
        }
        return el.$element[0];
    }

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

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

    /**
     * Transforms the `redirects` object returned by MediaWiki's `query` action into an
     * object instead of an array.
     *
     * @param redirects
     * @return Redirects as an object
     */
    function toRedirectsObject(redirects) {
        if (redirects == null) {
            return {};
        }
        const out = {};
        for (const redirect of redirects) {
            out[redirect.from] = redirect.to;
        }
        return out;
    }

    /**
     * Copies text to the clipboard. Relies on the old style of clipboard copying
     * (using `document.execCommand` due to a lack of support for `navigator`-based
     * clipboard handling).
     *
     * @param text The text to copy to the clipboard.
     */
    function copyToClipboard (text) {
        const body = document.getElementsByTagName('body')[0];
        const textarea = document.createElement('textarea');
        textarea.value = text;
        textarea.style.position = 'fixed';
        textarea.style.left = '-100vw';
        textarea.style.top = '-100vh';
        body.appendChild(textarea);
        textarea.select();
        // noinspection JSDeprecatedSymbols
        document.execCommand('copy');
        body.removeChild(textarea);
    }

    /**
     * Performs {{yesno}}-based string interpretation.
     *
     * @param value The value to check
     * @param pull Depends which direction to pull unspecified values.
     * @return If `pull` is true,
     * any value that isn't explicitly a negative value will return `true`. Otherwise,
     * any value that isn't explicitly a positive value will return `false`.
     */
    function yesNo (value, pull = true) {
        if (pull) {
            return value !== false &&
                value !== 'no' &&
                value !== 'n' &&
                value !== 'false' &&
                value !== 'f' &&
                value !== 'off' &&
                +value !== 0;
        }
        else {
            return !(value !== true &&
                value !== 'yes' &&
                value !== 'y' &&
                value !== 't' &&
                value !== 'true' &&
                value !== 'on' &&
                +value !== 1);
        }
    }

    /**
     * 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) {
            return new mw.Title(mw.config.get('wgPageName'));
        }
        else {
            return null;
        }
    }

    /**
     * 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 InternalCopiedTemplateRowPage;
    /**
     * The UI representation of a {{copied}} template row. This refers to a set of `diff`, `to`,
     * or `from` parameters on each {{copied}} template.
     *
     * Note that "Page" in the class title does not refer to a MediaWiki page, but rather
     * a OOUI PageLayout.
     */
    function initCopiedTemplateRowPage() {
        InternalCopiedTemplateRowPage = class CopiedTemplateRowPage extends OO.ui.PageLayout {
            /**
             * @param config Configuration to be passed to the element.
             */
            constructor(config) {
                const { copiedTemplateRow, parent } = config;
                if (parent == null) {
                    throw new Error('Parent dialog (CopiedTemplateEditorDialog) is required');
                }
                else if (copiedTemplateRow == null) {
                    throw new Error('Reference row (CopiedTemplateRow) is required');
                }
                const finalConfig = {
                    classes: ['cte-page-row']
                };
                super(copiedTemplateRow.id, finalConfig);
                this.parent = parent;
                this.copiedTemplateRow = copiedTemplateRow;
                this.refreshLabel();
                this.copiedTemplateRow.parent.addEventListener('destroy', () => {
                    parent.rebuildPages();
                });
                this.copiedTemplateRow.parent.addEventListener('rowDelete', () => {
                    parent.rebuildPages();
                });
                this.$element.append(this.render().$element);
            }
            /**
             * Refreshes the page's label
             */
            refreshLabel() {
                if (equalTitle(this.copiedTemplateRow.from, normalizeTitle(this.copiedTemplateRow.parent.parsoid.getPage())
                    .getSubjectPage())) {
                    this.label = mw.message('deputy.cte.copied.entry.shortTo', this.copiedTemplateRow.to || '???').text();
                }
                else if (equalTitle(this.copiedTemplateRow.to, normalizeTitle(this.copiedTemplateRow.parent.parsoid.getPage())
                    .getSubjectPage())) {
                    this.label = mw.message('deputy.cte.copied.entry.shortFrom', this.copiedTemplateRow.from || '???').text();
                }
                else {
                    this.label = mw.message('deputy.cte.copied.entry.short', this.copiedTemplateRow.from || '???', this.copiedTemplateRow.to || '???').text();
                }
                if (this.outlineItem) {
                    this.outlineItem.setLabel(this.label);
                }
            }
            /**
             * Renders this page. Returns a FieldsetLayout OOUI widget.
             *
             * @return An OOUI FieldsetLayout
             */
            render() {
                this.layout = new OO.ui.FieldsetLayout({
                    icon: 'parameter',
                    label: mw.message('deputy.cte.copied.entry.label').text(),
                    classes: ['cte-fieldset']
                });
                this.layout.$element.append(this.renderButtons());
                this.layout.addItems(this.renderFields());
                return this.layout;
            }
            /**
             * Renders a set of buttons used to modify a specific {{copied}} template row.
             *
             * @return An array of OOUI FieldLayouts
             */
            renderButtons() {
                const deleteButton = new OO.ui.ButtonWidget({
                    icon: 'trash',
                    title: mw.message('deputy.cte.copied.entry.remove').text(),
                    framed: false,
                    flags: ['destructive']
                });
                deleteButton.on('click', () => {
                    this.copiedTemplateRow.parent.deleteRow(this.copiedTemplateRow);
                });
                const copyButton = new OO.ui.ButtonWidget({
                    icon: 'quotes',
                    title: mw.message('deputy.cte.copied.entry.copy').text(),
                    framed: false
                });
                copyButton.on('click', () => {
                    // TODO: Find out a way to i18n-ize this.
                    let attributionString = `[[WP:PATT|Attribution]]: Content ${this.copiedTemplateRow.merge ? 'merged' : 'partially copied'}`;
                    let lacking = false;
                    if (this.copiedTemplateRow.from != null &&
                        this.copiedTemplateRow.from.length !== 0) {
                        attributionString += ` from [[${this.copiedTemplateRow.from}]]`;
                    }
                    else {
                        lacking = true;
                        if (this.copiedTemplateRow.from_oldid != null) {
                            attributionString += ' from a page';
                        }
                    }
                    if (this.copiedTemplateRow.from_oldid != null) {
                        attributionString += ` as of revision [[Special:Diff/${this.copiedTemplateRow.from_oldid}|${this.copiedTemplateRow.from_oldid}]]`;
                    }
                    if (this.copiedTemplateRow.to_diff != null ||
                        this.copiedTemplateRow.to_oldid != null) {
                        // Shifting will ensure that `to_oldid` will be used if `to_diff` is missing.
                        const diffPart1 = this.copiedTemplateRow.to_oldid ||
                            this.copiedTemplateRow.to_diff;
                        const diffPart2 = this.copiedTemplateRow.to_diff ||
                            this.copiedTemplateRow.to_oldid;
                        attributionString += ` with [[Special:Diff/${diffPart1 === diffPart2 ? diffPart1 : `${diffPart1}/${diffPart2}`}|this edit]]`;
                    }
                    if (this.copiedTemplateRow.from != null &&
                        this.copiedTemplateRow.from.length !== 0) {
                        attributionString += `; refer to that page's [[Special:PageHistory/${this.copiedTemplateRow.from}|edit history]] for additional attribution`;
                    }
                    attributionString += '.';
                    copyToClipboard(attributionString);
                    if (lacking) {
                        mw.notify(mw.message('deputy.cte.copied.entry.copy.lacking').text(), { title: mw.message('deputy.cte').text(), type: 'warn' });
                    }
                    else {
                        mw.notify(mw.message('deputy.cte.copied.entry.copy.success').text(), { title: mw.message('deputy.cte').text() });
                    }
                });
                return h_1("div", { style: {
                        float: 'right',
                        position: 'absolute',
                        top: '0.5em',
                        right: '0.5em'
                    } },
                    unwrapWidget(copyButton),
                    unwrapWidget(deleteButton));
            }
            /**
             * Renders a set of OOUI InputWidgets and FieldLayouts, eventually returning an
             * array of each FieldLayout to append to the FieldsetLayout.
             *
             * @return An array of OOUI FieldLayouts
             */
            renderFields() {
                const copiedTemplateRow = this.copiedTemplateRow;
                const parsedDate = (copiedTemplateRow.date == null || copiedTemplateRow.date.trim().length === 0) ?
                    undefined : (!isNaN(new Date(copiedTemplateRow.date.trim() + ' UTC').getTime()) ?
                    (new Date(copiedTemplateRow.date.trim() + ' UTC')) : (!isNaN(new Date(copiedTemplateRow.date.trim()).getTime()) ?
                    new Date(copiedTemplateRow.date.trim()) : null));
                this.inputs = {
                    from: new mw.widgets.TitleInputWidget({
                        $overlay: this.parent.$overlay,
                        placeholder: mw.message('deputy.cte.copied.from.placeholder').text(),
                        value: copiedTemplateRow.from,
                        validate: /^.+$/g
                    }),
                    from_oldid: new OO.ui.TextInputWidget({
                        placeholder: mw.message('deputy.cte.copied.from_oldid.placeholder').text(),
                        value: copiedTemplateRow.from_oldid,
                        validate: /^\d*$/
                    }),
                    to: new mw.widgets.TitleInputWidget({
                        $overlay: this.parent.$overlay,
                        placeholder: mw.message('deputy.cte.copied.to.placeholder').text(),
                        value: copiedTemplateRow.to
                    }),
                    to_diff: new OO.ui.TextInputWidget({
                        placeholder: mw.message('deputy.cte.copied.to_diff.placeholder').text(),
                        value: copiedTemplateRow.to_diff,
                        validate: /^\d*$/
                    }),
                    // Advanced options
                    to_oldid: new OO.ui.TextInputWidget({
                        placeholder: mw.message('deputy.cte.copied.to_oldid.placeholder').text(),
                        value: copiedTemplateRow.to_oldid,
                        validate: /^\d*$/
                    }),
                    diff: new OO.ui.TextInputWidget({
                        placeholder: mw.message('deputy.cte.copied.diff.placeholder').text(),
                        value: copiedTemplateRow.diff
                    }),
                    merge: new OO.ui.CheckboxInputWidget({
                        value: yesNo(copiedTemplateRow.merge)
                    }),
                    afd: new OO.ui.TextInputWidget({
                        placeholder: mw.message('deputy.cte.copied.afd.placeholder').text(),
                        value: copiedTemplateRow.afd,
                        disabled: copiedTemplateRow.merge === undefined,
                        // Prevent people from adding the WP:AFD prefix.
                        validate: /^((?!W(iki)?p(edia)?:(A(rticles)?[ _]?f(or)?[ _]?d(eletion)?\/)).+|$)/gi
                    }),
                    date: new mw.widgets.datetime.DateTimeInputWidget({
                        // calendar: {
                        //     $overlay: parent["$overlay"]
                        // },
                        calendar: null,
                        icon: 'calendar',
                        clearable: true,
                        value: parsedDate
                    }),
                    toggle: new OO.ui.ToggleSwitchWidget()
                };
                const diffConvert = new OO.ui.ButtonWidget({
                    label: 'Convert'
                });
                // const dateButton = new OO.ui.PopupButtonWidget({
                //     icon: "calendar",
                //     title: "Select a date"
                // });
                this.fieldLayouts = {
                    from: new OO.ui.FieldLayout(this.inputs.from, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.copied.from.label').text(),
                        align: 'top',
                        help: mw.message('deputy.cte.copied.from.help').text()
                    }),
                    from_oldid: new OO.ui.FieldLayout(this.inputs.from_oldid, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.copied.from_oldid.label').text(),
                        align: 'left',
                        help: mw.message('deputy.cte.copied.from_oldid.help').text()
                    }),
                    to: new OO.ui.FieldLayout(this.inputs.to, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.copied.to.label').text(),
                        align: 'top',
                        help: mw.message('deputy.cte.copied.to.help').text()
                    }),
                    to_diff: new OO.ui.FieldLayout(this.inputs.to_diff, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.copied.to_diff.label').text(),
                        align: 'left',
                        help: mw.message('deputy.cte.copied.to_diff.help').text()
                    }),
                    // Advanced options
                    to_oldid: new OO.ui.FieldLayout(this.inputs.to_oldid, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.copied.to_oldid.label').text(),
                        align: 'left',
                        help: mw.message('deputy.cte.copied.to_oldid.help').text()
                    }),
                    diff: new OO.ui.ActionFieldLayout(this.inputs.diff, diffConvert, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.copied.diff.label').text(),
                        align: 'inline',
                        help: new OO.ui.HtmlSnippet(mw.message('deputy.cte.copied.diff.help').plain())
                    }),
                    merge: new OO.ui.FieldLayout(this.inputs.merge, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.copied.merge.label').text(),
                        align: 'inline',
                        help: mw.message('deputy.cte.copied.merge.help').text()
                    }),
                    afd: new OO.ui.FieldLayout(this.inputs.afd, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.copied.afd.label').text(),
                        align: 'left',
                        help: mw.message('deputy.cte.copied.afd.help').text()
                    }),
                    date: new OO.ui.FieldLayout(this.inputs.date, {
                        align: 'inline',
                        classes: ['cte-fieldset-date']
                    }),
                    toggle: new OO.ui.FieldLayout(this.inputs.toggle, {
                        label: mw.message('deputy.cte.copied.advanced').text(),
                        align: 'inline',
                        classes: ['cte-fieldset-advswitch']
                    })
                };
                if (parsedDate === null) {
                    this.fieldLayouts.date.setWarnings([
                        mw.message('deputy.cte.copied.dateInvalid', copiedTemplateRow.date).text()
                    ]);
                }
                // Define options that get hidden when advanced options are toggled
                const advancedOptions = [
                    this.fieldLayouts.to_oldid,
                    this.fieldLayouts.diff,
                    this.fieldLayouts.merge,
                    this.fieldLayouts.afd
                ];
                // Self-imposed deprecation notice in order to steer away from plain URL
                // diff links. This will, in the long term, make it easier to parse out
                // and edit {{copied}} templates.
                const diffDeprecatedNotice = new OO.ui.HtmlSnippet(mw.message('deputy.cte.copied.diffDeprecate').plain());
                // Hide advanced options
                advancedOptions.forEach((e) => {
                    e.toggle(false);
                });
                // ...except for `diff` if it's supplied (legacy reasons)
                if (copiedTemplateRow.diff) {
                    this.fieldLayouts.diff.toggle(true);
                    this.fieldLayouts.diff.setWarnings([diffDeprecatedNotice]);
                }
                else {
                    diffConvert.setDisabled(true);
                }
                // Attach event listeners
                this.inputs.diff.on('change', () => {
                    if (this.inputs.diff.getValue().length > 0) {
                        try {
                            // Check if the diff URL is from this wiki.
                            if (
                            // TODO: l10n
                            new URL(this.inputs.diff.getValue(), window.location.href).host === window.location.host) {
                                // Prefer `to_oldid` and `to_diff`
                                this.fieldLayouts.diff.setWarnings([diffDeprecatedNotice]);
                                diffConvert.setDisabled(false);
                            }
                            else {
                                this.fieldLayouts.diff.setWarnings([]);
                                diffConvert.setDisabled(true);
                            }
                        }
                        catch (e) {
                            // Clear warnings just to be safe.
                            this.fieldLayouts.diff.setWarnings([]);
                            diffConvert.setDisabled(true);
                        }
                    }
                    else {
                        this.fieldLayouts.diff.setWarnings([]);
                        diffConvert.setDisabled(true);
                    }
                });
                this.inputs.merge.on('change', (value) => {
                    this.inputs.afd.setDisabled(!value);
                });
                this.inputs.toggle.on('change', (value) => {
                    advancedOptions.forEach((e) => {
                        e.toggle(value);
                    });
                    this.fieldLayouts.to_diff.setLabel(value ? 'Ending revision ID' : 'Revision ID');
                });
                this.inputs.from.on('change', () => {
                    this.refreshLabel();
                });
                this.inputs.to.on('change', () => {
                    this.refreshLabel();
                });
                for (const _field in this.inputs) {
                    if (_field === 'toggle') {
                        continue;
                    }
                    const field = _field;
                    const input = this.inputs[field];
                    // Attach the change listener
                    input.on('change', (value) => {
                        if (input instanceof OO.ui.CheckboxInputWidget) {
                            // Specific to `merge`. Watch out before adding more checkboxes.
                            this.copiedTemplateRow[field] = value ? 'yes' : '';
                        }
                        else if (input instanceof mw.widgets.datetime.DateTimeInputWidget) {
                            this.copiedTemplateRow[field] =
                                new Date(value).toLocaleDateString('en-GB', {
                                    year: 'numeric', month: 'long', day: 'numeric'
                                });
                            if (value.length > 0) {
                                this.fieldLayouts[field].setWarnings([]);
                            }
                        }
                        else {
                            this.copiedTemplateRow[field] = value;
                        }
                        copiedTemplateRow.parent.save();
                        this.refreshLabel();
                    });
                    if (input instanceof OO.ui.TextInputWidget) {
                        // Rechecks the validity of the field.
                        input.setValidityFlag();
                    }
                }
                // Diff convert click handler
                diffConvert.on('click', this.convertDeprecatedDiff.bind(this));
                return getObjectValues(this.fieldLayouts);
            }
            /**
             * Converts a raw diff URL on the same wiki as the current to use `to` and `to_oldid`
             * (and `to_diff`, if available).
             */
            convertDeprecatedDiff() {
                const value = this.inputs.diff.getValue();
                try {
                    const url = new URL(value, window.location.href);
                    if (!value) {
                        return;
                    }
                    if (url.host === window.location.host) {
                        console.warn('Attempted to convert a diff URL from another wiki.');
                    }
                    // From the same wiki, accept deprecation
                    // Attempt to get values from URL parameters (when using `/w/index.php?action=diff`)
                    let oldid = url.searchParams.get('oldid');
                    let diff = url.searchParams.get('diff');
                    const title = url.searchParams.get('title');
                    // Attempt to get values from Special:Diff short-link
                    const diffSpecialPageCheck = /\/wiki\/Special:Diff\/(prev|next|\d+)(?:\/(prev|next|\d+))?/.exec(url.pathname);
                    if (diffSpecialPageCheck != null) {
                        if (diffSpecialPageCheck[1] != null &&
                            diffSpecialPageCheck[2] == null) {
                            // Special:Diff/diff
                            diff = diffSpecialPageCheck[1];
                        }
                        else if (diffSpecialPageCheck[1] != null &&
                            diffSpecialPageCheck[2] != null) {
                            // Special:Diff/oldid/diff
                            oldid = diffSpecialPageCheck[1];
                            diff = diffSpecialPageCheck[2];
                        }
                    }
                    const confirmProcess = new OO.ui.Process();
                    for (const [_rowName, newValue] of [
                        ['to_oldid', oldid],
                        ['to_diff', diff],
                        ['to', title]
                    ]) {
                        const rowName = _rowName;
                        if (newValue == null) {
                            continue;
                        }
                        if (
                        // Field has an existing value
                        this.copiedTemplateRow[rowName] != null &&
                            this.copiedTemplateRow[rowName].length > 0 &&
                            this.copiedTemplateRow[rowName] !== newValue) {
                            confirmProcess.next(() => __awaiter(this, void 0, void 0, function* () {
                                const confirmPromise = OO.ui.confirm(mw.message('deputy.cte.copied.diffDeprecate.replace', rowName, this.copiedTemplateRow[rowName], newValue).text());
                                confirmPromise.done((confirmed) => {
                                    if (confirmed) {
                                        this.inputs[rowName].setValue(newValue);
                                    }
                                });
                                return confirmPromise;
                            }));
                        }
                        else {
                            this.inputs[rowName].setValue(newValue);
                        }
                    }
                    confirmProcess.next(() => {
                        this.copiedTemplateRow.parent.save();
                        this.inputs.diff.setValue('');
                        if (!this.inputs.toggle.getValue()) {
                            this.fieldLayouts.diff.toggle(false);
                        }
                    });
                    confirmProcess.execute();
                }
                catch (e) {
                    console.error('Cannot convert `diff` parameter to URL.', e);
                    OO.ui.alert(mw.message('deputy.cte.copied.diffDeprecate.failed').text());
                }
            }
            /**
             * Sets up the outline item of this page. Used in the BookletLayout.
             */
            setupOutlineItem() {
                /** @member any */
                if (this.outlineItem !== undefined) {
                    /** @member any */
                    this.outlineItem
                        .setMovable(true)
                        .setRemovable(true)
                        .setIcon('parameter')
                        .setLevel(1)
                        .setLabel(this.label);
                }
            }
        };
    }
    /**
     * Creates a new CopiedTemplateRowPage.
     *
     * @param config Configuration to be passed to the element.
     * @return A CopiedTemplateRowPage object
     */
    function CopiedTemplateRowPage (config) {
        if (!InternalCopiedTemplateRowPage) {
            initCopiedTemplateRowPage();
        }
        return new InternalCopiedTemplateRowPage(config);
    }

    /**
     * An attribution notice's row or entry.
     */
    class AttributionNoticeRow {
        /**
         *
         * @param parent
         */
        constructor(parent) {
            this._parent = parent;
            const r = btoa((Math.random() * 10000).toString()).slice(0, 6);
            this.name = this.parent.name + '#' + r;
            this.id = btoa(parent.node.getTarget().wt) + '-' + this.name;
        }
        /**
         * @return The parent of this attribution notice row.
         */
        get parent() {
            return this._parent;
        }
        /**
         * Sets the parent. Automatically moves this template from one
         * parent's row set to another.
         *
         * @param newParent The new parent.
         */
        set parent(newParent) {
            this._parent.deleteRow(this);
            newParent.addRow(this);
            this._parent = newParent;
        }
        /**
         * Clones this row.
         *
         * @param parent The parent of this new row.
         * @return The cloned row
         */
        clone(parent) {
            // noinspection JSCheckFunctionSignatures
            return new this.constructor(this, parent);
        }
    }

    const copiedTemplateRowParameters = [
        'from', 'from_oldid', 'to', 'to_diff',
        'to_oldid', 'diff', 'date', 'afd', 'merge'
    ];
    /**
     * Represents a row/entry in a {{copied}} template.
     */
    class CopiedTemplateRow extends AttributionNoticeRow {
        // noinspection JSDeprecatedSymbols
        /**
         * Creates a new RawCopiedTemplateRow
         *
         * @param rowObjects
         * @param parent
         */
        constructor(rowObjects, parent) {
            super(parent);
            this.from = rowObjects.from;
            // eslint-disable-next-line camelcase
            this.from_oldid = rowObjects.from_oldid;
            this.to = rowObjects.to;
            // eslint-disable-next-line camelcase
            this.to_diff = rowObjects.to_diff;
            // eslint-disable-next-line camelcase
            this.to_oldid = rowObjects.to_oldid;
            this.diff = rowObjects.diff;
            this.date = rowObjects.date;
            this.afd = rowObjects.afd;
            this.merge = rowObjects.merge;
        }
        /**
         * @inheritDoc
         */
        clone(parent) {
            return super.clone(parent);
        }
        /**
         * @inheritDoc
         */
        generatePage(dialog) {
            return CopiedTemplateRowPage({
                copiedTemplateRow: this,
                parent: dialog
            });
        }
    }

    /**
     * Merges templates together. Its own class to avoid circular dependencies.
     */
    class TemplateMerger {
        /**
         * Merge an array of CopiedTemplates into one big CopiedTemplate. Other templates
         * will be destroyed.
         *
         * @param templateList The list of templates to merge
         * @param pivot The template to merge into. If not supplied, the first template
         *              in the list will be used.
         */
        static copied(templateList, pivot) {
            pivot = pivot !== null && pivot !== void 0 ? pivot : templateList[0];
            while (templateList.length > 0) {
                const template = templateList[0];
                if (template !== pivot) {
                    pivot.merge(template, { delete: true });
                }
                // Pop the pivot template out of the list.
                templateList.shift();
            }
        }
    }

    /**
     * Renders the panel used to merge multiple {{split article}} templates.
     *
     * @param type
     * @param parentTemplate
     * @param mergeButton
     * @return A <div> element
     */
    function renderMergePanel(type, parentTemplate, mergeButton) {
        const mergePanel = new OO.ui.FieldsetLayout({
            classes: ['cte-merge-panel'],
            icon: 'tableMergeCells',
            label: mw.message('deputy.cte.merge.title').text()
        });
        unwrapWidget(mergePanel).style.padding = '16px';
        unwrapWidget(mergePanel).style.zIndex = '20';
        // Hide by default
        mergePanel.toggle(false);
        // <select> and button for merging templates
        const mergeTarget = new OO.ui.DropdownInputWidget({
            $overlay: true,
            label: mw.message('deputy.cte.merge.from.select').text()
        });
        const mergeTargetButton = new OO.ui.ButtonWidget({
            label: mw.message('deputy.cte.merge.button').text()
        });
        mergeTargetButton.on('click', () => {
            const template = parentTemplate.parsoid.findNoticeType(type).find((v) => v.name === mergeTarget.value);
            if (template) {
                // If template found, merge and reset panel
                parentTemplate.merge(template, { delete: true });
                mergeTarget.setValue(null);
                mergePanel.toggle(false);
            }
        });
        const mergeFieldLayout = new OO.ui.ActionFieldLayout(mergeTarget, mergeTargetButton, {
            label: mw.message('deputy.cte.merge.from.label').text(),
            align: 'left'
        });
        mergeButton.on('click', () => {
            mergePanel.toggle();
        });
        const mergeAllButton = new OO.ui.ButtonWidget({
            label: mw.message('deputy.cte.merge.all').text(),
            flags: ['progressive']
        });
        mergeAllButton.on('click', () => {
            const notices = parentTemplate.parsoid.findNoticeType(type);
            // Confirm before merging.
            OO.ui.confirm(mw.message('deputy.cte.merge.all.confirm', `${notices.length - 1}`).text()).done((confirmed) => {
                if (confirmed) {
                    // Recursively merge all templates
                    TemplateMerger.copied(notices, parentTemplate);
                    mergeTarget.setValue(null);
                    mergePanel.toggle(false);
                }
            });
        });
        const recalculateOptions = () => {
            const notices = parentTemplate.parsoid.findNoticeType(type);
            const options = [];
            for (const notice of notices) {
                if (notice === parentTemplate) {
                    continue;
                }
                options.push({
                    data: notice.name,
                    // Messages used here:
                    // * deputy.cte.copied.label
                    // * deputy.cte.splitArticle.label
                    // * deputy.cte.mergedFrom.label
                    // * deputy.cte.mergedTo.label
                    // * deputy.cte.backwardsCopy.label
                    // * deputy.cte.translatedPage.label
                    label: mw.message(`deputy.cte.${type}.label`, notice.name).text()
                });
            }
            if (options.length === 0) {
                options.push({
                    data: null,
                    label: mw.message('deputy.cte.merge.from.empty').text(),
                    disabled: true
                });
                mergeTargetButton.setDisabled(true);
                mergeAllButton.setDisabled(true);
            }
            else {
                mergeTargetButton.setDisabled(false);
                mergeAllButton.setDisabled(false);
            }
            mergeTarget.setOptions(options);
        };
        mergePanel.on('toggle', recalculateOptions);
        mergePanel.addItems([mergeFieldLayout, mergeAllButton]);
        return unwrapWidget(mergePanel);
    }
    /**
     * Renders the preview "panel". Not an actual panel, but rather a <div> that
     * shows a preview of the template to be saved. Automatically updates on
     * template changes.
     *
     * @param template The notice to generate previews for and listen events on.
     * @return A preview panel that automatically updates based on the provided notice.
     */
    function renderPreviewPanel(template) {
        const previewPanel = h_1("div", { class: "cte-preview" });
        const updatePreview = mw.util.throttle(() => __awaiter(this, void 0, void 0, function* () {
            if (!previewPanel) {
                // Skip if still unavailable.
                return;
            }
            yield template.generatePreview().then((data) => {
                previewPanel.innerHTML = data;
                // Make all anchor links open in a new tab (prevents exit navigation)
                previewPanel.querySelectorAll('a')
                    .forEach((el) => {
                    el.setAttribute('target', '_blank');
                    el.setAttribute('rel', 'noopener');
                });
                // Infuse collapsibles
                $(previewPanel).find('.mw-collapsible')
                    .makeCollapsible();
                $(previewPanel).find('.collapsible')
                    .each((i, e) => {
                    $(e).makeCollapsible({
                        collapsed: e.classList.contains('collapsed')
                    });
                });
            });
        }), 1000);
        // Listen for changes
        template.addEventListener('save', () => {
            updatePreview();
        });
        updatePreview();
        return previewPanel;
    }

    let InternalCopiedTemplatePage;
    /**
     * UI representation of a {{copied}} template. This representation is further broken
     * down with `CopiedTemplateRowPage`, which represents each row on the template.
     *
     * Note that "Page" in the class title does not refer to a MediaWiki page, but rather
     * a OOUI PageLayout.
     */
    function initCopiedTemplatePage() {
        InternalCopiedTemplatePage = class CopiedTemplatePage extends OO.ui.PageLayout {
            /**
             * @param config Configuration to be passed to the element.
             */
            constructor(config) {
                const { copiedTemplate, parent } = config;
                if (parent == null) {
                    throw new Error('Parent dialog (CopiedTemplateEditorDialog) is required');
                }
                else if (copiedTemplate == null) {
                    throw new Error('Reference template (CopiedTemplate) is required');
                }
                const label = mw.message('deputy.cte.copied.label', config.copiedTemplate.name).text();
                const finalConfig = {
                    label: label,
                    classes: ['cte-page-template']
                };
                super(copiedTemplate.id, finalConfig);
                /**
                 * All child pages of this CopiedTemplatePage. Garbage collected when rechecked.
                 */
                this.childPages = new Map();
                this.document = config.copiedTemplate.parsoid;
                this.copiedTemplate = config.copiedTemplate;
                this.parent = config.parent;
                this.label = label;
                copiedTemplate.addEventListener('rowAdd', () => {
                    parent.rebuildPages();
                });
                copiedTemplate.addEventListener('rowDelete', () => {
                    parent.rebuildPages();
                });
                copiedTemplate.addEventListener('destroy', () => {
                    parent.rebuildPages();
                });
                this.$element.append(this.renderButtons(), this.renderHeader(), renderMergePanel('copied', this.copiedTemplate, this.mergeButton), renderPreviewPanel(this.copiedTemplate), this.renderTemplateOptions());
            }
            /**
             * @inheritDoc
             */
            getChildren() {
                const rows = this.copiedTemplate.rows;
                const rowPages = [];
                for (const row of rows) {
                    if (!this.childPages.has(row)) {
                        this.childPages.set(row, row.generatePage(this.parent));
                    }
                    rowPages.push(this.childPages.get(row));
                }
                // Delete deleted rows from cache.
                this.childPages.forEach((page, row) => {
                    if (rowPages.indexOf(page) === -1) {
                        this.childPages.delete(row);
                    }
                });
                return rowPages;
            }
            /**
             * @return The rendered header of this PageLayout.
             */
            renderHeader() {
                return h_1("h3", null, this.label);
            }
            /**
             * Renders the set of buttons that appear at the top of the page.
             *
             * @return A <div> element.
             */
            renderButtons() {
                const buttonSet = h_1("div", { style: { float: 'right' } });
                this.mergeButton = new OO.ui.ButtonWidget({
                    icon: 'tableMergeCells',
                    title: mw.message('deputy.cte.merge').text(),
                    framed: false
                });
                const deleteButton = new OO.ui.ButtonWidget({
                    icon: 'trash',
                    title: mw.message('deputy.cte.copied.remove').text(),
                    framed: false,
                    flags: ['destructive']
                });
                deleteButton.on('click', () => {
                    if (this.copiedTemplate.rows.length > 0) {
                        OO.ui.confirm(mw.message('deputy.cte.copied.remove.confirm', `${this.copiedTemplate.rows.length}`).text()).done((confirmed) => {
                            if (confirmed) {
                                this.copiedTemplate.destroy();
                            }
                        });
                    }
                    else {
                        this.copiedTemplate.destroy();
                    }
                });
                const addButton = new OO.ui.ButtonWidget({
                    flags: ['progressive'],
                    icon: 'add',
                    label: mw.message('deputy.cte.copied.add').text()
                });
                addButton.on('click', () => {
                    this.copiedTemplate.addRow(new CopiedTemplateRow({
                        to: new mw.Title(this.document.getPage()).getSubjectPage().getPrefixedText()
                    }, this.copiedTemplate));
                });
                buttonSet.appendChild(unwrapWidget(this.mergeButton));
                buttonSet.appendChild(unwrapWidget(deleteButton));
                buttonSet.appendChild(unwrapWidget(addButton));
                return buttonSet;
            }
            /**
             * Renders the panel used to merge multiple {{copied}} templates.
             *
             * @return A <div> element
             */
            renderMergePanel() {
                return renderMergePanel('copied', this.copiedTemplate, this.mergeButton);
            }
            /**
             * Renders the global options of this template. This includes parameters that are not
             * counted towards an entry and affect the template as a whole.
             *
             * @return A <div> element.
             */
            renderTemplateOptions() {
                var _a, _b;
                this.inputSet = {
                    collapse: new OO.ui.CheckboxInputWidget({
                        selected: yesNo((_a = this.copiedTemplate.collapsed) === null || _a === void 0 ? void 0 : _a.trim(), false)
                    }),
                    small: new OO.ui.CheckboxInputWidget({
                        selected: yesNo((_b = this.copiedTemplate.small) === null || _b === void 0 ? void 0 : _b.trim(), false)
                    })
                };
                this.fields = {
                    collapse: new OO.ui.FieldLayout(this.inputSet.collapse, {
                        label: mw.message('deputy.cte.copied.collapse').text(),
                        align: 'inline'
                    }),
                    small: new OO.ui.FieldLayout(this.inputSet.small, {
                        label: mw.message('deputy.cte.copied.small').text(),
                        align: 'inline'
                    })
                };
                this.inputSet.collapse.on('change', (value) => {
                    this.copiedTemplate.collapsed = value ? 'yes' : null;
                    this.copiedTemplate.save();
                });
                this.inputSet.small.on('change', (value) => {
                    this.copiedTemplate.small = value ? 'yes' : null;
                    this.copiedTemplate.save();
                });
                return h_1("div", { class: "cte-templateOptions" },
                    h_1("div", null, unwrapWidget(this.fields.collapse)),
                    h_1("div", null, unwrapWidget(this.fields.small)));
            }
            /**
             * Sets up the outline item of this page. Used in the BookletLayout.
             */
            setupOutlineItem() {
                /** @member any */
                if (this.outlineItem !== undefined) {
                    /** @member any */
                    this.outlineItem
                        .setMovable(true)
                        .setRemovable(true)
                        .setIcon('puzzle')
                        .setLevel(0)
                        .setLabel(this.label);
                }
            }
        };
    }
    /**
     * Creates a new CopiedTemplatePage.
     *
     * @param config Configuration to be passed to the element.
     * @return A CopiedTemplatePage object
     */
    function CopiedTemplatePage (config) {
        if (!InternalCopiedTemplatePage) {
            initCopiedTemplatePage();
        }
        return new InternalCopiedTemplatePage(config);
    }

    /**
     * The AttributionNotice abstract class serves as the blueprint for other
     * subclasses that are instances of AttributionNotices (e.g {@link CopiedTemplate}).
     * It provides the basic functionality for the processing of attribution notices.
     */
    class AttributionNotice extends EventTarget {
        /**
         * Super constructor for AttributionNotice subclasses.
         *
         * @param node
         *        The ParsoidTransclusionTemplateNode of this notice.
         */
        constructor(node) {
            super();
            this.node = node;
            this.name = this.element.getAttribute('about')
                .replace(/^#mwt/, '') + '-' + this.i;
            this.id = btoa(node.getTarget().wt) + '-' + this.name;
            this.parse();
        }
        /**
         * @return The ParsoidDocument handling this notice (specifically its node).
         */
        get parsoid() {
            return this.node.parsoidDocument;
        }
        /**
         * @return The HTMLElement of the node
         */
        get element() {
            return this.node.element;
        }
        /**
         * @return This template's `i` variable, used to identify this template in
         * the template's `parts` (`data-mw`).
         */
        get i() {
            return this.node.i;
        }
        /**
         * Provides low-level access to a template's `data-mw` entry. When possible,
         * use functions from `.node` instead, as these are much more stable.
         *
         * @param callback The callback for data-modifying operations.
         */
        accessTemplateData(callback) {
            const jsonData = JSON.parse(this.element.getAttribute('data-mw'));
            let templateData;
            let index;
            jsonData.parts.forEach((v, k) => {
                if (v != null && v.template !== undefined && v.template.i === this.i) {
                    templateData = v;
                    index = k;
                }
            });
            if (templateData === undefined) {
                throw new TypeError('Invalid `i` given to template.');
            }
            templateData = callback(templateData);
            if (templateData === undefined) {
                jsonData.parts.splice(index, 1);
            }
            else {
                jsonData.parts[index] = templateData;
            }
            this.element.setAttribute('data-mw', JSON.stringify(jsonData));
            if (jsonData.parts.length === 0) {
                this.parsoid.getDocument().querySelectorAll(`[about="${this.element.getAttribute('about')}"]`).forEach((e) => {
                    e.parentElement.removeChild(e);
                });
            }
        }
        /**
         * Gets a wikitext string representation of this template. Used for
         * previews.
         *
         * @return wikitext.
         */
        toWikitext() {
            let wikitext = '{{';
            this.accessTemplateData((data) => {
                wikitext += data.template.target.wt;
                for (const key in data.template.params) {
                    if (!Object.hasOwnProperty.call(data.template.params, key)) {
                        continue;
                    }
                    const value = data.template.params[key];
                    wikitext += `| ${key} = ${value.wt}\n`;
                }
                return data;
            });
            return wikitext + '}}';
        }
        /**
         * Converts this notice to parsed HTML.
         *
         * @return {Promise<string>}
         */
        generatePreview() {
            return __awaiter(this, void 0, void 0, function* () {
                return new mw.Api().post({
                    action: 'parse',
                    format: 'json',
                    formatversion: '2',
                    utf8: 1,
                    title: this.parsoid.getPage(),
                    text: this.toWikitext(),
                    disableeditsection: true
                }).then((data) => data.parse.text);
            });
        }
    }

    /**
     * An event that reflects a change in a given {{copied}} template
     * row.
     */
    class RowChangeEvent extends Event {
        /**
         * Creates a new RowChangeEvent.
         *
         * @param type The event type.
         * @param row The changed row.
         */
        constructor(type, row) {
            super(type);
            this.row = row;
        }
    }

    /**
     * This is a sub-abstract class of {@link AttributionNotice} that represents any
     * attribution notice template that can contain multiple entries (or rows).
     */
    class RowedAttributionNotice extends AttributionNotice {
        /**
         * @return This template's rows.
         */
        get rows() {
            return this._rows;
        }
        /**
         * Checks if this current template has row parameters with a given suffix, or no
         * suffix if not supplied.
         *
         * @param parameters The parameter names to check for
         * @param suffix The suffix of the parameter
         * @return `true` if parameters exist
         * @private
         */
        hasRowParameters(parameters, suffix = '') {
            return Object.keys(this.node.getParameters()).some((v) => parameters.map((v2) => `${v2}${suffix}`)
                .indexOf(v) !== -1);
        }
        /**
         * Extracts parameters from `this.node` and returns a row.
         *
         * @param parameters The parameter names to check for
         * @param suffix The suffix of the parameter
         * @return A row, or null if no parameters were found.
         * @private
         */
        extractRowParameters(parameters, suffix = '') {
            const row = {};
            parameters.forEach((key) => {
                if (this.node.hasParameter(key + suffix) !== undefined) {
                    row[key] = this.node.getParameter(key + suffix);
                }
                else if (suffix === '' && this.node.hasParameter(`${key}1`)) {
                    // Non-numbered parameter not found but a numbered parameter with
                    // an index of 1 was found. Fall back to that value.
                    row[key] = this.node.getParameter(`${key}1`).trim();
                }
                else if (suffix === 1 && this.node.hasParameter(`${key}`)) {
                    // This is i = 1, so fall back to a non-numbered parameter (if exists)
                    const unnumberedParamValue = this.node.getParameter(`${key}`).trim();
                    if (unnumberedParamValue !== undefined) {
                        row[key] = unnumberedParamValue;
                    }
                }
            });
            return row;
        }
        /**
         * Adds a row to this template.
         *
         * @param row The row to add.
         */
        addRow(row) {
            this._rows.push(row);
            this.save();
            this.dispatchEvent(new RowChangeEvent('rowAdd', row));
        }
        /**
         * Deletes a row to this template.
         *
         * @param row The row to delete.
         */
        deleteRow(row) {
            const i = this._rows.findIndex((v) => v === row);
            if (i !== -1) {
                this._rows.splice(i, 1);
                this.save();
                this.dispatchEvent(new RowChangeEvent('rowDelete', row));
            }
            if (this._rows.length === 0) {
                this.destroy();
            }
        }
        /**
         * Copies in the rows of another {@link SplitArticleTemplate}, and
         * optionally deletes that template or clears its contents.
         *
         * @param template The template to copy from.
         * @param options Options for this merge.
         * @param options.delete
         *        Whether the reference template will be deleted after merging.
         * @param options.clear
         *        Whether the reference template's rows will be cleared after merging.
         */
        merge(template, options = {}) {
            if (template.rows === undefined || template === this) {
                // Deleted or self
                return;
            }
            for (const row of template.rows) {
                if (options.clear) {
                    row.parent = this;
                }
                else {
                    this.addRow(row.clone(this));
                }
            }
            if (options.delete) {
                template.destroy();
            }
        }
    }

    /**
     * Represents a single {{copied}} template in the Parsoid document.
     */
    class CopiedTemplate extends RowedAttributionNotice {
        /**
         * @return This template's rows.
         */
        get rows() {
            return this._rows;
        }
        /**
         * @inheritDoc
         */
        parse() {
            if (this.node.getParameter('collapse')) {
                this.collapsed = this.node.getParameter('collapse');
            }
            if (this.node.getParameter('small')) {
                this.small = this.node.getParameter('small');
            }
            // Extract {{copied}} rows.
            const rows = [];
            // Numberless
            if (this.hasRowParameters(copiedTemplateRowParameters)) {
                // If `from`, `to`, ..., or `merge` is found.
                rows.push(new CopiedTemplateRow(this.extractRowParameters(copiedTemplateRowParameters), this));
            }
            // Numbered
            let i = 1, continueExtracting = true;
            do {
                if (this.hasRowParameters(copiedTemplateRowParameters, i)) {
                    rows.push(new CopiedTemplateRow(this.extractRowParameters(copiedTemplateRowParameters, i), this));
                }
                else if (!(i === 1 && rows.length > 0)) {
                    // Row doesn't exist. Stop parsing from here.
                    continueExtracting = false;
                }
                i++;
            } while (continueExtracting);
            /**
             * All the rows of this template.
             *
             * @type {CopiedTemplateRow[]}
             */
            this._rows = rows;
        }
        /**
         * @inheritDoc
         */
        save() {
            if (this.collapsed !== undefined) {
                this.node.setParameter('collapse', yesNo(this.collapsed) ? 'yes' : null);
            }
            if (this.small !== undefined) {
                this.node.setParameter('small', yesNo(this.small) ? 'yes' : null);
            }
            const existingParameters = this.node.getParameters();
            for (const param in existingParameters) {
                if (copiedTemplateRowParameters.some((v) => param.startsWith(v))) {
                    // This is a row parameter. Remove it in preparation for rebuild (further below).
                    this.node.removeParameter(param);
                }
            }
            if (this._rows.length === 1) {
                // If there is only one row, don't bother with numbered rows.
                for (const param of copiedTemplateRowParameters) {
                    if (this._rows[0][param] !== undefined) {
                        this.node.setParameter(param, this._rows[0][param]);
                    }
                }
            }
            else {
                // If there are multiple rows, add number suffixes (except for i = 0).
                for (let i = 0; i < this._rows.length; i++) {
                    for (const param of copiedTemplateRowParameters) {
                        if (this._rows[i][param] !== undefined) {
                            this.node.setParameter(param + (i === 0 ? '' : i + 1), this._rows[i][param]);
                        }
                    }
                }
            }
            this.dispatchEvent(new Event('save'));
        }
        /**
         * @inheritDoc
         */
        destroy() {
            this.node.destroy();
            // Self-destruct
            Object.keys(this).forEach((k) => delete this[k]);
            this.dispatchEvent(new Event('destroy'));
        }
        /**
         * @inheritDoc
         */
        generatePage(dialog) {
            return CopiedTemplatePage({
                copiedTemplate: this,
                parent: dialog
            });
        }
        /**
         * Copies in the rows of another {@link CopiedTemplate}, and
         * optionally deletes that template or clears its contents.
         *
         * @param template The template to copy from.
         * @param options Options for this merge.
         * @param options.delete
         *        Whether the reference template will be deleted after merging.
         * @param options.clear
         *        Whether the reference template's rows will be cleared after merging.
         */
        merge(template, options = {}) {
            if (template.rows === undefined || template === this) {
                // Deleted or self
                return;
            }
            for (const row of template.rows) {
                if (options.clear) {
                    row.parent = this;
                }
                else {
                    this.addRow(row.clone(this));
                }
            }
            if (options.delete) {
                template.destroy();
            }
        }
    }

    /**
     *
     */
    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({
                parameters: {
                    format: 'json',
                    formatversion: 2,
                    utf8: 1,
                    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());
        }
    }

    let InternalSplitArticleTemplateRowPage;
    /**
     * Initializes the process element.
     */
    function initSplitArticleTemplateRowPage() {
        InternalSplitArticleTemplateRowPage = class SplitArticleTemplateRowPage extends OO.ui.PageLayout {
            /**
             * @param config Configuration to be passed to the element.
             */
            constructor(config) {
                const { splitArticleTemplateRow, parent } = config;
                if (parent == null) {
                    throw new Error('Parent dialog (CopiedTemplateEditorDialog) is required');
                }
                else if (splitArticleTemplateRow == null) {
                    throw new Error('Reference row (SplitArticleTemplateRow) is required');
                }
                const finalConfig = {
                    classes: ['cte-page-row']
                };
                super(splitArticleTemplateRow.id, finalConfig);
                this.parent = parent;
                this.splitArticleTemplateRow = splitArticleTemplateRow;
                this.refreshLabel();
                this.splitArticleTemplateRow.parent.addEventListener('destroy', () => {
                    parent.rebuildPages();
                });
                this.splitArticleTemplateRow.parent.addEventListener('rowDelete', () => {
                    parent.rebuildPages();
                });
                this.$element.append(this.render().$element);
            }
            /**
             * Refreshes the page's label
             */
            refreshLabel() {
                this.label = mw.message('deputy.cte.splitArticle.entry.short', this.splitArticleTemplateRow.to || '???', this.splitArticleTemplateRow.date || '???').text();
                if (this.outlineItem) {
                    this.outlineItem.setLabel(this.label);
                }
            }
            /**
             * Renders this page. Returns a FieldsetLayout OOUI widget.
             *
             * @return An OOUI FieldsetLayout
             */
            render() {
                this.layout = new OO.ui.FieldsetLayout({
                    icon: 'parameter',
                    label: mw.message('deputy.cte.splitArticle.entry.label').text(),
                    classes: ['cte-fieldset']
                });
                this.layout.$element.append(this.renderButtons());
                this.layout.addItems(this.renderFields());
                return this.layout;
            }
            /**
             * Renders a set of buttons used to modify a specific {{copied}} template row.
             *
             * @return An array of OOUI FieldLayouts
             */
            renderButtons() {
                const deleteButton = new OO.ui.ButtonWidget({
                    icon: 'trash',
                    title: mw.message('deputy.cte.splitArticle.entry.remove').text(),
                    framed: false,
                    flags: ['destructive']
                });
                deleteButton.on('click', () => {
                    this.splitArticleTemplateRow.parent.deleteRow(this.splitArticleTemplateRow);
                });
                return h_1("div", { style: {
                        float: 'right',
                        position: 'absolute',
                        top: '0.5em',
                        right: '0.5em'
                    } }, unwrapWidget(deleteButton));
            }
            /**
             * Renders a set of OOUI InputWidgets and FieldLayouts, eventually returning an
             * array of each FieldLayout to append to the FieldsetLayout.
             *
             * @return An array of OOUI FieldLayouts
             */
            renderFields() {
                const rowDate = this.splitArticleTemplateRow.date;
                const parsedDate = (rowDate == null || rowDate.trim().length === 0) ?
                    undefined : (!isNaN(new Date(rowDate.trim() + ' UTC').getTime()) ?
                    (new Date(rowDate.trim() + ' UTC')) : (!isNaN(new Date(rowDate.trim()).getTime()) ?
                    new Date(rowDate.trim()) : null));
                const inputs = {
                    to: new mw.widgets.TitleInputWidget({
                        $overlay: this.parent.$overlay,
                        required: true,
                        value: this.splitArticleTemplateRow.to || '',
                        placeholder: mw.message('deputy.cte.splitArticle.to.placeholder').text()
                    }),
                    // eslint-disable-next-line camelcase
                    from_oldid: new OO.ui.TextInputWidget({
                        $overlay: this.parent.$overlay,
                        value: this.splitArticleTemplateRow.from_oldid || '',
                        placeholder: mw.message('deputy.cte.splitArticle.to.placeholder').text()
                    }),
                    date: new mw.widgets.datetime.DateTimeInputWidget({
                        $overlay: this.parent.$overlay,
                        required: true,
                        calendar: null,
                        icon: 'calendar',
                        clearable: true,
                        value: parsedDate
                    }),
                    diff: new OO.ui.TextInputWidget({
                        $overlay: this.parent.$overlay,
                        value: this.splitArticleTemplateRow.from_oldid || '',
                        placeholder: mw.message('deputy.cte.splitArticle.diff.placeholder').text(),
                        validate: (value) => {
                            if (value.trim().length === 0) {
                                return true;
                            }
                            try {
                                return typeof new URL(value).href === 'string';
                            }
                            catch (e) {
                                return false;
                            }
                        }
                    })
                };
                const fieldLayouts = {
                    to: new OO.ui.FieldLayout(inputs.to, {
                        $overlay: this.parent.$overlay,
                        align: 'top',
                        label: mw.message('deputy.cte.splitArticle.to.label').text(),
                        help: mw.message('deputy.cte.splitArticle.to.help').text()
                    }),
                    // eslint-disable-next-line camelcase
                    from_oldid: new OO.ui.FieldLayout(inputs.from_oldid, {
                        $overlay: this.parent.$overlay,
                        align: 'left',
                        label: mw.message('deputy.cte.splitArticle.from_oldid.label').text(),
                        help: mw.message('deputy.cte.splitArticle.from_oldid.help').text()
                    }),
                    date: new OO.ui.FieldLayout(inputs.date, {
                        $overlay: this.parent.$overlay,
                        align: 'left',
                        label: mw.message('deputy.cte.splitArticle.date.label').text(),
                        help: mw.message('deputy.cte.splitArticle.date.help').text()
                    }),
                    diff: new OO.ui.FieldLayout(inputs.diff, {
                        $overlay: this.parent.$overlay,
                        align: 'left',
                        label: mw.message('deputy.cte.splitArticle.diff.label').text(),
                        help: mw.message('deputy.cte.splitArticle.diff.help').text()
                    })
                };
                for (const _field in inputs) {
                    const field = _field;
                    const input = inputs[field];
                    // Attach the change listener
                    input.on('change', (value) => {
                        if (input instanceof mw.widgets.datetime.DateTimeInputWidget) {
                            this.splitArticleTemplateRow[field] =
                                new Date(value).toLocaleDateString('en-GB', {
                                    year: 'numeric', month: 'long', day: 'numeric'
                                });
                            if (value.length > 0) {
                                fieldLayouts[field].setWarnings([]);
                            }
                        }
                        else {
                            this.splitArticleTemplateRow[field] = value;
                        }
                        this.splitArticleTemplateRow.parent.save();
                    });
                    if (input instanceof OO.ui.TextInputWidget) {
                        // Rechecks the validity of the field.
                        input.setValidityFlag();
                    }
                }
                inputs.to.on('change', () => {
                    this.refreshLabel();
                });
                inputs.date.on('change', () => {
                    this.refreshLabel();
                });
                return getObjectValues(fieldLayouts);
            }
            /**
             * Sets up the outline item of this page. Used in the BookletLayout.
             */
            setupOutlineItem() {
                /** @member any */
                if (this.outlineItem !== undefined) {
                    /** @member any */
                    this.outlineItem
                        .setMovable(true)
                        .setRemovable(true)
                        .setIcon('parameter')
                        .setLevel(1)
                        .setLabel(this.label);
                }
            }
        };
    }
    /**
     * Creates a new SplitArticleTemplateRowPage.
     *
     * @param config Configuration to be passed to the element.
     * @return A SplitArticleTemplateRowPage object
     */
    function SplitArticleTemplateRowPage (config) {
        if (!InternalSplitArticleTemplateRowPage) {
            initSplitArticleTemplateRowPage();
        }
        return new InternalSplitArticleTemplateRowPage(config);
    }

    // noinspection JSDeprecatedSymbols
    const splitArticleTemplateRowParameters = [
        'to', 'from_oldid', 'date', 'diff'
    ];
    /**
     * Represents a row/entry in a {{split article}} template.
     */
    class SplitArticleTemplateRow extends AttributionNoticeRow {
        /**
         * Creates a new RawCopiedTemplateRow
         *
         * @param rowObjects
         * @param parent
         */
        constructor(rowObjects, parent) {
            super(parent);
            this.to = rowObjects.to;
            // eslint-disable-next-line camelcase
            this.from_oldid = rowObjects.from_oldid;
            this.date = rowObjects.date;
            this.diff = rowObjects.diff;
            this._parent = parent;
        }
        /**
         * @inheritDoc
         */
        clone(parent) {
            return super.clone(parent);
        }
        /**
         * @inheritDoc
         */
        generatePage(dialog) {
            return SplitArticleTemplateRowPage({
                splitArticleTemplateRow: this,
                parent: dialog
            });
        }
    }

    let InternalSplitArticleTemplatePage;
    /**
     * Initializes the process element.
     */
    function initSplitArticleTemplatePage() {
        InternalSplitArticleTemplatePage = class SplitArticleTemplatePage extends OO.ui.PageLayout {
            /**
             * @param config Configuration to be passed to the element.
             */
            constructor(config) {
                const { splitArticleTemplate, parent } = config;
                if (parent == null) {
                    throw new Error('Parent dialog (CopiedTemplateEditorDialog) is required');
                }
                else if (splitArticleTemplate == null) {
                    throw new Error('Reference template (SplitArticleTemplate) is required');
                }
                const label = mw.message('deputy.cte.splitArticle.label', config.splitArticleTemplate.name).text();
                const finalConfig = {
                    label: label,
                    classes: ['cte-page-template']
                };
                super(splitArticleTemplate.id, finalConfig);
                /**
                 * All child pages of this splitArticleTemplatePage. Garbage collected when rechecked.
                 */
                this.childPages = new Map();
                this.document = config.splitArticleTemplate.parsoid;
                this.splitArticleTemplate = config.splitArticleTemplate;
                this.parent = config.parent;
                this.label = label;
                splitArticleTemplate.addEventListener('rowAdd', () => {
                    parent.rebuildPages();
                });
                splitArticleTemplate.addEventListener('rowDelete', () => {
                    parent.rebuildPages();
                });
                splitArticleTemplate.addEventListener('destroy', () => {
                    parent.rebuildPages();
                });
                this.$element.append(this.renderButtons(), this.renderHeader(), renderMergePanel('splitArticle', this.splitArticleTemplate, this.mergeButton), renderPreviewPanel(this.splitArticleTemplate), this.renderTemplateOptions());
            }
            /**
             * @inheritDoc
             */
            getChildren() {
                const rows = this.splitArticleTemplate.rows;
                const rowPages = [];
                for (const row of rows) {
                    if (!this.childPages.has(row)) {
                        this.childPages.set(row, row.generatePage(this.parent));
                    }
                    rowPages.push(this.childPages.get(row));
                }
                // Delete deleted rows from cache.
                this.childPages.forEach((page, row) => {
                    if (rowPages.indexOf(page) === -1) {
                        this.childPages.delete(row);
                    }
                });
                return rowPages;
            }
            /**
             * Renders the set of buttons that appear at the top of the page.
             *
             * @return A <div> element.
             */
            renderButtons() {
                const buttonSet = h_1("div", { style: { float: 'right' } });
                this.mergeButton = new OO.ui.ButtonWidget({
                    icon: 'tableMergeCells',
                    title: mw.message('deputy.cte.merge').text(),
                    framed: false
                });
                const deleteButton = new OO.ui.ButtonWidget({
                    icon: 'trash',
                    title: mw.message('deputy.cte.splitArticle.remove').text(),
                    framed: false,
                    flags: ['destructive']
                });
                deleteButton.on('click', () => {
                    if (this.splitArticleTemplate.rows.length > 0) {
                        OO.ui.confirm(mw.message('deputy.cte.splitArticle.remove.confirm', `${this.splitArticleTemplate.rows.length}`).text()).done((confirmed) => {
                            if (confirmed) {
                                this.splitArticleTemplate.destroy();
                            }
                        });
                    }
                    else {
                        this.splitArticleTemplate.destroy();
                    }
                });
                const addButton = new OO.ui.ButtonWidget({
                    flags: ['progressive'],
                    icon: 'add',
                    label: mw.message('deputy.cte.splitArticle.add').text()
                });
                addButton.on('click', () => {
                    this.splitArticleTemplate.addRow(new SplitArticleTemplateRow({}, this.splitArticleTemplate));
                });
                this.splitArticleTemplate.addEventListener('rowAdd', () => {
                    // TODO: Remove after template improvements.
                    addButton.setDisabled(this.splitArticleTemplate.rows.length >= 10);
                });
                buttonSet.appendChild(unwrapWidget(this.mergeButton));
                buttonSet.appendChild(unwrapWidget(deleteButton));
                buttonSet.appendChild(unwrapWidget(addButton));
                return buttonSet;
            }
            /**
             * @return The rendered header of this PageLayout.
             */
            renderHeader() {
                return h_1("h3", null, this.label);
            }
            /**
             * Renders the global options of this template. This includes parameters that are not
             * counted towards an entry and affect the template as a whole.
             *
             * @return A <div> element.
             */
            renderTemplateOptions() {
                const page = new mw.Title(this.splitArticleTemplate.parsoid.getPage()).getSubjectPage().getPrefixedText();
                const collapse = new OO.ui.CheckboxInputWidget({
                    selected: this.splitArticleTemplate.collapse ?
                        yesNo(this.splitArticleTemplate.collapse) : false
                });
                const from = new mw.widgets.TitleInputWidget({
                    $overlay: this.parent.$overlay,
                    value: this.splitArticleTemplate.from || '',
                    placeholder: page
                });
                collapse.on('change', (value) => {
                    this.splitArticleTemplate.collapse = value ? 'yes' : null;
                    this.splitArticleTemplate.save();
                });
                from.on('change', (value) => {
                    this.splitArticleTemplate.from = value.length > 0 ? value : page;
                    this.splitArticleTemplate.save();
                });
                return h_1("div", { class: "cte-templateOptions" },
                    h_1("div", null, unwrapWidget(new OO.ui.FieldLayout(from, {
                        $overlay: this.parent.$overlay,
                        align: 'top',
                        label: mw.message('deputy.cte.splitArticle.from').text(),
                        help: mw.message('deputy.cte.splitArticle.from.help').text()
                    }))),
                    h_1("div", { style: {
                            flex: '0',
                            alignSelf: 'center',
                            marginLeft: '8px'
                        } }, unwrapWidget(new OO.ui.FieldLayout(collapse, {
                        $overlay: this.parent.$overlay,
                        align: 'top',
                        label: mw.message('deputy.cte.splitArticle.collapse').text()
                    }))));
            }
            /**
             * Sets up the outline item of this page. Used in the BookletLayout.
             */
            setupOutlineItem() {
                /** @member any */
                if (this.outlineItem !== undefined) {
                    /** @member any */
                    this.outlineItem
                        .setMovable(true)
                        .setRemovable(true)
                        .setIcon('puzzle')
                        .setLevel(0)
                        .setLabel(this.label);
                }
            }
        };
    }
    /**
     * Creates a new SplitArticleTemplatePage.
     *
     * @param config Configuration to be passed to the element.
     * @return A SplitArticleTemplatePage object
     */
    function SplitArticleTemplatePage (config) {
        if (!InternalSplitArticleTemplatePage) {
            initSplitArticleTemplatePage();
        }
        return new InternalSplitArticleTemplatePage(config);
    }

    /**
     * Represents a single {{split article}} template.
     */
    class SplitArticleTemplate extends RowedAttributionNotice {
        /**
         * @inheritDoc
         */
        parse() {
            if (this.node.hasParameter('from')) {
                this.from = this.node.getParameter('from');
            }
            if (this.node.hasParameter('collapse')) {
                this.collapse = this.node.getParameter('collapse');
            }
            // Extract {{copied}} rows.
            const rows = [];
            // Numberless
            if (this.hasRowParameters(splitArticleTemplateRowParameters)) {
                // If `from`, `to`, ..., or `merge` is found.
                rows.push(new SplitArticleTemplateRow(this.extractRowParameters(splitArticleTemplateRowParameters), this));
            }
            // Numbered
            let i = 1, continueExtracting = true;
            do {
                if (this.hasRowParameters(splitArticleTemplateRowParameters, i)) {
                    rows.push(new SplitArticleTemplateRow(this.extractRowParameters(splitArticleTemplateRowParameters, i), this));
                }
                else if (!(i === 1 && rows.length > 0)) {
                    // Row doesn't exist. Stop parsing from here.
                    continueExtracting = false;
                }
                i++;
                // Hard limit to `i` added due to the template's construction.
                // TODO: Modify template to allow more than 10.
            } while (continueExtracting && i <= 10);
            this._rows = rows;
        }
        /**
         * @inheritDoc
         */
        save() {
            if (this.collapse !== undefined) {
                this.node.setParameter('collapse', yesNo(this.collapse) ? 'yes' : null);
            }
            this.node.setParameter('from', this.from);
            const existingParameters = this.node.getParameters();
            for (const param in existingParameters) {
                if (splitArticleTemplateRowParameters.some((v) => param.startsWith(v))) {
                    // This is a row parameter. Remove it in preparation for rebuild (further below).
                    this.node.removeParameter(param);
                }
            }
            this._rows.forEach((row, i) => {
                this.node.setParameter(`to${i > 0 ? i + 1 : ''}`, row.to);
                this.node.setParameter(`from_oldid${i > 0 ? i + 1 : ''}`, row.from_oldid);
                this.node.setParameter(`date${i > 0 ? i + 1 : ''}`, row.date);
                this.node.setParameter(`diff${i > 0 ? i + 1 : ''}`, row.diff);
            });
            this.dispatchEvent(new Event('save'));
        }
        /**
         *
         * @inheritDoc
         */
        destroy() {
            this.node.destroy();
            // Self-destruct
            Object.keys(this).forEach((k) => delete this[k]);
            this.dispatchEvent(new Event('destroy'));
        }
        /**
         * @inheritDoc
         */
        generatePage(dialog) {
            return SplitArticleTemplatePage({
                splitArticleTemplate: this,
                parent: dialog
            });
        }
    }

    let InternalMergedFromTemplatePage;
    /**
     * Initializes the process element.
     */
    function initMergedFromTemplatePage() {
        InternalMergedFromTemplatePage = class MergedFromTemplatePage extends OO.ui.PageLayout {
            /**
             * @param config Configuration to be passed to the element.
             */
            constructor(config) {
                const { mergedFromTemplate, parent } = config;
                if (parent == null) {
                    throw new Error('Parent dialog (CopiedTemplateEditorDialog) is required');
                }
                else if (mergedFromTemplate == null) {
                    throw new Error('Reference template (MergedFromTemplate) is required');
                }
                const finalConfig = {
                    classes: ['cte-page-template']
                };
                super(mergedFromTemplate.id, finalConfig);
                this.document = mergedFromTemplate.parsoid;
                this.mergedFromTemplate = mergedFromTemplate;
                this.parent = config.parent;
                this.refreshLabel();
                mergedFromTemplate.addEventListener('destroy', () => {
                    parent.rebuildPages();
                });
                this.$element.append(this.renderButtons(), this.renderHeader(), renderPreviewPanel(this.mergedFromTemplate), this.renderTemplateOptions());
            }
            /**
             * Refreshes the page's label
             */
            refreshLabel() {
                this.label = mw.message('deputy.cte.mergedFrom.label', this.mergedFromTemplate.article || '???').text();
                if (this.outlineItem) {
                    this.outlineItem.setLabel(this.label);
                }
            }
            /**
             * Renders the set of buttons that appear at the top of the page.
             *
             * @return A <div> element.
             */
            renderButtons() {
                const buttonSet = h_1("div", { style: { float: 'right' } });
                const deleteButton = new OO.ui.ButtonWidget({
                    icon: 'trash',
                    title: mw.message('deputy.cte.mergedFrom.remove').text(),
                    framed: false,
                    flags: ['destructive']
                });
                deleteButton.on('click', () => {
                    this.mergedFromTemplate.destroy();
                });
                buttonSet.appendChild(unwrapWidget(deleteButton));
                return buttonSet;
            }
            /**
             * @return The rendered header of this PageLayout.
             */
            renderHeader() {
                return h_1("h3", null, this.label);
            }
            /**
             * @return The options for this template
             */
            renderTemplateOptions() {
                const layout = new OO.ui.FieldsetLayout({
                    icon: 'parameter',
                    label: mw.message('deputy.cte.templateOptions').text(),
                    classes: ['cte-fieldset']
                });
                const rowDate = this.mergedFromTemplate.date;
                const parsedDate = (rowDate == null || rowDate.trim().length === 0) ?
                    undefined : (!isNaN(new Date(rowDate.trim() + ' UTC').getTime()) ?
                    (new Date(rowDate.trim() + ' UTC')) : (!isNaN(new Date(rowDate.trim()).getTime()) ?
                    new Date(rowDate.trim()) : null));
                const inputs = {
                    article: new mw.widgets.TitleInputWidget({
                        $overlay: this.parent.$overlay,
                        required: true,
                        value: this.mergedFromTemplate.article || '',
                        placeholder: mw.message('deputy.cte.mergedFrom.article.placeholder').text()
                    }),
                    date: new mw.widgets.datetime.DateTimeInputWidget({
                        $overlay: this.parent.$overlay,
                        required: true,
                        calendar: null,
                        icon: 'calendar',
                        clearable: true,
                        value: parsedDate
                    }),
                    target: new mw.widgets.TitleInputWidget({
                        $overlay: this.parent.$overlay,
                        value: this.mergedFromTemplate.target || '',
                        placeholder: mw.message('deputy.cte.mergedFrom.target.placeholder').text()
                    }),
                    afd: new mw.widgets.TitleInputWidget({
                        $overlay: this.parent.$overlay,
                        value: this.mergedFromTemplate.afd || '',
                        placeholder: mw.message('deputy.cte.mergedFrom.afd.placeholder').text(),
                        validate: (title) => {
                            // TODO: l10n
                            return title.trim().length === 0 || title.startsWith(new mw.Title('Articles for deletion/', nsId('wikipedia'))
                                .toText());
                        }
                    }),
                    talk: new OO.ui.CheckboxInputWidget({
                        $overlay: this.parent.$overlay,
                        selected: yesNo(this.mergedFromTemplate.target)
                    })
                };
                const fieldLayouts = {
                    article: new OO.ui.FieldLayout(inputs.article, {
                        $overlay: this.parent.$overlay,
                        align: 'top',
                        label: mw.message('deputy.cte.mergedFrom.article.label').text(),
                        help: mw.message('deputy.cte.mergedFrom.article.help').text()
                    }),
                    date: new OO.ui.FieldLayout(inputs.date, {
                        $overlay: this.parent.$overlay,
                        align: 'left',
                        label: mw.message('deputy.cte.mergedFrom.date.label').text(),
                        help: mw.message('deputy.cte.mergedFrom.date.help').text()
                    }),
                    target: new OO.ui.FieldLayout(inputs.target, {
                        $overlay: this.parent.$overlay,
                        align: 'left',
                        label: mw.message('deputy.cte.mergedFrom.target.label').text(),
                        help: mw.message('deputy.cte.mergedFrom.target.help').text()
                    }),
                    afd: new OO.ui.FieldLayout(inputs.afd, {
                        $overlay: this.parent.$overlay,
                        align: 'left',
                        label: mw.message('deputy.cte.mergedFrom.afd.label').text(),
                        help: mw.message('deputy.cte.mergedFrom.afd.help').text()
                    }),
                    talk: new OO.ui.FieldLayout(inputs.talk, {
                        $overlay: this.parent.$overlay,
                        align: 'inline',
                        label: mw.message('deputy.cte.mergedFrom.talk.label').text(),
                        help: mw.message('deputy.cte.mergedFrom.talk.help').text()
                    })
                };
                for (const _field in inputs) {
                    const field = _field;
                    const input = inputs[field];
                    // Attach the change listener
                    input.on('change', (value) => {
                        if (input instanceof OO.ui.CheckboxInputWidget) {
                            this.mergedFromTemplate[field] = value ? 'yes' : 'no';
                        }
                        else if (input instanceof mw.widgets.datetime.DateTimeInputWidget) {
                            this.mergedFromTemplate[field] =
                                new Date(value).toLocaleDateString('en-GB', {
                                    year: 'numeric', month: 'long', day: 'numeric'
                                });
                            if (value.length > 0) {
                                fieldLayouts[field].setWarnings([]);
                            }
                        }
                        else {
                            this.mergedFromTemplate[field] = value;
                        }
                        this.mergedFromTemplate.save();
                    });
                    if (input instanceof OO.ui.TextInputWidget) {
                        // Rechecks the validity of the field.
                        input.setValidityFlag();
                    }
                }
                inputs.article.on('change', () => {
                    this.refreshLabel();
                });
                layout.addItems(getObjectValues(fieldLayouts));
                return unwrapWidget(layout);
            }
            /**
             * Sets up the outline item of this page. Used in the BookletLayout.
             */
            setupOutlineItem() {
                /** @member any */
                if (this.outlineItem !== undefined) {
                    /** @member any */
                    this.outlineItem
                        .setMovable(true)
                        .setRemovable(true)
                        .setIcon('puzzle')
                        .setLevel(0)
                        .setLabel(this.label);
                }
            }
        };
    }
    /**
     * Creates a new MergedFromTemplatePage.
     *
     * @param config Configuration to be passed to the element.
     * @return A MergedFromTemplatePage object
     */
    function MergedFromTemplatePage (config) {
        if (!InternalMergedFromTemplatePage) {
            initMergedFromTemplatePage();
        }
        return new InternalMergedFromTemplatePage(config);
    }

    /**
     * Represents a single {{merged-from}} template in the Parsoid document.
     */
    class MergedFromTemplate extends AttributionNotice {
        /**
         * @inheritDoc
         */
        parse() {
            if (this.node.hasParameter('1')) {
                this.article = this.node.getParameter('1');
            }
            if (this.node.hasParameter('2')) {
                this.date = this.node.getParameter('2');
            }
            if (this.node.hasParameter('talk')) {
                this.talk = this.node.getParameter('talk');
            }
            if (this.node.hasParameter('target')) {
                this.target = this.node.getParameter('target');
            }
            if (this.node.hasParameter('afd')) {
                this.afd = this.node.getParameter('afd');
            }
        }
        /**
         * @inheritDoc
         */
        save() {
            var _a, _b;
            this.node.setParameter('1', this.article);
            this.node.setParameter('2', this.date);
            if (this.talk !== undefined) {
                this.node.setParameter('talk', yesNo(this.talk) ? null : 'no');
            }
            this.node.setParameter('target', ((_a = this.target) !== null && _a !== void 0 ? _a : '').length > 0 ? this.target : null);
            this.node.setParameter('afd', ((_b = this.afd) !== null && _b !== void 0 ? _b : '').length > 0 ? this.afd : null);
            this.dispatchEvent(new Event('save'));
        }
        /**
         * @inheritDoc
         */
        destroy() {
            this.node.destroy();
            // Self-destruct
            Object.keys(this).forEach((k) => delete this[k]);
            this.dispatchEvent(new Event('destroy'));
        }
        /**
         * @inheritDoc
         */
        generatePage(dialog) {
            return MergedFromTemplatePage({
                mergedFromTemplate: this,
                parent: dialog
            });
        }
    }

    let InternalMergedToTemplatePage;
    /**
     * Initializes the process element.
     */
    function initMergedToTemplatePage() {
        InternalMergedToTemplatePage = class MergedToTemplatePage extends OO.ui.PageLayout {
            /**
             * @param config Configuration to be passed to the element.
             */
            constructor(config) {
                const { mergedToTemplate, parent } = config;
                if (parent == null) {
                    throw new Error('Parent dialog (CopiedTemplateEditorDialog) is required');
                }
                else if (mergedToTemplate == null) {
                    throw new Error('Reference template (MergedToTemplate) is required');
                }
                const finalConfig = {
                    classes: ['cte-page-template']
                };
                super(mergedToTemplate.id, finalConfig);
                this.document = mergedToTemplate.parsoid;
                this.mergedToTemplate = mergedToTemplate;
                this.parent = config.parent;
                this.refreshLabel();
                mergedToTemplate.addEventListener('destroy', () => {
                    parent.rebuildPages();
                });
                this.$element.append(this.renderButtons(), this.renderHeader(), renderPreviewPanel(this.mergedToTemplate), this.renderTemplateOptions());
            }
            /**
             * Refreshes the page's label
             */
            refreshLabel() {
                this.label = mw.message('deputy.cte.mergedTo.label', this.mergedToTemplate.to || '???').text();
                if (this.outlineItem) {
                    this.outlineItem.setLabel(this.label);
                }
            }
            /**
             * Renders the set of buttons that appear at the top of the page.
             *
             * @return A <div> element.
             */
            renderButtons() {
                const buttonSet = h_1("div", { style: { float: 'right' } });
                const deleteButton = new OO.ui.ButtonWidget({
                    icon: 'trash',
                    title: mw.message('deputy.cte.mergedTo.remove').text(),
                    framed: false,
                    flags: ['destructive']
                });
                deleteButton.on('click', () => {
                    this.mergedToTemplate.destroy();
                });
                buttonSet.appendChild(unwrapWidget(deleteButton));
                return buttonSet;
            }
            /**
             * @return The rendered header of this PageLayout.
             */
            renderHeader() {
                return h_1("h3", null, this.label);
            }
            /**
             * @return The options for this template
             */
            renderTemplateOptions() {
                const layout = new OO.ui.FieldsetLayout({
                    icon: 'parameter',
                    label: mw.message('deputy.cte.templateOptions').text(),
                    classes: ['cte-fieldset']
                });
                const rowDate = this.mergedToTemplate.date;
                const parsedDate = (rowDate == null || rowDate.trim().length === 0) ?
                    undefined : (!isNaN(new Date(rowDate.trim() + ' UTC').getTime()) ?
                    (new Date(rowDate.trim() + ' UTC')) : (!isNaN(new Date(rowDate.trim()).getTime()) ?
                    new Date(rowDate.trim()) : null));
                const inputs = {
                    to: new mw.widgets.TitleInputWidget({
                        $overlay: this.parent.$overlay,
                        required: true,
                        value: this.mergedToTemplate.to || '',
                        placeholder: mw.message('deputy.cte.mergedTo.to.placeholder').text()
                    }),
                    date: new mw.widgets.datetime.DateTimeInputWidget({
                        $overlay: this.parent.$overlay,
                        required: true,
                        calendar: null,
                        icon: 'calendar',
                        clearable: true,
                        value: parsedDate
                    }),
                    small: new OO.ui.CheckboxInputWidget({
                        $overlay: this.parent.$overlay,
                        selected: yesNo(this.mergedToTemplate.small, false)
                    })
                };
                const fieldLayouts = {
                    to: new OO.ui.FieldLayout(inputs.to, {
                        $overlay: this.parent.$overlay,
                        align: 'top',
                        label: mw.message('deputy.cte.mergedTo.to.label').text(),
                        help: mw.message('deputy.cte.mergedTo.to.help').text()
                    }),
                    date: new OO.ui.FieldLayout(inputs.date, {
                        $overlay: this.parent.$overlay,
                        align: 'left',
                        label: mw.message('deputy.cte.mergedTo.date.label').text(),
                        help: mw.message('deputy.cte.mergedTo.date.help').text()
                    }),
                    small: new OO.ui.FieldLayout(inputs.small, {
                        $overlay: this.parent.$overlay,
                        align: 'inline',
                        label: mw.message('deputy.cte.mergedTo.small.label').text(),
                        help: mw.message('deputy.cte.mergedTo.small.help').text()
                    })
                };
                for (const _field in inputs) {
                    const field = _field;
                    const input = inputs[field];
                    // Attach the change listener
                    input.on('change', (value) => {
                        if (input instanceof OO.ui.CheckboxInputWidget) {
                            this.mergedToTemplate[field] = value ? 'yes' : 'no';
                        }
                        else if (input instanceof mw.widgets.datetime.DateTimeInputWidget) {
                            this.mergedToTemplate[field] =
                                new Date(value).toLocaleDateString('en-GB', {
                                    year: 'numeric', month: 'long', day: 'numeric'
                                });
                            if (value.length > 0) {
                                fieldLayouts[field].setWarnings([]);
                            }
                        }
                        else {
                            this.mergedToTemplate[field] = value;
                        }
                        this.mergedToTemplate.save();
                    });
                    if (input instanceof OO.ui.TextInputWidget) {
                        // Rechecks the validity of the field.
                        input.setValidityFlag();
                    }
                }
                inputs.to.on('change', () => {
                    this.refreshLabel();
                });
                layout.addItems(getObjectValues(fieldLayouts));
                return unwrapWidget(layout);
            }
            /**
             * Sets up the outline item of this page. Used in the BookletLayout.
             */
            setupOutlineItem() {
                /** @member any */
                if (this.outlineItem !== undefined) {
                    /** @member any */
                    this.outlineItem
                        .setMovable(true)
                        .setRemovable(true)
                        .setIcon('puzzle')
                        .setLevel(0)
                        .setLabel(this.label);
                }
            }
        };
    }
    /**
     * Creates a new MergedToTemplatePage.
     *
     * @param config Configuration to be passed to the element.
     * @return A MergedToTemplatePage object
     */
    function MergedToTemplatePage (config) {
        if (!InternalMergedToTemplatePage) {
            initMergedToTemplatePage();
        }
        return new InternalMergedToTemplatePage(config);
    }

    /**
     * Represents a single {{merged-to}} template in the Parsoid document.
     */
    class MergedToTemplate extends AttributionNotice {
        /**
         * inheritDoc
         */
        parse() {
            if (this.node.hasParameter('to')) {
                this.to = this.node.getParameter('to');
            }
            else if (this.node.hasParameter('1')) {
                this.to = this.node.getParameter('1');
            }
            if (this.node.hasParameter('date')) {
                this.date = this.node.getParameter('date');
            }
            else if (this.node.hasParameter('2')) {
                this.date = this.node.getParameter('2');
            }
            if (this.node.hasParameter('small')) {
                this.small = this.node.getParameter('small');
            }
        }
        /**
         * @inheritDoc
         */
        save() {
            // Reset named parameters
            this.node.setParameter('to', null);
            this.node.setParameter('date', null);
            this.node.setParameter('1', this.to);
            this.node.setParameter('2', this.date);
            if (this.small !== undefined) {
                this.node.setParameter('small', yesNo(this.small) ? 'yes' : null);
            }
            this.dispatchEvent(new Event('save'));
        }
        /**
         * @inheritDoc
         */
        destroy() {
            this.node.destroy();
            // Self-destruct
            Object.keys(this).forEach((k) => delete this[k]);
            this.dispatchEvent(new Event('destroy'));
        }
        /**
         * @inheritDoc
         */
        generatePage(dialog) {
            return MergedToTemplatePage({
                mergedToTemplate: this,
                parent: dialog
            });
        }
    }

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

    /**
     * Replacement for String.prototype.matchALl (ES2020 only)
     *
     * @param _regex The regular expression to exec with
     * @param string The string to exec against
     */
    function matchAll(_regex, string) {
        const regex = cloneRegex$1(_regex);
        const res = [];
        let current = regex.exec(string);
        while (current != null) {
            res.push(current);
            current = regex.exec(string);
        }
        return res;
    }

    let InternalBackwardsCopyTemplateRowPage;
    /**
     * The UI representation of a {{copied}} template row. This refers to a set of `diff`, `to`,
     * or `from` parameters on each {{copied}} template.
     *
     * Note that "Page" in the class title does not refer to a MediaWiki page, but rather
     * a OOUI PageLayout.
     */
    function initBackwardsCopyTemplateRowPage() {
        InternalBackwardsCopyTemplateRowPage = class BackwardsCopyTemplateRowPage extends OO.ui.PageLayout {
            /**
             * @param config Configuration to be passed to the element.
             */
            constructor(config) {
                const { backwardsCopyTemplateRow, parent } = config;
                if (parent == null) {
                    throw new Error('Parent dialog (BackwardsCopyTemplateEditorDialog) is required');
                }
                else if (backwardsCopyTemplateRow == null) {
                    throw new Error('Reference row (BackwardsCopyTemplateRow) is required');
                }
                const finalConfig = {
                    classes: ['cte-page-row']
                };
                super(backwardsCopyTemplateRow.id, finalConfig);
                this.parent = parent;
                this.backwardsCopyTemplateRow = backwardsCopyTemplateRow;
                this.refreshLabel();
                this.backwardsCopyTemplateRow.parent.addEventListener('destroy', () => {
                    parent.rebuildPages();
                });
                this.backwardsCopyTemplateRow.parent.addEventListener('rowDelete', () => {
                    parent.rebuildPages();
                });
                this.backwardsCopyTemplateRow.parent.addEventListener('save', () => {
                    this.refreshLabel();
                });
                this.$element.append(this.render().$element);
            }
            /**
             * Refreshes the page's label
             */
            refreshLabel() {
                this.label = mw.message('deputy.cte.backwardsCopy.entry.short', this.backwardsCopyTemplateRow.title || '???').text();
                if (this.outlineItem) {
                    this.outlineItem.setLabel(this.label);
                }
            }
            /**
             * Renders this page. Returns a FieldsetLayout OOUI widget.
             *
             * @return An OOUI FieldsetLayout
             */
            render() {
                this.layout = new OO.ui.FieldsetLayout({
                    icon: 'parameter',
                    label: mw.message('deputy.cte.copied.entry.label').text(),
                    classes: ['cte-fieldset']
                });
                this.layout.$element.append(this.renderButtons());
                this.layout.addItems(this.renderFields());
                return this.layout;
            }
            /**
             * Renders a set of buttons used to modify a specific {{copied}} template row.
             *
             * @return An array of OOUI FieldLayouts
             */
            renderButtons() {
                const deleteButton = new OO.ui.ButtonWidget({
                    icon: 'trash',
                    title: mw.message('deputy.cte.backwardsCopy.entry.remove').text(),
                    framed: false,
                    flags: ['destructive']
                });
                deleteButton.on('click', () => {
                    this.backwardsCopyTemplateRow.parent.deleteRow(this.backwardsCopyTemplateRow);
                });
                return h_1("div", { style: {
                        float: 'right',
                        position: 'absolute',
                        top: '0.5em',
                        right: '0.5em'
                    } }, unwrapWidget(deleteButton));
            }
            /**
             * Renders a set of OOUI InputWidgets and FieldLayouts, eventually returning an
             * array of each FieldLayout to append to the FieldsetLayout.
             *
             * @return An array of OOUI FieldLayouts
             */
            renderFields() {
                var _a, _b, _c;
                // Use order: `date`, `monthday` + `year`, `year`
                const rowDate = (_a = this.backwardsCopyTemplateRow.date) !== null && _a !== void 0 ? _a : (this.backwardsCopyTemplateRow.monthday ?
                    `${this.backwardsCopyTemplateRow.monthday} ${this.backwardsCopyTemplateRow.year}` :
                    this.backwardsCopyTemplateRow.year);
                const parsedDate = (rowDate == null || rowDate.trim().length === 0) ?
                    undefined : (!isNaN(new Date(rowDate.trim() + ' UTC').getTime()) ?
                    (new Date(rowDate.trim() + ' UTC')) : (!isNaN(new Date(rowDate.trim()).getTime()) ?
                    new Date(rowDate.trim()) : null));
                // TODO: l10n
                const authorRegex = /(.+?, (?:[A-Z]\.\s?)*)(?:(?:&amp;|[&;]|[,;] (?:&amp;|[&;])?)\s*|$)/g;
                const authors = matchAll(authorRegex, (_b = this.backwardsCopyTemplateRow.authorlist) !== null && _b !== void 0 ? _b : this.backwardsCopyTemplateRow.author).map((v) => v[1]);
                const inputs = {
                    title: new OO.ui.TextInputWidget({
                        required: true,
                        placeholder: mw.message('deputy.cte.backwardsCopy.entry.title.placeholder').text(),
                        value: (_c = this.backwardsCopyTemplateRow.title) !== null && _c !== void 0 ? _c : this.backwardsCopyTemplateRow.articlename
                    }),
                    date: new mw.widgets.datetime.DateTimeInputWidget({
                        $overlay: this.parent.$overlay,
                        required: true,
                        calendar: null,
                        icon: 'calendar',
                        clearable: true,
                        value: parsedDate
                    }),
                    author: new OO.ui.TagMultiselectWidget({
                        allowArbitrary: true,
                        placeholder: mw.message('deputy.cte.backwardsCopy.entry.author.placeholder').text(),
                        selected: authors
                    }),
                    url: new OO.ui.TextInputWidget({
                        placeholder: mw.message('deputy.cte.backwardsCopy.entry.url.placeholder').text(),
                        value: this.backwardsCopyTemplateRow.url,
                        validate: (value) => {
                            if (value.trim().length === 0) {
                                return true;
                            }
                            try {
                                return typeof new URL(value).href === 'string';
                            }
                            catch (e) {
                                return false;
                            }
                        }
                    }),
                    org: new OO.ui.TextInputWidget({
                        placeholder: mw.message('deputy.cte.backwardsCopy.entry.org.placeholder').text(),
                        value: this.backwardsCopyTemplateRow.org
                    })
                };
                const fields = {
                    title: new OO.ui.FieldLayout(inputs.title, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.backwardsCopy.entry.title.label').text(),
                        align: 'top',
                        help: mw.message('deputy.cte.backwardsCopy.entry.title.help').text()
                    }),
                    date: new OO.ui.FieldLayout(inputs.date, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.backwardsCopy.entry.date.label').text(),
                        align: 'left',
                        help: mw.message('deputy.cte.backwardsCopy.entry.date.help').text()
                    }),
                    author: new OO.ui.FieldLayout(inputs.author, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.backwardsCopy.entry.author.label').text(),
                        align: 'left',
                        help: mw.message('deputy.cte.backwardsCopy.entry.author.help').text()
                    }),
                    url: new OO.ui.FieldLayout(inputs.url, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.backwardsCopy.entry.url.label').text(),
                        align: 'left',
                        help: mw.message('deputy.cte.backwardsCopy.entry.url.help').text()
                    }),
                    org: new OO.ui.FieldLayout(inputs.org, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.backwardsCopy.entry.org.label').text(),
                        align: 'left',
                        help: mw.message('deputy.cte.backwardsCopy.entry.org.help').text()
                    })
                };
                for (const _field in inputs) {
                    const field = _field;
                    const input = inputs[field];
                    if (field === 'author') {
                        input.on('change', (value) => {
                            if (value.length === 0) {
                                this.backwardsCopyTemplateRow.author = null;
                                this.backwardsCopyTemplateRow.authorlist = null;
                            }
                            else if (value.length > 1) {
                                this.backwardsCopyTemplateRow.author = null;
                                this.backwardsCopyTemplateRow.authorlist =
                                    // TODO: l10n
                                    value.map((v) => v.data).join('; ');
                            }
                            else {
                                this.backwardsCopyTemplateRow.authorlist = null;
                                this.backwardsCopyTemplateRow.author =
                                    value[0].data;
                            }
                            this.backwardsCopyTemplateRow.parent.save();
                        });
                    }
                    else {
                        // Attach the change listener
                        input.on('change', (value) => {
                            if (input instanceof mw.widgets.datetime.DateTimeInputWidget) {
                                this.backwardsCopyTemplateRow[field] =
                                    new Date(value).toLocaleDateString('en-GB', {
                                        year: 'numeric', month: 'long', day: 'numeric'
                                    });
                                if (value.length > 0) {
                                    fields[field].setWarnings([]);
                                }
                            }
                            else {
                                this.backwardsCopyTemplateRow[field] = value;
                            }
                            this.backwardsCopyTemplateRow.parent.save();
                        });
                    }
                    if (input instanceof OO.ui.TextInputWidget) {
                        // Rechecks the validity of the field.
                        input.setValidityFlag();
                    }
                }
                return getObjectValues(fields);
            }
            /**
             * Sets up the outline item of this page. Used in the BookletLayout.
             */
            setupOutlineItem() {
                /** @member any */
                if (this.outlineItem !== undefined) {
                    /** @member any */
                    this.outlineItem
                        .setMovable(true)
                        .setRemovable(true)
                        .setIcon('parameter')
                        .setLevel(1)
                        .setLabel(this.label);
                }
            }
        };
    }
    /**
     * Creates a new BackwardsCopyTemplateRowPage.
     *
     * @param config Configuration to be passed to the element.
     * @return A BackwardsCopyTemplateRowPage object
     */
    function BackwardsCopyTemplateRowPage (config) {
        if (!InternalBackwardsCopyTemplateRowPage) {
            initBackwardsCopyTemplateRowPage();
        }
        return new InternalBackwardsCopyTemplateRowPage(config);
    }

    const backwardsCopyTemplateRowParameters = [
        'title', 'year', 'author', 'authorlist',
        'display_authors', 'url', 'org', 'monthday',
        'articlename', 'date'
    ];
    /**
     * Represents a row/entry in a {{copied}} template.
     */
    class BackwardsCopyTemplateRow extends AttributionNoticeRow {
        // noinspection JSDeprecatedSymbols
        /**
         * Creates a new RawBackwardsCopyRow
         *
         * @param rowObjects
         * @param parent
         */
        constructor(rowObjects, parent) {
            super(parent);
            this.articlename = rowObjects.articlename;
            this.title = rowObjects.title;
            this.year = rowObjects.year;
            this.author = rowObjects.author;
            this.authorlist = rowObjects.authorlist;
            // eslint-disable-next-line camelcase
            this.display_authors = rowObjects.display_authors;
            this.url = rowObjects.url;
            this.org = rowObjects.org;
            this.date = rowObjects.date;
            this.monthday = rowObjects.monthday;
        }
        /**
         * @inheritDoc
         */
        clone(parent) {
            return super.clone(parent);
        }
        /**
         * @inheritDoc
         */
        generatePage(dialog) {
            return BackwardsCopyTemplateRowPage({
                backwardsCopyTemplateRow: this,
                parent: dialog
            });
        }
    }

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

    /**
     * Displayed when the actively-edited notice is in a demonstration mode or `nocat` mode.
     *
     * @param nocat
     * @return HTML element
     */
    function DemoTemplateMessage (nocat = false) {
        return h_1("span", null,
            h_1("b", null, mw.message(nocat ? 'deputy.cci.nocat.head' : 'deputy.cci.demo.head').parseDom().get()),
            h_1("br", null),
            mw.message(nocat ? 'deputy.cci.nocat.help' : 'deputy.cci.demo.help').parseDom().get(),
            h_1("br", null),
            h_1("span", { class: "cte-message-button" }));
    }

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

    let InternalBackwardsCopyTemplatePage;
    /**
     * UI representation of a {{backwards copy}} template. This representation is further broken
     * down with `BackwardsCopyTemplateRowPage`, which represents each row on the template.
     *
     * Note that "Page" in the class title does not refer to a MediaWiki page, but rather
     * a OOUI PageLayout.
     */
    function initBackwardsCopyTemplatePage() {
        InternalBackwardsCopyTemplatePage = class BackwardsCopyTemplatePage extends OO.ui.PageLayout {
            /**
             * @param config Configuration to be passed to the element.
             */
            constructor(config) {
                const { backwardsCopyTemplate, parent } = config;
                if (parent == null) {
                    throw new Error('Parent dialog (BackwardsCopyTemplateEditorDialog) is required');
                }
                else if (backwardsCopyTemplate == null) {
                    throw new Error('Reference template (BackwardsCopyTemplate) is required');
                }
                const label = mw.message('deputy.cte.backwardsCopy.label', config.backwardsCopyTemplate.name).text();
                const finalConfig = {
                    label: label,
                    classes: ['cte-page-template']
                };
                super(backwardsCopyTemplate.id, finalConfig);
                /**
                 * All child pages of this BackwardsCopyTemplatePage. Garbage collected when rechecked.
                 */
                this.childPages = new Map();
                this.document = config.backwardsCopyTemplate.parsoid;
                this.backwardsCopyTemplate = config.backwardsCopyTemplate;
                this.parent = config.parent;
                this.label = label;
                backwardsCopyTemplate.addEventListener('rowAdd', () => {
                    parent.rebuildPages();
                });
                backwardsCopyTemplate.addEventListener('rowDelete', () => {
                    parent.rebuildPages();
                });
                backwardsCopyTemplate.addEventListener('destroy', () => {
                    parent.rebuildPages();
                });
                this.$element.append(this.renderButtons(), this.renderHeader(), renderMergePanel('backwardsCopy', this.backwardsCopyTemplate, this.mergeButton), this.renderBotPanel(), this.renderDemoPanel(), renderPreviewPanel(this.backwardsCopyTemplate), this.renderTemplateOptions());
            }
            /**
             * @inheritDoc
             */
            getChildren() {
                const rows = this.backwardsCopyTemplate.rows;
                const rowPages = [];
                for (const row of rows) {
                    if (!this.childPages.has(row)) {
                        this.childPages.set(row, row.generatePage(this.parent));
                    }
                    rowPages.push(this.childPages.get(row));
                }
                // Delete deleted rows from cache.
                this.childPages.forEach((page, row) => {
                    if (rowPages.indexOf(page) === -1) {
                        this.childPages.delete(row);
                    }
                });
                return rowPages;
            }
            /**
             * @return The rendered header of this PageLayout.
             */
            renderHeader() {
                return h_1("h3", null, this.label);
            }
            /**
             * Renders the set of buttons that appear at the top of the page.
             *
             * @return A <div> element.
             */
            renderButtons() {
                const buttonSet = h_1("div", { style: { float: 'right' } });
                this.mergeButton = new OO.ui.ButtonWidget({
                    icon: 'tableMergeCells',
                    title: mw.message('deputy.cte.merge').text(),
                    framed: false
                });
                const deleteButton = new OO.ui.ButtonWidget({
                    icon: 'trash',
                    title: mw.message('deputy.cte.copied.remove').text(),
                    framed: false,
                    flags: ['destructive']
                });
                deleteButton.on('click', () => {
                    if (this.backwardsCopyTemplate.rows.length > 0) {
                        OO.ui.confirm(mw.message('deputy.cte.copied.remove.confirm', `${this.backwardsCopyTemplate.rows.length}`).text()).done((confirmed) => {
                            if (confirmed) {
                                this.backwardsCopyTemplate.destroy();
                            }
                        });
                    }
                    else {
                        this.backwardsCopyTemplate.destroy();
                    }
                });
                const addButton = new OO.ui.ButtonWidget({
                    flags: ['progressive'],
                    icon: 'add',
                    label: mw.message('deputy.cte.copied.add').text()
                });
                addButton.on('click', () => {
                    this.backwardsCopyTemplate.addRow(new BackwardsCopyTemplateRow({}, this.backwardsCopyTemplate));
                });
                buttonSet.appendChild(unwrapWidget(this.mergeButton));
                buttonSet.appendChild(unwrapWidget(deleteButton));
                buttonSet.appendChild(unwrapWidget(addButton));
                return buttonSet;
            }
            /**
             * Renders a panel that shows when a bot is used.
             *
             * @return An unwrapped OOUI MessageWidget
             */
            renderBotPanel() {
                if (this.backwardsCopyTemplate.node.hasParameter('bot')) {
                    const bot = this.backwardsCopyTemplate.node.getParameter('bot');
                    return unwrapWidget(new OO.ui.MessageWidget({
                        type: 'notice',
                        icon: 'robot',
                        label: new OO.ui.HtmlSnippet(mw.message('deputy.cte.backwardsCopy.bot', bot).parse())
                    }));
                }
                else {
                    return null;
                }
            }
            /**
             * Renders a panel that shows when demo mode is enabled.
             *
             * @return An unwrapped OOUI MessageWidget
             */
            renderDemoPanel() {
                if (this.backwardsCopyTemplate.node.hasParameter('bot')) {
                    // Insert element directly into widget (not as text, or else event
                    // handlers will be destroyed).
                    const messageBox = new OO.ui.MessageWidget({
                        type: 'notice',
                        icon: 'alert',
                        label: new OO.ui.HtmlSnippet(DemoTemplateMessage().innerHTML)
                    });
                    const clearButton = new OO.ui.ButtonWidget({
                        flags: ['progressive', 'primary'],
                        label: mw.message('deputy.cte.demo.clear').text()
                    });
                    clearButton.on('click', () => {
                        this.backwardsCopyTemplate.node.removeParameter('demo');
                        removeElement(unwrapWidget(messageBox));
                    });
                    swapElements(unwrapWidget(messageBox)
                        .querySelector('.cte-message-button'), unwrapWidget(clearButton));
                    return unwrapWidget(messageBox);
                }
                else {
                    return null;
                }
            }
            /**
             * Renders the panel used to merge multiple {{copied}} templates.
             *
             * @return A <div> element
             */
            renderMergePanel() {
                return renderMergePanel('backwardsCopy', this.backwardsCopyTemplate, this.mergeButton);
            }
            /**
             * Renders the global options of this template. This includes parameters that are not
             * counted towards an entry and affect the template as a whole.
             *
             * @return A <div> element.
             */
            renderTemplateOptions() {
                var _a, _b;
                const inputSet = {
                    comments: new OO.ui.TextInputWidget({
                        placeholder: mw.message('deputy.cte.backwardsCopy.comments.placeholder').text(),
                        value: (_a = this.backwardsCopyTemplate.comments) === null || _a === void 0 ? void 0 : _a.trim()
                    }),
                    id: new OO.ui.TextInputWidget({
                        placeholder: mw.message('deputy.cte.backwardsCopy.id.placeholder').text(),
                        value: (_b = this.backwardsCopyTemplate.revid) === null || _b === void 0 ? void 0 : _b.trim()
                    })
                };
                const fields = {
                    comments: new OO.ui.FieldLayout(inputSet.comments, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.backwardsCopy.comments.label').text(),
                        help: mw.message('deputy.cte.backwardsCopy.comments.help').text(),
                        align: 'top'
                    }),
                    id: new OO.ui.FieldLayout(inputSet.id, {
                        $overlay: this.parent.$overlay,
                        label: mw.message('deputy.cte.backwardsCopy.id.label').text(),
                        help: mw.message('deputy.cte.backwardsCopy.id.help').text(),
                        align: 'top'
                    })
                };
                inputSet.comments.on('change', (value) => {
                    this.backwardsCopyTemplate.comments = value.trim();
                    this.backwardsCopyTemplate.save();
                });
                inputSet.id.on('change', (value) => {
                    this.backwardsCopyTemplate.revid = value.trim();
                    this.backwardsCopyTemplate.save();
                });
                return h_1("div", { class: "cte-templateOptions" },
                    h_1("div", { style: { marginRight: '8px' } }, unwrapWidget(fields.comments)),
                    h_1("div", { style: { flex: '0.5' } }, unwrapWidget(fields.id)));
            }
            /**
             * Sets up the outline item of this page. Used in the BookletLayout.
             */
            setupOutlineItem() {
                /** @member any */
                if (this.outlineItem !== undefined) {
                    /** @member any */
                    this.outlineItem
                        .setMovable(true)
                        .setRemovable(true)
                        .setIcon('puzzle')
                        .setLevel(0)
                        .setLabel(this.label);
                }
            }
        };
    }
    /**
     * Creates a new BackwardsCopyTemplatePage.
     *
     * @param config Configuration to be passed to the element.
     * @return A BackwardsCopyTemplatePage object
     */
    function BackwardsCopyTemplatePage (config) {
        if (!InternalBackwardsCopyTemplatePage) {
            initBackwardsCopyTemplatePage();
        }
        return new InternalBackwardsCopyTemplatePage(config);
    }

    /**
     * Represents a single {{copied}} template in the Parsoid document.
     */
    class BackwardsCopyTemplate extends RowedAttributionNotice {
        /**
         * @return This template's rows.
         */
        get rows() {
            return this._rows;
        }
        /**
         * Parses parameters into class properties. This WILL destroy unknown
         * parameters and parameters in the incorrect order!
         *
         * This function does not modify the template data.
         */
        parse() {
            if (this.node.getParameter('demo')) {
                this.demo = this.node.getParameter('demo');
            }
            if (this.node.getParameter('comments')) {
                this.comments = this.node.getParameter('comments');
            }
            if (this.node.getParameter('id')) {
                this.revid = this.node.getParameter('id');
            }
            // Extract {{backwards copy}} rows.
            const rows = [];
            // Numberless
            if (this.hasRowParameters(backwardsCopyTemplateRowParameters)) {
                // If `from`, `to`, ..., or `merge` is found.
                rows.push(new BackwardsCopyTemplateRow(this.extractRowParameters(backwardsCopyTemplateRowParameters), this));
            }
            // Numbered
            let i = 1, continueExtracting = true;
            do {
                if (this.hasRowParameters(backwardsCopyTemplateRowParameters, i)) {
                    rows.push(new BackwardsCopyTemplateRow(this.extractRowParameters(backwardsCopyTemplateRowParameters, i), this));
                }
                else if (!(i === 1 && rows.length > 0)) {
                    // Row doesn't exist. Stop parsing from here.
                    continueExtracting = false;
                }
                i++;
            } while (continueExtracting);
            /**
             * All the rows of this template.
             *
             * @type {BackwardsCopyTemplateRow[]}
             */
            this._rows = rows;
        }
        /**
         * Saves the current template data to the Parsoid element.
         */
        save() {
            this.node.removeParameter('bot');
            if (this.demo) {
                this.node.setParameter('demo', this.demo);
            }
            this.node.setParameter('comments', this.comments);
            this.node.setParameter('id', this.revid);
            const existingParameters = this.node.getParameters();
            for (const param in existingParameters) {
                if (backwardsCopyTemplateRowParameters.some((v) => param.startsWith(v))) {
                    // This is a row parameter. Remove it in preparation for rebuild (further below).
                    this.node.removeParameter(param);
                }
            }
            if (this._rows.length === 1) {
                // If there is only one row, don't bother with numbered rows.
                for (const param of backwardsCopyTemplateRowParameters) {
                    if (this._rows[0][param] !== undefined) {
                        this.node.setParameter(param, this._rows[0][param]);
                    }
                }
            }
            else {
                // If there are multiple rows, add number suffixes (except for i = 0).
                for (let i = 0; i < this._rows.length; i++) {
                    for (const param of backwardsCopyTemplateRowParameters) {
                        if (this._rows[i][param] !== undefined) {
                            this.node.setParameter(param + (i === 0 ? '' : i + 1), this._rows[i][param]);
                        }
                    }
                }
            }
            this.dispatchEvent(new Event('save'));
        }
        /**
         * Destroys this template completely.
         */
        destroy() {
            this.node.destroy();
            // Self-destruct
            Object.keys(this).forEach((k) => delete this[k]);
            this.dispatchEvent(new Event('destroy'));
        }
        /**
         * @inheritDoc
         */
        generatePage(dialog) {
            return BackwardsCopyTemplatePage({
                backwardsCopyTemplate: this,
                parent: dialog
            });
        }
        /**
         * Copies in the rows of another {@link BackwardsCopyTemplate}, and
         * optionally deletes that template or clears its contents.
         *
         * @param template The template to copy from.
         * @param options Options for this merge.
         * @param options.delete
         *        Whether the reference template will be deleted after merging.
         * @param options.clear
         *        Whether the reference template's rows will be cleared after merging.
         */
        merge(template, options = {}) {
            if (template.rows === undefined || template === this) {
                // Deleted or self
                return;
            }
            for (const row of template.rows) {
                if (options.clear) {
                    row.parent = this;
                }
                else {
                    this.addRow(row.clone(this));
                }
            }
            if (options.delete) {
                template.destroy();
            }
        }
    }

    let InternalTranslatedPageTemplatePage;
    /**
     * Initializes the process element.
     */
    function initTranslatedPageTemplatePage() {
        InternalTranslatedPageTemplatePage = class TranslatedPageTemplatePage extends OO.ui.PageLayout {
            /**
             * @param config Configuration to be passed to the element.
             */
            constructor(config) {
                const { translatedPageTemplate, parent } = config;
                if (parent == null) {
                    throw new Error('Parent dialog (CopiedTemplateEditorDialog) is required');
                }
                else if (translatedPageTemplate == null) {
                    throw new Error('Reference template (TranslatedPageTemplate) is required');
                }
                const finalConfig = {
                    classes: ['cte-page-template']
                };
                super(translatedPageTemplate.id, finalConfig);
                this.document = translatedPageTemplate.parsoid;
                this.translatedPageTemplate = translatedPageTemplate;
                this.parent = config.parent;
                this.refreshLabel();
                translatedPageTemplate.addEventListener('destroy', () => {
                    parent.rebuildPages();
                });
                this.$element.append(this.renderButtons(), this.renderHeader(), renderPreviewPanel(this.translatedPageTemplate), this.renderTemplateOptions());
            }
            /**
             * Refreshes the page's label
             */
            refreshLabel() {
                this.label = mw.message('deputy.cte.translatedPage.label', this.translatedPageTemplate.lang || '??', this.translatedPageTemplate.page || '???').text();
                if (this.outlineItem) {
                    this.outlineItem.setLabel(this.label);
                }
            }
            /**
             * Renders the set of buttons that appear at the left of the page.
             *
             * @return A <div> element.
             */
            renderButtons() {
                const buttonSet = h_1("div", { style: { float: 'right' } });
                const deleteButton = new OO.ui.ButtonWidget({
                    icon: 'trash',
                    title: mw.message('deputy.cte.translatedPage.remove').text(),
                    framed: false,
                    flags: ['destructive']
                });
                deleteButton.on('click', () => {
                    this.translatedPageTemplate.destroy();
                });
                buttonSet.appendChild(unwrapWidget(deleteButton));
                return buttonSet;
            }
            /**
             * @return The rendered header of this PageLayout.
             */
            renderHeader() {
                return h_1("h3", null, this.label);
            }
            /**
             * @return The options for this template
             */
            renderTemplateOptions() {
                var _a;
                const layout = new OO.ui.FieldsetLayout({
                    icon: 'parameter',
                    label: mw.message('deputy.cte.templateOptions').text(),
                    classes: ['cte-fieldset']
                });
                const searchApi = new mw.ForeignApi(mw.util.wikiScript('api'), {
                    anonymous: true
                });
                const inputs = {
                    lang: new OO.ui.TextInputWidget({
                        required: true,
                        value: this.translatedPageTemplate.lang,
                        placeholder: mw.message('deputy.cte.translatedPage.lang.placeholder').text(),
                        validate: /^[a-z\d-]+$/gi
                    }),
                    page: new mw.widgets.TitleInputWidget({
                        $overlay: this.parent.$overlay,
                        api: searchApi,
                        required: true,
                        value: this.translatedPageTemplate.page || '',
                        placeholder: mw.message('deputy.cte.translatedPage.page.placeholder').text()
                    }),
                    comments: new OO.ui.TextInputWidget({
                        value: this.translatedPageTemplate.comments,
                        placeholder: mw.message('deputy.cte.translatedPage.comments.placeholder').text()
                    }),
                    version: new OO.ui.NumberInputWidget({
                        value: this.translatedPageTemplate.comments,
                        placeholder: mw.message('deputy.cte.translatedPage.version.placeholder').text()
                    }),
                    insertversion: new OO.ui.NumberInputWidget({
                        value: this.translatedPageTemplate.comments,
                        placeholder: mw.message('deputy.cte.translatedPage.insertversion.placeholder').text()
                    }),
                    section: new OO.ui.TextInputWidget({
                        value: this.translatedPageTemplate.section,
                        placeholder: mw.message('deputy.cte.translatedPage.section.placeholder').text()
                    }),
                    small: new OO.ui.CheckboxInputWidget({
                        selected: yesNo((_a = this.translatedPageTemplate.small) !== null && _a !== void 0 ? _a : 'yes')
                    }),
                    partial: new OO.ui.CheckboxInputWidget({
                        selected: !!this.translatedPageTemplate.partial
                    })
                };
                const fieldLayouts = {
                    lang: new OO.ui.FieldLayout(inputs.lang, {
                        $overlay: this.parent.$overlay,
                        align: 'left',
                        label: mw.message('deputy.cte.translatedPage.lang.label').text(),
                        help: mw.message('deputy.cte.translatedPage.lang.help').text()
                    }),
                    page: new OO.ui.FieldLayout(inputs.page, {
                        $overlay: this.parent.$overlay,
                        align: 'left',
                        label: mw.message('deputy.cte.translatedPage.page.label').text(),
                        help: mw.message('deputy.cte.translatedPage.page.help').text()
                    }),
                    comments: new OO.ui.FieldLayout(inputs.comments, {
                        $overlay: this.parent.$overlay,
                        align: 'left',
                        label: mw.message('deputy.cte.translatedPage.comments.label').text(),
                        help: mw.message('deputy.cte.translatedPage.comments.help').text()
                    }),
                    version: new OO.ui.FieldLayout(inputs.version, {
                        $overlay: this.parent.$overlay,
                        align: 'left',
                        label: mw.message('deputy.cte.translatedPage.version.label').text(),
                        help: mw.message('deputy.cte.translatedPage.version.help').text()
                    }),
                    insertversion: new OO.ui.FieldLayout(inputs.insertversion, {
                        $overlay: this.parent.$overlay,
                        align: 'left',
                        label: mw.message('deputy.cte.translatedPage.insertversion.label').text(),
                        help: mw.message('deputy.cte.translatedPage.insertversion.help').text()
                    }),
                    section: new OO.ui.FieldLayout(inputs.section, {
                        $overlay: this.parent.$overlay,
                        align: 'left',
                        label: mw.message('deputy.cte.translatedPage.section.label').text(),
                        help: mw.message('deputy.cte.translatedPage.section.help').text()
                    }),
                    small: new OO.ui.FieldLayout(inputs.small, {
                        $overlay: this.parent.$overlay,
                        align: 'inline',
                        label: mw.message('deputy.cte.translatedPage.small.label').text(),
                        help: mw.message('deputy.cte.translatedPage.small.help').text()
                    }),
                    partial: new OO.ui.FieldLayout(inputs.partial, {
                        $overlay: this.parent.$overlay,
                        align: 'inline',
                        label: mw.message('deputy.cte.translatedPage.partial.label').text(),
                        help: mw.message('deputy.cte.translatedPage.partial.help').text()
                    })
                };
                for (const _field in inputs) {
                    const field = _field;
                    const input = inputs[field];
                    // Attach the change listener
                    input.on('change', (value) => {
                        if (input instanceof OO.ui.CheckboxInputWidget) {
                            this.translatedPageTemplate[field] = value ? 'yes' : 'no';
                        }
                        else {
                            this.translatedPageTemplate[field] = value;
                        }
                        this.translatedPageTemplate.save();
                    });
                    if (input instanceof OO.ui.TextInputWidget) {
                        // Rechecks the validity of the field.
                        input.setValidityFlag();
                    }
                }
                inputs.lang.on('change', (value) => {
                    this.refreshLabel();
                    if (!/^[a-z\d-]+$/gi.test(value)) {
                        return;
                    }
                    searchApi.apiUrl = searchApi.defaults.ajax.url =
                        '//' + value + '.wikipedia.org/w/api.php';
                });
                inputs.page.on('change', () => {
                    this.refreshLabel();
                });
                if (this.translatedPageTemplate.lang) {
                    searchApi.apiUrl = searchApi.defaults.ajax.url =
                        '//' + this.translatedPageTemplate.lang + '.wikipedia.org/w/api.php';
                }
                layout.addItems(getObjectValues(fieldLayouts));
                return unwrapWidget(layout);
            }
            /**
             * Sets up the outline item of this page. Used in the BookletLayout.
             */
            setupOutlineItem() {
                /** @member any */
                if (this.outlineItem !== undefined) {
                    /** @member any */
                    this.outlineItem
                        .setMovable(true)
                        .setRemovable(true)
                        .setIcon('puzzle')
                        .setLevel(0)
                        .setLabel(this.label);
                }
            }
        };
    }
    /**
     * Creates a new TranslatedPageTemplatePage.
     *
     * @param config Configuration to be passed to the element.
     * @return A TranslatedPageTemplatePage object
     */
    function TranslatedPageTemplatePage (config) {
        if (!InternalTranslatedPageTemplatePage) {
            initTranslatedPageTemplatePage();
        }
        return new InternalTranslatedPageTemplatePage(config);
    }

    /**
     * Represents a single {{merged-from}} template in the Parsoid document.
     */
    class TranslatedPageTemplate extends AttributionNotice {
        /**
         * @inheritDoc
         */
        parse() {
            this.lang = this.node.getParameter('1');
            this.page = this.node.getParameter('2');
            this.comments = this.node.getParameter('3');
            this.version = this.node.getParameter('version');
            this.insertversion = this.node.getParameter('insertversion');
            this.section = this.node.getParameter('section');
            this.small = this.node.getParameter('small');
            this.partial = this.node.getParameter('partial');
        }
        /**
         * @inheritDoc
         */
        save() {
            this.node.setParameter('1', this.lang);
            this.node.setParameter('2', this.page);
            this.node.setParameter('3', this.comments);
            this.node.setParameter('version', this.version);
            this.node.setParameter('insertversion', this.insertversion);
            this.node.setParameter('section', this.section);
            if (this.small !== undefined) {
                this.node.setParameter('small', yesNo(this.small) ? null : 'no');
            }
            if (this.partial !== undefined) {
                this.node.setParameter('partial', yesNo(this.partial) ? 'yes' : null);
            }
            this.dispatchEvent(new Event('save'));
        }
        /**
         * @inheritDoc
         */
        destroy() {
            this.node.destroy();
            // Self-destruct
            Object.keys(this).forEach((k) => delete this[k]);
            this.dispatchEvent(new Event('destroy'));
        }
        /**
         * @inheritDoc
         */
        generatePage(dialog) {
            return TranslatedPageTemplatePage({
                translatedPageTemplate: this,
                parent: dialog
            });
        }
    }

    /**
     * An object mapping notice types to their expected on-wiki page titles.
     */
    const attributionNoticeTemplatePages = {
        copied: 'Copied',
        splitArticle: 'Split article',
        mergedFrom: 'Merged-from',
        mergedTo: 'Merged-to',
        backwardsCopy: 'Backwards copy',
        translatedPage: 'Translated page'
    };
    /**
     * This class contains functions, utilities, and other variables that assist in connecting
     * attribution notice templates on-wiki and converting them into their AttributionNotice
     * counterparts.
     */
    class WikiAttributionNotices {
        /**
         * Initializes.
         */
        static init() {
            return __awaiter(this, void 0, void 0, function* () {
                const attributionNoticeTemplates = {};
                const templateAliasCache = {};
                for (const key of Object.keys(attributionNoticeTemplatePages)) {
                    attributionNoticeTemplates[key] = new mw.Title(attributionNoticeTemplatePages[key], nsId('template'));
                    templateAliasCache[key] = [attributionNoticeTemplates[key]];
                }
                this.attributionNoticeTemplates = attributionNoticeTemplates;
                this.templateAliasCache = templateAliasCache;
                // templateAliasCache setup
                const aliasRequest = yield MwApi.action.get({
                    action: 'query',
                    format: 'json',
                    prop: 'linkshere',
                    titles: getObjectValues(this.attributionNoticeTemplates)
                        .map((v) => v.getPrefixedText())
                        .join('|'),
                    lhprop: 'title',
                    lhnamespace: nsId('template'),
                    lhshow: 'redirect',
                    lhlimit: '500'
                });
                const aliasRequestRedirects = toRedirectsObject(aliasRequest.query.redirects);
                for (const page of aliasRequest.query.pages) {
                    let cacheKey;
                    // Find the key of this page in the list of attribution notice templates.
                    // Slightly expensive, but this init should only be run once anyway.
                    for (const key in this.attributionNoticeTemplates) {
                        const templatePage = this.attributionNoticeTemplates[key].getPrefixedText();
                        if (
                        // Page is a perfect match.
                        templatePage === page.title ||
                            // If the page was moved, and the page originally listed above is a redirect.
                            // This checks if the resolved redirect matches the input page.
                            aliasRequestRedirects[templatePage] === page.title) {
                            cacheKey = key;
                            break;
                        }
                    }
                    if (!cacheKey) {
                        // Unexpected key not found. Page must have been moved or modified.
                        // Give up here.
                        continue;
                    }
                    const links = page.linkshere.map((v) => new mw.Title(v.title));
                    this.templateAliasCache[cacheKey].push(...links);
                }
                // templateAliasKeymap setup
                this.templateAliasKeymap = {};
                for (const noticeType in this.templateAliasCache) {
                    for (const title of this.templateAliasCache[noticeType]) {
                        this.templateAliasKeymap[title.getPrefixedDb()] = noticeType;
                    }
                }
                // templateAliasRegExp setup
                const summarizedTitles = [];
                for (const titles of getObjectValues(this.templateAliasCache)) {
                    for (const title of titles) {
                        summarizedTitles.push(title.getPrefixedDb());
                    }
                }
                this.templateAliasRegExp = new RegExp(summarizedTitles.map((v) => `(${mw.util.escapeRegExp(v)})`).join('|'), 'g');
            });
        }
        /**
         * Get the notice type of a given template from its href string, or `undefined` if it
         * is not a valid notice.
         *
         * @param href The href of the template.
         * @return A notice type string.
         */
        static getTemplateNoticeType(href) {
            return this.templateAliasKeymap[href.replace(/^\.\//, '')];
        }
    }
    /**
     * An object mapping notice types to their respective class.
     */
    WikiAttributionNotices.attributionNoticeClasses = {
        copied: CopiedTemplate,
        splitArticle: SplitArticleTemplate,
        mergedFrom: MergedFromTemplate,
        mergedTo: MergedToTemplate,
        backwardsCopy: BackwardsCopyTemplate,
        translatedPage: TranslatedPageTemplate
    };

    /**
     * Renders a MenuLayout responsible for displaying analysis options or tools.
     */
    class AttributionNoticeAddMenu {
        /**
         * @param document
         * @param baseWidget
         */
        constructor(document, baseWidget) {
            this.document = document;
            this.baseWidget = baseWidget;
        }
        /**
         * @inheritDoc
         */
        render() {
            const menuItems = new Map();
            const menuSelectWidget = new OO.ui.MenuSelectWidget({
                hideWhenOutOfView: false,
                verticalPosition: 'below',
                horizontalPosition: 'start',
                widget: this.baseWidget,
                $floatableContainer: this.baseWidget.$element,
                items: Object.keys(attributionNoticeTemplatePages).map((key) => {
                    const item = new OO.ui.MenuOptionWidget({
                        data: key,
                        icon: 'add',
                        // Will automatically use template name as
                        // provided by WikiAttributionNotices.
                        label: `{{${attributionNoticeTemplatePages[key]}}}`,
                        flags: ['progressive']
                    });
                    menuItems.set(item, key);
                    return item;
                })
            });
            menuSelectWidget.on('select', () => {
                const selected = menuSelectWidget.findSelectedItem();
                if (selected) {
                    const type = selected.getData();
                    const spot = this.document.findNoticeSpot(type);
                    this.document.insertNewNotice(type, spot);
                    // Clear selections.
                    menuSelectWidget.selectItem();
                }
            });
            // Disables clipping (allows the menu to be wider than the button)
            menuSelectWidget.toggleClipping(false);
            this.baseWidget.on('click', () => {
                menuSelectWidget.toggle(true);
            });
            return unwrapWidget(menuSelectWidget);
        }
    }

    let InternalAttributionNoticesEmptyPage;
    /**
     * Initializes the process element.
     */
    function initAttributionNoticesEmptyPage() {
        InternalAttributionNoticesEmptyPage = class AttributionNoticesEmptyPage extends OO.ui.PageLayout {
            /**
             * @param config Configuration to be passed to the element.
             */
            constructor(config) {
                super('cte-no-templates', {});
                this.parent = config.parent;
                this.parsoid = config.parsoid;
                const addListener = this.parent.layout.on('add', () => {
                    for (const name of Object.keys(this.parent.layout.pages)) {
                        /** @member any */
                        if (name !== 'cte-no-templates' && this.outlineItem !== null) {
                            // Pop this page out if a page exists.
                            this.parent.layout.removePages([this]);
                            this.parent.layout.off(addListener);
                            return;
                        }
                    }
                });
                // Render the page.
                const add = new OO.ui.ButtonWidget({
                    icon: 'add',
                    label: mw.message('deputy.cte.empty.add').text(),
                    flags: ['progressive']
                });
                this.parent.$overlay.append(new AttributionNoticeAddMenu(this.parsoid, add).render());
                this.$element.append(h_1("h3", null, mw.message('deputy.cte.empty.header').text()), h_1("p", null, mw.message(this.parsoid.originalCount > 0 ?
                    'deputy.cte.empty.removed' :
                    'deputy.cte.empty.none').text()), add.$element);
            }
            /**
             * Sets up the outline item of this page. Used in the BookletLayout.
             */
            setupOutlineItem() {
                /** @member any */
                if (this.outlineItem !== undefined) {
                    /** @member any */
                    this.outlineItem.toggle(false);
                }
            }
        };
    }
    /**
     * Creates a new AttributionNoticesEmptyPage.
     *
     * @param config Configuration to be passed to the element.
     * @return A AttributionNoticesEmptyPage object
     */
    function CopiedTemplatesEmptyPage (config) {
        if (!InternalAttributionNoticesEmptyPage) {
            initAttributionNoticesEmptyPage();
        }
        return new InternalAttributionNoticesEmptyPage(config);
    }

    var ParsoidDocument_module = {};

    Object.defineProperty(ParsoidDocument_module, "__esModule", { value: true });
    // ParsoidDocument:start
    /**
     * The root of this wiki's RestBase endpoint. This MUST NOT end with a slash.
     */
    const restBaseRoot = window.restBaseRoot || '/api/rest_';
    /**
     * Encodes text for an API parameter. This performs both an encodeURIComponent
     * and a string replace to change spaces into underscores.
     *
     * @param {string} text
     * @return {string}
     */
    function encodeAPIComponent(text) {
        return encodeURIComponent(text.replace(/ /g, '_'));
    }
    /**
     * Clones a regular expression.
     *
     * @param regex The regular expression to clone.
     * @return A new regular expression object.
     */
    function cloneRegex(regex) {
        return new RegExp(regex.source, regex.flags);
    }
    /**
     * A class denoting a transclusion template node (a transcluded template, barring any included
     * text or inline parameters) inside an element with [typeof="mw:Transclusion"].
     */
    class ParsoidTransclusionTemplateNode {
        /**
         * Create a new ParsoidTransclusionTemplateNode.
         *
         * @param {ParsoidDocument} parsoidDocument
         *     The document handling this transclusion node.
         * @param {HTMLElement} originalElement
         *     The original element where the `data-mw` of this node is found.
         * @param {*} data
         *     The `data-mw` `part.template` of this node.
         * @param {number} i
         *     The `i` property of this node.
         * @param {boolean} autosave
         *     Whether to automatically save parameter and target changes or not.
         */
        constructor(parsoidDocument, originalElement, data, i, autosave = true) {
            this.parsoidDocument = parsoidDocument;
            this.element = originalElement;
            this.data = data;
            this.i = i;
            this.autosave = autosave;
        }
        /**
         * Creates a new ParsoidTransclusionTemplateNode. Can be used later on to add a template
         * into wikitext. To have this node show up in wikitext, append the node's element (using
         * {@link ParsoidTransclusionTemplateNode.element}) to the document of a ParsoidDocument.
         *
         * @param document The document used to generate this node.
         * @param template The template to create. If you wish to generate wikitext as a block-type
         *   transclusion (as long as a format is not provided through TemplateData), append a "\n"
         *   to the end of the template name.
         * @param parameters The parameters to the template.
         * @param autosave
         * @return A new ParsoidTransclusionTemplateNode.
         */
        static fromNew(document, template, parameters, autosave) {
            const el = document.getDocument().createElement('span');
            const target = { wt: template };
            if (mw.Title) {
                // If `mediawiki.Title` is loaded, use it.
                target.href = './' + new mw.Title(target.wt, mw.config.get('wgNamespaceIds').template).getPrefixedDb();
            }
            const data = {
                target,
                params: {},
                i: 0
            };
            for (const param in (parameters !== null && parameters !== void 0 ? parameters : {})) {
                const value = parameters[param];
                data.params[param] = {
                    wt: typeof value === 'string' ? value : value.toString()
                };
            }
            el.setAttribute('typeof', 'mw:Transclusion');
            el.setAttribute('data-mw', JSON.stringify({
                parts: [{
                        template: data
                    }]
            }));
            return new ParsoidTransclusionTemplateNode(document, el, data, data.i, autosave);
        }
        /**
         * Gets the target of this node.
         *
         * @return {Object} The target of this node, in wikitext and href (for links).
         */
        getTarget() {
            return this.data.target;
        }
        /**
         * Sets the target of this template (in wikitext).
         *
         * @param {string} wikitext
         *   The target template (in wikitext, e.g. `Test/{{FULLPAGENAME}}`).
         */
        setTarget(wikitext) {
            this.data.target.wt = wikitext;
            if (mw.Title) {
                // If `mediawiki.Title` is loaded, use it.
                this.data.target.href = './' + new mw.Title(wikitext, mw.config.get('wgNamespaceIds').template).getPrefixedDb();
            }
            else {
                // Likely inaccurate. Just remove it to make sent data cleaner.
                delete this.data.target.href;
            }
            if (this.autosave) {
                this.save();
            }
        }
        /**
         * Gets the parameters of this node.
         *
         * @return {Object.<string, {wt: string}>} The parameters of this node, in wikitext.
         */
        getParameters() {
            return this.data.params;
        }
        /**
         * Checks if a template has a parameter.
         *
         * @param {string} key The key of the parameter to check.
         * @return {boolean} `true` if the template has the given parameter
         */
        hasParameter(key) {
            return this.data.params[key] != null;
        }
        /**
         * Gets the value of a parameter.
         *
         * @param {string} key The key of the parameter to check.
         * @return {string} The parameter value.
         */
        getParameter(key) {
            var _a;
            return (_a = this.data.params[key]) === null || _a === void 0 ? void 0 : _a.wt;
        }
        /**
         * Sets the value for a specific parameter. If `value` is null or undefined,
         * the parameter is removed.
         *
         * @param {string} key The parameter key to set.
         * @param {string} value The new value of the parameter.
         */
        setParameter(key, value) {
            if (value != null) {
                this.data.params[key] = { wt: value };
                if (this.autosave) {
                    this.save();
                }
            }
            else {
                this.removeParameter(key);
            }
        }
        /**
         * Removes a parameter from the template.
         *
         * @param key The parameter key to remove.
         */
        removeParameter(key) {
            if (this.data.params[key] != null) {
                delete this.data.params[key];
            }
            if (this.autosave) {
                this.save();
            }
        }
        /**
         * Fix improperly-set parameters.
         */
        cleanup() {
            for (const key of Object.keys(this.data.params)) {
                const param = this.data.params[key];
                if (typeof param === 'string') {
                    this.data.params[key] = {
                        wt: param
                    };
                }
            }
        }
        /**
         * Removes this node from its element. This will prevent the node from being saved
         * again.
         *
         * @param eraseLine For block templates. Setting this to `true` will also erase a newline
         * that immediately succeeds this template, if one exists. This is useful in ensuring that
         * there are no excesses of newlines in the document.
         */
        destroy(eraseLine) {
            const existingData = JSON.parse(this.element.getAttribute('data-mw'));
            const partToRemove = existingData.parts.find((part) => { var _a; return ((_a = part.template) === null || _a === void 0 ? void 0 : _a.i) === this.i; });
            if (eraseLine) {
                const iFront = existingData.parts.indexOf(partToRemove) - 1;
                const iBack = existingData.parts.indexOf(partToRemove) + 1;
                let removed = false;
                if (iBack < existingData.parts.length && typeof existingData.parts[iBack] === 'string') {
                    // Attempt to remove whitespace from the string in front of the template.
                    if (/^\r?\n/.test(existingData.parts[iBack])) {
                        // Whitespace found, remove it.
                        existingData.parts[iBack] =
                            existingData.parts[iBack].replace(/^\r?\n/, '');
                        removed = true;
                    }
                }
                if (!removed && iFront > -1 && typeof existingData.parts[iFront] === 'string') {
                    // Attempt to remove whitespace from the string behind the template.
                    if (/\r?\n$/.test(existingData.parts[iFront])) {
                        // Whitespace found, remove it.
                        existingData.parts[iFront] =
                            existingData.parts[iFront].replace(/\r?\n$/, '');
                    }
                }
            }
            existingData.parts.splice(existingData.parts.indexOf(partToRemove), 1);
            this.element.setAttribute('data-mw', JSON.stringify(existingData));
        }
        /**
         * Saves this node (including modifications) back into its element.
         */
        save() {
            this.cleanup();
            const existingData = JSON.parse(this.element.getAttribute('data-mw'));
            existingData.parts.find((part) => { var _a; return ((_a = part.template) === null || _a === void 0 ? void 0 : _a.i) === this.i; }).template = this.data;
            this.element.setAttribute('data-mw', JSON.stringify(existingData));
        }
    }
    /**
     * A class containing an {@link HTMLIFrameElement} along with helper functions
     * to make manipulation easier.
     */
    class ParsoidDocument extends EventTarget {
        /**
         * Create a new ParsoidDocument instance.
         */
        constructor() {
            super();
            this.iframe = document.createElement('iframe');
            this.iframe.id = 'coordinatorFrame';
            Object.assign(this.iframe.style, {
                width: '0',
                height: '0',
                border: '0',
                position: 'fixed',
                top: '0',
                left: '0'
            });
            this.iframe.addEventListener('load', () => {
                if (this.iframe.contentWindow.document.URL === 'about:blank') {
                    // Blank document loaded. Ignore.
                    return;
                }
                /**
                 * The document of this ParsoidDocument's IFrame.
                 *
                 * @type {Document}
                 * @protected
                 */
                this.document = this.iframe.contentWindow.document;
                this.$document = $(this.document);
                this.setupJquery(this.$document);
                this.buildIndex();
                if (this.observer) {
                    // This very much assumes that the MutationObserver is still connected.
                    // Yes, this is quite an assumption, but should not be a problem during normal use.
                    // If only MutationObserver had a `.connected` field...
                    this.observer.disconnect();
                }
                this.observer = new MutationObserver(() => {
                    this.buildIndex();
                });
                this.observer.observe(this.document.getElementsByTagName('body')[0], {
                    // Listen for ALL DOM mutations.
                    attributes: true,
                    childList: true,
                    subtree: true
                });
            });
            document.getElementsByTagName('body')[0].appendChild(this.iframe);
        }
        /**
         * Create a new ParsoidDocument instance from a page on-wiki.
         *
         * @param {string} page The page to load.
         * @param {Object} options Options for frame loading.
         * @param {boolean} options.reload
         *   Whether the current page should be discarded and reloaded.
         * @param options.allowMissing
         *   Set to `false` to avoid loading a blank document if the page does not exist.
         */
        static async fromPage(page, options = {}) {
            const doc = new ParsoidDocument();
            await doc.loadPage(page, options);
            return doc;
        }
        /**
         * Create a new ParsoidDocument instance from plain HTML.
         *
         * @param {string} page The name of the page.
         * @param {string} html The HTML to use.
         * @param restBaseUri The relative URI to the RESTBase instance to be used for transforms.
         * @param {boolean} wrap Set to `false` to avoid wrapping the HTML within the body.
         */
        static async fromHTML(page, html, restBaseUri, wrap = true) {
            const doc = new ParsoidDocument();
            await doc.loadHTML(page, wrap ? ParsoidDocument.blankDocument : html, restBaseUri);
            if (wrap) {
                doc.document.getElementsByTagName('body')[0].innerHTML = html;
            }
            return doc;
        }
        /**
         * Creates a new ParsoidDocument from a blank page.
         *
         * @param {string} page The name of the page.
         * @param restBaseUri
         */
        static async fromBlank(page, restBaseUri) {
            const doc = new ParsoidDocument();
            await doc.loadHTML(page, ParsoidDocument.blankDocument, restBaseUri);
            return doc;
        }
        /**
         * Creates a new ParsoidDocument from wikitext.
         *
         * @param {string} page The page of the document.
         * @param {string} wikitext The wikitext to load.
         * @param restBaseUri
         */
        static async fromWikitext(page, wikitext, restBaseUri) {
            const doc = new ParsoidDocument();
            await doc.loadWikitext(page, wikitext, restBaseUri);
            return doc;
        }
        /**
         * Set up a JQuery object for this window.
         *
         * @param $doc The JQuery object to set up.
         * @return The JQuery object.
         */
        setupJquery($doc) {
            // noinspection JSPotentiallyInvalidConstructorUsage
            const $proto = $doc.constructor.prototype;
            /* eslint-disable-next-line @typescript-eslint/no-this-alias */
            const doc = this;
            $proto.parsoidNode = function () {
                if (this.length === 1) {
                    return doc.findParsoidNode(this[0]);
                }
                else {
                    return this.map((node) => doc.findParsoidNode(node));
                }
            };
            $proto.parsoid = function () {
                /**
                 * Processes an element and extracts its transclusion parts.
                 *
                 * @param {HTMLElement} element Element to process.
                 * @return The transclusion parts.
                 */
                function process(element) {
                    const rootNode = doc.findParsoidNode(element);
                    const mwData = JSON.parse(rootNode.getAttribute('data-mw'));
                    return mwData.parts.map((part) => {
                        if (part.template) {
                            return new ParsoidTransclusionTemplateNode(this, rootNode, part.template, part.template.i);
                        }
                        else {
                            return part;
                        }
                    });
                }
                if (this.length === 1) {
                    return process(this[0]);
                }
                else {
                    return this.map((element) => process(element));
                }
            };
            return $doc;
        }
        /**
         * Notify the user of a document loading error.
         *
         * @param {Error} error An error object.
         */
        notifyLoadError(error) {
            if (mw.notify) {
                mw.notify([
                    (() => {
                        const a = document.createElement('span');
                        a.innerText = 'An error occurred while loading a Parsoid document: ';
                        return a;
                    })(),
                    (() => {
                        const b = document.createElement('b');
                        b.innerText = error.message;
                        return b;
                    })()
                ], {
                    tag: 'parsoidDocument-error',
                    type: 'error'
                });
            }
            throw error;
        }
        /**
         * Loads a wiki page with this ParsoidDocument.
         *
         * @param {string} page The page to load.
         *
         * @param {Object} options Options for frame loading.
         * @param {boolean} options.reload
         *   Whether the current page should be discarded and reloaded.
         * @param options.allowMissing
         *   Set to `false` to avoid loading a blank document if the page does not exist.
         * @param options.restBaseUri
         *   A relative or absolute URI to the wiki's RESTBase root. This is
         */
        async loadPage(page, options = {}) {
            var _a;
            if (this.document && options.reload !== true) {
                throw new Error('Attempted to reload an existing frame.');
            }
            this.restBaseUri = (_a = options.restBaseUri) !== null && _a !== void 0 ? _a : restBaseRoot;
            return fetch(`${this.restBaseUri}v1/page/html/${encodeAPIComponent(page)}?stash=true&t=${Date.now()}`, {
                cache: 'no-cache'
            })
                .then((data) => {
                /**
                 * The ETag of this iframe's content.
                 *
                 * @type {string}
                 */
                this.etag = data.headers.get('ETag');
                if (data.status === 404 && options.allowMissing !== false) {
                    this.fromExisting = false;
                    // A Blob is used in order to allow cross-frame access without changing
                    // the origin of the frame.
                    return Promise.resolve(ParsoidDocument.defaultDocument);
                }
                else {
                    this.fromExisting = true;
                    return data.text();
                }
            })
                .then((html) => this.loadHTML(page, html, this.restBaseUri))
                .catch(this.notifyLoadError);
        }
        /**
         * Load a document from wikitext.
         *
         * @param {string} page The page title of this document.
         * @param {string} wikitext The wikitext to load.
         * @param restBaseUri
         */
        async loadWikitext(page, wikitext, restBaseUri) {
            this.restBaseUri = restBaseUri !== null && restBaseUri !== void 0 ? restBaseUri : restBaseRoot;
            return fetch(`${this.restBaseUri}v1/transform/wikitext/to/html/${encodeAPIComponent(page)}?t=${Date.now()}`, {
                cache: 'no-cache',
                method: 'POST',
                body: (() => {
                    const formData = new FormData();
                    formData.set('wikitext', wikitext);
                    formData.set('body_only', 'false');
                    return formData;
                })()
            })
                .then((data) => {
                /**
                 * The ETag of this iframe's content.
                 *
                 * @type {string}
                 */
                this.etag = data.headers.get('ETag');
                this.fromExisting = false;
                return data.text();
            })
                .then((html) => this.loadHTML(page, html, this.restBaseUri))
                .catch(this.notifyLoadError);
        }
        /**
         * Load a document from HTML.
         *
         * @param {string} page The loaded page's name.
         * @param {string} html The page's HTML.
         * @param restBaseUri A relative or absolute URI to the wiki's RESTBase root.
         */
        async loadHTML(page, html, restBaseUri) {
            this.restBaseUri = restBaseUri !== null && restBaseUri !== void 0 ? restBaseUri : restBaseRoot;
            // A Blob is used in order to allow cross-frame access without changing
            // the origin of the frame.
            this.iframe.src = URL.createObjectURL(new Blob([html], { type: 'text/html' }));
            this.page = page;
            return new Promise((res) => {
                this.iframe.addEventListener('load', () => {
                    res();
                });
            });
        }
        /**
         * Destroys the frame and pops it off of the DOM (if inserted).
         * Silently fails if the frame has not yet been built.
         */
        destroy() {
            if (this.iframe && this.iframe.parentElement) {
                this.iframe.parentElement.removeChild(this.iframe);
                this.iframe = undefined;
            }
        }
        /**
         * Reloads the page. This will destroy any modifications made to the document.
         */
        async reload() {
            const page = this.page;
            this.page = undefined;
            return this.loadPage(page, { reload: true });
        }
        /**
         * Clears the frame for a future reload. This will later permit `loadPage` and
         * other related functions to run without the `reload` option.
         */
        reset() {
            // Reset the page
            this.page = undefined;
            // Reset the element index
            this.elementIndex = undefined;
            // Reset DOM-related fields
            this.document = undefined;
            this.$document = undefined;
            this.etag = undefined;
            this.fromExisting = undefined;
            // Disconnect the mutation observer
            this.observer.disconnect();
            this.observer = undefined;
            // Reset the IFrame
            this.iframe.src = 'about:blank';
            // By this point, this whole thing should be a clean state.
        }
        /**
         * Constructs the {@link ParsoidDocument#elementIndex} from the current document.
         */
        buildIndex() {
            if (this.document == null) {
                throw new Error("Can't perform operations without a loaded page.");
            }
            this.elementIndex = {};
            const nodes = this.document.querySelectorAll('[typeof^=\'mw:\']');
            nodes.forEach((node) => {
                node.getAttribute('typeof')
                    .split(/\s+/g)
                    .map((type) => type.replace(/^mw:/, ''))
                    .forEach((type) => {
                    if (this.elementIndex[type] == null) {
                        this.elementIndex[type] = [];
                    }
                    this.elementIndex[type].push(node);
                });
            });
        }
        /**
         * Gets the `<section>` HTMLElement given a section ID.
         *
         * @param id The ID of the section
         * @return The HTMLElement of the section. If the section cannot be found, `null`.
         */
        getSection(id) {
            return this.document.querySelector(`section[data-mw-section-id="${id}"]`);
        }
        /**
         * Finds a template in the loaded document.
         *
         * @param {string|RegExp} templateName The name of the template to look for.
         * @param {boolean} hrefMode Use the href instead of the wikitext to search for templates.
         * @return {HTMLElement} A list of elements.
         */
        findTemplate(templateName, hrefMode = false) {
            var _a;
            if (this.document == null) {
                throw new Error("Can't perform operations without a loaded page.");
            }
            const templates = (_a = this.elementIndex) === null || _a === void 0 ? void 0 : _a.Transclusion;
            if (templates == null || templates.length === 0) {
                return [];
            }
            return templates.map((node) => {
                const mwData = JSON.parse(node.getAttribute('data-mw'));
                const matching = mwData.parts.filter((part) => {
                    if (part.template == null) {
                        return false;
                    }
                    const compareTarget = part.template.target[hrefMode ? 'href' : 'wt'];
                    if (typeof templateName !== 'string') {
                        return cloneRegex(templateName).test(compareTarget.trim());
                    }
                    else {
                        return templateName === compareTarget.trim();
                    }
                });
                if (matching.length > 0) {
                    return matching.map((part) => {
                        return new ParsoidTransclusionTemplateNode(this, node, part.template, part.template.i);
                    });
                }
                else {
                    return [];
                }
            }).reduce((a, b) => a.concat(b), []);
        }
        /**
         * Finds the element with the "data-mw" attribute containing the element
         * passed into the function.
         *
         * @param {HTMLElement} element
         *   The element to find the parent of. This must be a member of the
         *   ParsoidDocument's document.
         * @return {HTMLElement} The element responsible for showing the given element.
         */
        findParsoidNode(element) {
            let pivot = element;
            while (pivot.getAttribute('about') == null) {
                if (pivot.parentElement == null) {
                    // Dead end.
                    throw new Error('Reached root of DOM while looking for original Parsoid node.');
                }
                pivot = pivot.parentElement;
            }
            return this.document.querySelector(`[about="${pivot.getAttribute('about')}"][data-mw]`);
        }
        /**
         * Converts the contents of this document to wikitext.
         *
         * @return {Promise<string>} The wikitext of this document.
         */
        async toWikitext() {
            // this.restBaseUri should be set.
            let target = `${this.restBaseUri}v1/transform/html/to/wikitext/${encodeAPIComponent(this.page)}`;
            if (this.fromExisting) {
                target += `/${+(/(\d+)$/.exec(this.document.documentElement.getAttribute('about'))[1])}`;
            }
            return fetch(target, {
                method: 'POST',
                headers: {
                    'If-Match': this.fromExisting ? this.etag : undefined
                },
                body: (() => {
                    const data = new FormData();
                    data.set('html', this.document.documentElement.outerHTML);
                    data.set('scrub_wikitext', 'true');
                    data.set('stash', 'true');
                    return data;
                })()
            }).then((data) => data.text());
        }
        /**
         * Get the {@link Document} object of this ParsoidDocument.
         *
         * @return {Document} {@link ParsoidDocument#document}
         */
        getDocument() {
            return this.document;
        }
        /**
         * Get the JQuery object associated with this ParsoidDocument.
         *
         * @return {*} {@link ParsoidDocument#$document}
         */
        getJQuery() {
            return this.$document;
        }
        /**
         * Get the IFrame element of this ParsoidDocument.
         *
         * @return {HTMLIFrameElement} {@link ParsoidDocument#iframe}
         */
        getIframe() {
            return this.iframe;
        }
        /**
         * Get the page name of the currently-loaded page.
         *
         * @return {string} {@link ParsoidDocument#page}
         */
        getPage() {
            return this.page;
        }
        /**
         * Get the element index of this ParsoidDocument.
         *
         * @return {Object.<string, HTMLElement[]>} {@link ParsoidDocument#elementIndex}
         */
        getElementIndex() {
            return this.elementIndex;
        }
        /**
         * Check if this element exists on-wiki or not.
         *
         * @return {boolean} {@link ParsoidDocument#fromExisting}
         */
        isFromExisting() {
            return this.fromExisting;
        }
    }
    ParsoidDocument.Node = ParsoidTransclusionTemplateNode;
    /**
     * A blank Parsoid document, with a section 0.
     *
     * @type {string}
     */
    ParsoidDocument.blankDocument = '<html><body><section data-mw-section-id="0"></section></body></html>';
    /**
     * The default document to create if a page was not found.
     *
     * @type {string}
     */
    ParsoidDocument.defaultDocument = ParsoidDocument.blankDocument;
    // ParsoidDocument:end
    var _default = ParsoidDocument_module.default = ParsoidDocument;

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

    /**
     * An event dispatched when a template inside a `CopiedTemplateEditorDialog` is inserted.
     */
    class TemplateInsertEvent extends Event {
        /**
         * @param template The template that was inserted
         * @param eventInitDict
         */
        constructor(template, eventInitDict) {
            super('templateInsert', eventInitDict);
            this.template = template;
        }
    }

    /**
     * Extension class of ParsoidDocument's node. Used to type `parsoidDocument` in the
     * below function. Since the original node is always instantiated with `this`, it
     * can be assumed that `parsoidDocument` is a valid CTEParsoidDocument.
     */
    class CTEParsoidTransclusionTemplateNode extends _default.Node {
        /**
         * @inheritDoc
         */
        static fromNew(document, template, parameters, autosave) {
            return this.upgradeNode(super.fromNew(document, template, parameters, autosave), document);
        }
        /**
         * Upgrades a vanilla ParsoidDocument.Node to a CTEParsoidTransclusionTemplateNode.
         *
         * @param node The node to upgrade
         * @param document The document to attach
         * @return A CTEParsoidTransclusionTemplateNode
         */
        static upgradeNode(node, document) {
            return new CTEParsoidTransclusionTemplateNode(document, node.element, node.data, node.i, node.autosave);
        }
    }

    /**
     * Creates blank attribution notices. Its own class to avoid circular dependencies.
     */
    class TemplateFactory {
        /**
         * Get the template wikitext (`target.wt`) of a given notice type.
         *
         * @param type
         * @return The wikitext of the template's target page
         */
        static getTemplateWikitext(type) {
            return WikiAttributionNotices.attributionNoticeTemplates[type].getNamespaceId() ===
                nsId('template') ?
                // If in the "Template" namespace, "Copied"
                WikiAttributionNotices.attributionNoticeTemplates[type].getNameText() :
                // If not in the "Template" namespace, "Namespace:Copied"
                WikiAttributionNotices.attributionNoticeTemplates[type].getPrefixedText();
        }
        /**
         * Creates a new {@link CopiedTemplate}
         *
         * @param document
         * @return A new CopiedTemplate
         */
        static copied(document) {
            const templateWikitext = TemplateFactory.getTemplateWikitext('copied');
            const node = CTEParsoidTransclusionTemplateNode.fromNew(document, templateWikitext, {
                // Pre-fill with target page
                to: new mw.Title(document.getPage()).getSubjectPage().getPrefixedText()
            });
            node.element.setAttribute('about', `N${TemplateFactory.noticeCount++}`);
            node.element.classList.add('copiednotice');
            return new CopiedTemplate(node);
        }
        /**
         * Creates a new {@link SplitArticleTemplate}
         *
         * @param document
         * @return A new SplitArticleTemplate
         */
        static splitArticle(document) {
            const templateWikitext = TemplateFactory.getTemplateWikitext('splitArticle');
            const node = CTEParsoidTransclusionTemplateNode.fromNew(document, templateWikitext, {
                from: new mw.Title(document.getPage()).getSubjectPage().getPrefixedText(),
                // Blank string to trigger row creation
                to: ''
            });
            node.element.setAttribute('about', `N${TemplateFactory.noticeCount++}`);
            node.element.classList.add('box-split-article');
            return new SplitArticleTemplate(node);
        }
        /**
         * Creates a new {@link MergedFromTemplate}
         *
         * @param document
         * @return A new MergedFromTemplate
         */
        static mergedFrom(document) {
            const templateWikitext = TemplateFactory.getTemplateWikitext('mergedFrom');
            const node = CTEParsoidTransclusionTemplateNode.fromNew(document, templateWikitext, {});
            node.element.setAttribute('about', `N${TemplateFactory.noticeCount++}`);
            node.element.classList.add('box-merged-from');
            return new MergedFromTemplate(node);
        }
        /**
         * Creates a new {@link MergedToTemplate}
         *
         * @param document
         * @return A new MergedToTemplate
         */
        static mergedTo(document) {
            const templateWikitext = TemplateFactory.getTemplateWikitext('mergedTo');
            const node = CTEParsoidTransclusionTemplateNode.fromNew(document, templateWikitext, {});
            node.element.setAttribute('about', `N${TemplateFactory.noticeCount++}`);
            node.element.classList.add('box-merged-to');
            return new MergedToTemplate(node);
        }
        /**
         * Creates a new {@link BackwardsCopyTemplate}
         *
         * @param document
         * @return A new MergedToTemplate
         */
        static backwardsCopy(document) {
            const templateWikitext = TemplateFactory.getTemplateWikitext('backwardsCopy');
            const node = CTEParsoidTransclusionTemplateNode.fromNew(document, templateWikitext, {
                // Blank string to trigger row creation
                title: ''
            });
            node.element.setAttribute('about', `N${TemplateFactory.noticeCount++}`);
            node.element.classList.add('box-merged-to');
            return new BackwardsCopyTemplate(node);
        }
        /**
         * Creates a new {@link TranslatedPageTemplate}
         *
         * @param document
         * @return A new MergedToTemplate
         */
        static translatedPage(document) {
            const templateWikitext = TemplateFactory.getTemplateWikitext('translatedPage');
            const node = CTEParsoidTransclusionTemplateNode.fromNew(document, templateWikitext);
            node.element.setAttribute('about', `N${TemplateFactory.noticeCount++}`);
            node.element.classList.add('box-translated-page');
            return new TranslatedPageTemplate(node);
        }
    }
    /**
     * Simply increments when notices are added. This gives specific notices a
     * human-friendly identifier.
     */
    TemplateFactory.noticeCount = 1;

    /**
     * Moves a value as determined by an index of the array to the start of the array.
     * Mutates the original array.
     *
     * @param array The array to use
     * @param index The index of the value to move to the start of the array
     * @return The reordered array.
     */
    function moveToStart(array, index) {
        const el = array[index];
        array.splice(index, 1);
        array.splice(0, 0, el);
        return array;
    }

    /**
     * An object containing an {@link HTMLIFrameElement} along with helper functions
     * to make manipulation easier.
     */
    class CTEParsoidDocument extends _default {
        /**
         * Creates a new CTE-specific ParsoidDocument. This extends from the existing
         * ParsoidDocument with functions specifically catered for pages that have
         * {{copied}} (or will have) templates.
         */
        constructor() {
            super();
            /**
             * A map of all Parsoid HTML elements and their attribution notices. When notices are
             * detected, they are added here. ParsoidTemplateTransclusionNode is not used here
             * since they are regenerated every time `findTemplate` is called.
             */
            this.notices = new Map();
            /**
             * The original number of {{copied}} notices in the document.
             */
            this.originalCount = null;
            // Event listeners should be fired synchronously. Load listener found in
            // `super` should have been run by this point.
            this.iframe.addEventListener('load', () => {
                if (this.iframe.contentWindow.document.URL === 'about:blank') {
                    // Blank document loaded. Ignore.
                    return;
                }
                const notices = this.findNoticeType('copied');
                this.originalCount = notices.length;
            });
        }
        /**
         * @inheritDoc
         */
        reset() {
            super.reset();
            this.originalCount = undefined;
            this.notices.clear();
        }
        /**
         * Finds all content attribution notices in the talk page. This includes {{copied}},
         * {{merged to}}, {{merged from}}, etc.
         *
         * @return An array of AttributionNotice objects.
         */
        findNotices() {
            this.buildIndex();
            // Used instead of `this.notices.values()` to exclude nodes that are no longer on the DOM.
            const notices = [];
            for (const node of this.findTemplate(WikiAttributionNotices.templateAliasRegExp, true)) {
                if (!this.notices.has(node.element)) {
                    // Notice not yet cached, but this is an attribution notice.
                    // Now to determine what type.
                    const type = WikiAttributionNotices.getTemplateNoticeType(node.getTarget().href);
                    const noticeInstance = new (WikiAttributionNotices.attributionNoticeClasses[type])(CTEParsoidTransclusionTemplateNode.upgradeNode(node, this));
                    this.notices.set(node.element, noticeInstance);
                }
                notices.push(this.notices.get(node.element));
            }
            return notices;
        }
        /**
         * Finds this document's {{copied}} notices.
         *
         * @param type
         * @return An array of all CopiedTemplate objects found
         */
        findNoticeType(type) {
            return this.findNotices().filter((notice) => notice instanceof
                WikiAttributionNotices.attributionNoticeClasses[type]);
        }
        /**
         * Look for a good spot to place a {{copied}} template.
         *
         * @param type The type of the notice to look a spot for.
         * @return A spot to place the template, `null` if a spot could not be found.
         */
        findNoticeSpot(type) {
            var _a;
            // TODO: Just use a simple "if" for {{translated page}}.
            const positionIndices = {
                copied: 0,
                splitArticle: 1,
                mergedFrom: 2,
                mergedTo: 3,
                backwardsCopy: 4,
                translatedPage: 5
            };
            const positionIndex = positionIndices[type];
            const variableSpots = [
                [
                    positionIndex >= positionIndices.copied ? 'afterend' : 'beforebegin',
                    positionIndex >= positionIndices.copied ?
                        last(this.document.querySelectorAll('.copiednotice')) :
                        this.document.querySelector('.copiednotice')
                ],
                [
                    positionIndex >= positionIndices.splitArticle ? 'afterend' : 'beforebegin',
                    positionIndex >= positionIndices.splitArticle ?
                        last(this.document.querySelectorAll('.box-split-article')) :
                        this.document.querySelector('.box-split-article')
                ],
                [
                    positionIndex >= positionIndices.mergedFrom ? 'afterend' : 'beforebegin',
                    positionIndex >= positionIndices.mergedFrom ?
                        last(this.document.querySelectorAll('.box-merged-from')) :
                        this.document.querySelector('.box-merged-from')
                ],
                [
                    positionIndex >= positionIndices.mergedTo ? 'afterend' : 'beforebegin',
                    positionIndex >= positionIndices.mergedTo ?
                        last(this.document.querySelectorAll('.box-merged-to')) :
                        this.document.querySelector('.box-merged-to')
                ],
                [
                    positionIndex >= positionIndices.backwardsCopy ? 'afterend' : 'beforebegin',
                    positionIndex >= positionIndices.backwardsCopy ?
                        last(this.document.querySelectorAll('.box-backwards-copy')) :
                        this.document.querySelector('.box-backwards-copy')
                ],
                [
                    positionIndex >= positionIndices.translatedPage ? 'afterend' : 'beforebegin',
                    positionIndex >= positionIndices.translatedPage ?
                        last(this.document.querySelectorAll('.box-translated-page')) :
                        this.document.querySelector('.box-translated-page')
                ]
            ];
            moveToStart(variableSpots, positionIndex);
            const possibleSpots = [
                ...variableSpots,
                // After the {{to do}} template
                ['afterend', last(this.document.querySelectorAll('.t-todo'))],
                // After the WikiProject banner shell
                ['afterend', this.document.querySelector('.wpbs') ? last(this.document.querySelectorAll(`[about="${this.document.querySelector('.wpbs')
                    .getAttribute('about')}"]`)) : null],
                // After all WikiProject banners
                ['afterend', last(this.document.querySelectorAll('.wpb'))],
                // After the last talk page message box that is not a small box
                ['afterend', last(this.document.querySelectorAll('[data-mw-section-id="0"] .tmbox:not(.mbox-small):not(.talkheader)'))],
                // After the talk page header
                ['afterend', this.document.querySelector('.talkheader')],
                // At the start of the talk page
                ['afterbegin', this.document.querySelector('section[data-mw-section-id="0"]')]
            ];
            for (const spot of possibleSpots) {
                if (spot[1] != null) {
                    if (spot[1].hasAttribute('data-mw')) {
                        return spot;
                    }
                    else {
                        const identifier = ((_a = spot[1].getAttribute('about')) !== null && _a !== void 0 ? _a : spot[1].getAttribute('id')).replace(/^#/, '');
                        // Find the last element from that specific transclusion.
                        const transclusionRoot = last(this.document.querySelectorAll(`#${identifier}, [about="#${identifier}"]`));
                        return [
                            spot[0],
                            transclusionRoot
                        ];
                    }
                }
            }
            return null;
        }
        /**
         * Inserts a new attribution notice of a given type.
         *
         * @param type A notice type
         * @param spot The spot to place the template.
         * @param spot."0" See {@link CTEParsoidDocument.findNoticeSpot()}[0]
         * @param spot."1" See {@link CTEParsoidDocument.findNoticeSpot()}[1]
         */
        insertNewNotice(type, [position, element]) {
            const template = {
                copied: TemplateFactory.copied,
                splitArticle: TemplateFactory.splitArticle,
                mergedFrom: TemplateFactory.mergedFrom,
                mergedTo: TemplateFactory.mergedTo,
                backwardsCopy: TemplateFactory.backwardsCopy,
                translatedPage: TemplateFactory.translatedPage
            }[type](this);
            // Insert.
            element.insertAdjacentElement(position, template.element);
            this.notices.set(template.element, template);
            this.dispatchEvent(new TemplateInsertEvent(template));
        }
    }
    CTEParsoidDocument.addedRows = 1;
    /**
     * Extremely minimalist valid Parsoid document. This includes a section 0
     * element for findCopiedNoticeSpot.
     *
     * @type {string}
     */
    CTEParsoidDocument.defaultDocument = '<html><body><section data-mw-section-id="0"></section></body></html>';

    /**
     * Converts a normal error into an OO.ui.Error for ProcessDialogs.
     *
     * @param error A plain error object.
     * @param config Error configuration.
     * @param config.recoverable Whether or not the error is recoverable.
     * @param config.warning Whether or not the error is a warning.
     * @return An OOUI Error.
     */
    function errorToOO(error, config) {
        return new OO.ui.Error(error.message, config);
    }

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

    /**
     * Deputy's current version, exported as a string.
     *
     * Why is this in its own file? Multiple modules can be run standalone (without
     * Deputy), but they are still part of Deputy and hence use the same
     * `decorateEditSummary` function. However, Deputy (core) may not be available
     * at the moment, leading to a reference error if `window.deputy.version` were
     * to be used.
     *
     * This ensures that the version is available, even if the core is not loaded.
     * It also keeps standalone versions lightweight to avoid too much additional code.
     *
     * This file is automatically modified by npm when running `npm version ...`. Avoid
     * modifying it manually.
     */
    var deputyVersion = /* v */ '0.0.4' /* v */;

    /**
     * Appends extra information to an edit summary (also known as the "advert").
     *
     * @param editSummary The edit summary
     * @return The decorated edit summary (in wikitext)
     */
    function decorateEditSummary (editSummary) {
        return `${editSummary} ([[User:Chlod/Scripts/Deputy|Deputy]] v${deputyVersion})`;
    }

    let InternalCopiedTemplateEditorDialog;
    /**
     * Initializes the process element.
     */
    function initCopiedTemplateEditorDialog() {
        var _a;
        InternalCopiedTemplateEditorDialog = (_a = class CopiedTemplateEditorDialog extends OO.ui.ProcessDialog {
                /**
                 * @param config
                 */
                constructor(config) {
                    super(config);
                    /**
                     * Parsoid document for this dialog.
                     */
                    this.parsoid = new CTEParsoidDocument();
                    /**
                     * A map of OOUI PageLayouts keyed by their notices. These PageLayouts also include
                     * functions specific to AttributionNoticePageLayout, such as functions to get child
                     * pages.
                     */
                    this.pageCache = new Map();
                    this.main = config.main;
                }
                /**
                 * @return The body height of this dialog.
                 */
                getBodyHeight() {
                    return 900;
                }
                /**
                 * Initializes the dialog.
                 */
                initialize() {
                    super.initialize();
                    this.layout = new OO.ui.BookletLayout({
                        continuous: true,
                        outlined: true
                    });
                    this.layout.on('remove', () => {
                        if (Object.keys(this.layout.pages).length === 0) {
                            // If no pages left, append the "no notices" page.
                            this.layout.addPages([CopiedTemplatesEmptyPage({
                                    parent: this,
                                    parsoid: this.parsoid
                                })], 0);
                        }
                    });
                    this.parsoid.addEventListener('templateInsert', (event) => {
                        if (!this.pageCache.has(event.template)) {
                            this.pageCache.set(event.template, event.template.generatePage(this));
                            this.rebuildPages();
                        }
                    });
                    this.renderMenuActions();
                    this.$body.append(this.layout.$element);
                }
                /**
                 * Rebuilds the pages of this dialog.
                 */
                rebuildPages() {
                    const notices = this.parsoid.findNotices();
                    const pages = [];
                    for (const notice of notices) {
                        let cachedPage = this.pageCache.get(notice);
                        if (cachedPage == null) {
                            cachedPage = notice.generatePage(this);
                            this.pageCache.set(notice, cachedPage);
                        }
                        pages.push(cachedPage);
                        if (cachedPage.getChildren != null) {
                            pages.push(...cachedPage.getChildren());
                        }
                    }
                    const lastFocusedPage = this.layout.getCurrentPage();
                    let nextFocusedPageName;
                    const layoutPages = getObjectValues(this.layout.pages);
                    const removed = layoutPages
                        .filter((item) => pages.indexOf(item) === -1);
                    if (removed.indexOf(lastFocusedPage) === -1) {
                        // Focus on an existing (and currently focused) page.
                        nextFocusedPageName = this.layout.getCurrentPageName();
                    }
                    else if (lastFocusedPage != null) {
                        const layoutNames = Object.keys(this.layout.pages);
                        // Find the next page AFTER the one previously focused on (which is
                        // about to get removed).
                        for (let i = layoutNames.indexOf(lastFocusedPage.getName()); i < layoutNames.length; i++) {
                            const layoutName = layoutNames[i];
                            if (removed.some((p) => p.getName() !== layoutName)) {
                                // This element will not get removed later on. Use it.
                                nextFocusedPageName = layoutName;
                                break;
                            }
                        }
                        if (nextFocusedPageName == null) {
                            // Fall back to last element in the set (most likely position)
                            nextFocusedPageName = last(pages).getName();
                        }
                    }
                    // Remove all removed pages
                    this.layout.removePages(removed);
                    // Jank, but no other options while page rearranging isn't a thing.
                    this.layout.addPages(pages);
                    if (nextFocusedPageName) {
                        this.layout.setPage(nextFocusedPageName);
                    }
                    // Delete deleted pages from cache.
                    this.pageCache.forEach((page, notice) => {
                        if (removed.indexOf(page) !== -1) {
                            this.pageCache.delete(notice);
                        }
                    });
                }
                /**
                 * Renders the collection of actions at the top of the page menu. Also
                 * appends the panel to the layout.
                 */
                renderMenuActions() {
                    const addButton = new OO.ui.ButtonWidget({
                        icon: 'add',
                        framed: false,
                        invisibleLabel: true,
                        label: mw.message('deputy.cte.add').text(),
                        title: mw.message('deputy.cte.add').text(),
                        flags: ['progressive']
                    });
                    this.mergeButton = new OO.ui.ButtonWidget({
                        icon: 'tableMergeCells',
                        framed: false,
                        invisibleLabel: true,
                        label: mw.message('deputy.cte.mergeAll').text(),
                        title: mw.message('deputy.cte.mergeAll').text(),
                        disabled: true
                    });
                    // TODO: Repair mergeButton
                    this.mergeButton.on('click', () => {
                        const notices = this.parsoid.findNoticeType('copied');
                        if (notices.length > 1) {
                            return OO.ui.confirm(mw.message('deputy.cte.mergeAll.confirm', `${notices.length}`).text()).done((confirmed) => {
                                if (!confirmed) {
                                    return;
                                }
                                TemplateMerger.copied(notices);
                            });
                        }
                        else {
                            return OO.ui.alert('There are no templates to merge.');
                        }
                    });
                    const resetButton = new OO.ui.ButtonWidget({
                        icon: 'reload',
                        framed: false,
                        invisibleLabel: true,
                        label: mw.message('deputy.cte.reset').text(),
                        title: mw.message('deputy.cte.reset').text()
                    });
                    resetButton.on('click', () => {
                        return OO.ui.confirm(mw.message('deputy.cte.reset.confirm').text()).done((confirmed) => {
                            if (confirmed) {
                                this.parsoid.reload().then(() => {
                                    this.layout.clearPages();
                                    this.rebuildPages();
                                });
                            }
                        });
                    });
                    const deleteButton = new OO.ui.ButtonWidget({
                        icon: 'trash',
                        framed: false,
                        invisibleLabel: true,
                        label: mw.message('deputy.cte.delete').text(),
                        title: mw.message('deputy.cte.delete').text(),
                        flags: ['destructive']
                    });
                    deleteButton.on('click', () => {
                        // Original copied notice count.
                        const notices = this.parsoid.findNotices();
                        return OO.ui.confirm(mw.message('deputy.cte.delete.confirm', `${notices.length}`).text()).done((confirmed) => {
                            if (confirmed) {
                                for (const notice of notices) {
                                    notice.destroy();
                                }
                            }
                        });
                    });
                    this.layout.on('remove', () => {
                        const notices = this.parsoid.findNotices();
                        // TODO: Repair mergeButton
                        // this.mergeButton.setDisabled( notices.length < 2 );
                        deleteButton.setDisabled(notices.length === 0);
                    });
                    this.parsoid.addEventListener('templateInsert', () => {
                        const notices = this.parsoid.findNotices();
                        // TODO: Repair mergeButton
                        // this.mergeButton.setDisabled( notices.length < 2 );
                        deleteButton.setDisabled(notices.length === 0);
                    });
                    this.$overlay.append(new AttributionNoticeAddMenu(this.parsoid, addButton).render());
                    const actionPanel = h_1("div", { class: "cte-actionPanel" },
                        unwrapWidget(addButton),
                        unwrapWidget(this.mergeButton),
                        unwrapWidget(resetButton),
                        unwrapWidget(deleteButton));
                    const targetPanel = unwrapWidget(this.layout).querySelector('.oo-ui-menuLayout .oo-ui-menuLayout-menu');
                    targetPanel.insertAdjacentElement('afterbegin', actionPanel);
                }
                /**
                 * Gets the setup process for this dialog. This is run prior to the dialog
                 * opening.
                 *
                 * @param data Additional data. Unused for this dialog.
                 * @return An OOUI Process
                 */
                getSetupProcess(data) {
                    const process = super.getSetupProcess(data);
                    if (this.parsoid.getDocument() == null) {
                        // Load the talk page
                        process.next(this.parsoid.loadPage(new mw.Title(mw.config.get('wgPageName'))
                            .getTalkPage()
                            .getPrefixedText()).catch(errorToOO).then(() => true));
                    }
                    // Rebuild the list of pages
                    process.next(() => {
                        this.rebuildPages();
                        return true;
                    });
                    // Block exits
                    process.next(() => {
                        blockExit('cte');
                        return true;
                    });
                    return process;
                }
                /**
                 * Gets this dialog's ready process. Called after the dialog has opened.
                 *
                 * @return An OOUI Process
                 */
                getReadyProcess() {
                    var _a;
                    const process = super.getReadyProcess();
                    // Recheck state of merge button
                    this.mergeButton.setDisabled(((_a = this.parsoid.findNoticeType('copied').length) !== null && _a !== void 0 ? _a : 0) < 2);
                    process.next(() => {
                        for (const page of getObjectValues(this.layout.pages)) {
                            // Dirty check to see if this is a CopiedTemplatePage.
                            if (page.updatePreview != null) {
                                page.updatePreview();
                            }
                        }
                    }, this);
                    return process;
                }
                /**
                 * Gets this dialog's action process. Handles all actions (primarily dialog
                 * button clicks, etc.)
                 *
                 * @param action
                 * @return An OOUI Process
                 */
                getActionProcess(action) {
                    const process = super.getActionProcess(action);
                    if (action === 'save') {
                        // Quick and dirty validity check.
                        if (unwrapWidget(this.layout)
                            .querySelector('.oo-ui-flaggedElement-invalid') != null) {
                            return new OO.ui.Process(() => {
                                OO.ui.alert(mw.message('deputy.cte.invalid').text());
                            });
                        }
                        // Saves the page.
                        process.next(() => __awaiter(this, void 0, void 0, function* () {
                            return new mw.Api().postWithEditToken({
                                action: 'edit',
                                format: 'json',
                                formatversion: '2',
                                utf8: 'true',
                                title: this.parsoid.getPage(),
                                text: yield this.parsoid.toWikitext(),
                                // TODO: l10n
                                summary: decorateEditSummary(`${this.parsoid.originalCount > 0 ?
                                'Modifying' : 'Adding'} content attribution notices`)
                            }).catch(errorToOO);
                        }), this);
                        // Page redirect
                        process.next(() => {
                            unblockExit('cte');
                            if (mw.config.get('wgPageName') === this.parsoid.getPage()) {
                                // If on the talk page, reload the page.
                                window.location.reload();
                            }
                            else {
                                // If on another page, open the talk page.
                                window.location.href =
                                    mw.config.get('wgArticlePath').replace(/\$1/g, encodeURIComponent(this.parsoid.getPage()));
                            }
                        }, this);
                    }
                    process.next(() => {
                        this.close({ action: action });
                    }, this);
                    return process;
                }
                /**
                 * Gets the teardown process. Called when the dialog is closing.
                 *
                 * @return An OOUI process.
                 */
                getTeardownProcess() {
                    const process = super.getTeardownProcess();
                    process.next(() => {
                        // Already unblocked if "save", but this cuts down on code footprint.
                        unblockExit('cte');
                        this.main.toggleButtons(true);
                    });
                    return process;
                }
            },
            _a.static = {
                name: 'copiedTemplateEditorDialog',
                title: mw.message('deputy.cte').text(),
                size: 'huge',
                actions: [
                    {
                        flags: ['primary', 'progressive'],
                        label: mw.message('deputy.cte.save').text(),
                        title: mw.message('deputy.cte.save').text(),
                        action: 'save'
                    },
                    {
                        flags: ['safe', 'close'],
                        icon: 'close',
                        label: mw.message('deputy.cte.close').text(),
                        title: mw.message('deputy.cte.close').text(),
                        invisibleLabel: true,
                        action: 'close'
                    }
                ]
            },
            _a);
    }
    /**
     * Creates a new CopiedTemplateEditorDialog.
     *
     * @param config
     * @return A CopiedTemplateEditorDialog object
     */
    function CopiedTemplateEditorDialog (config) {
        if (!InternalCopiedTemplateEditorDialog) {
            initCopiedTemplateEditorDialog();
        }
        return new InternalCopiedTemplateEditorDialog(config);
    }

    var cteStyles = ".copied-template-editor .oo-ui-window-frame {width: 1000px !important;}.copied-template-editor .oo-ui-menuLayout > .oo-ui-menuLayout-menu {height: 20em;width: 20em;}.copied-template-editor .oo-ui-menuLayout > .oo-ui-menuLayout-content {left: 20em;}.cte-preview .copiednotice {margin-left: 0;margin-right: 0;}.cte-merge-panel {padding: 16px;z-index: 20;border: 1px solid lightgray;margin-bottom: 8px;}.copied-template-editor .oo-ui-bookletLayout-outlinePanel {bottom: 32px;}.cte-actionPanel {height: 32px;width: 100%;position: absolute;bottom: 0;z-index: 1;background-color: white;border-top: 1px solid #c8ccd1;}.cte-actionPanel > .oo-ui-buttonElement {display: inline-block;margin: 0 !important;}.cte-templateOptions {margin: 8px;display: flex;}.cte-templateOptions > * {flex: 1;}.cte-fieldset {border: 1px solid gray;background-color: #ddf7ff;padding: 16px;min-width: 200px;clear: both;}.cte-fieldset-date {float: left;margin-top: 10px !important;}.cte-fieldset-advswitch {float: right;}.cte-fieldset-advswitch .oo-ui-fieldLayout-field,.cte-fieldset-date .oo-ui-fieldLayout-field {display: inline-block !important;}.cte-fieldset-advswitch .oo-ui-fieldLayout-header {display: inline-block !important;margin-right: 16px;}.copied-template-editor .mw-widgets-datetime-dateTimeInputWidget-handle .oo-ui-iconElement-icon {left: 0.5em;width: 1em;height: 1em;top: 0.4em;}.cte-fieldset-date .mw-widgets-datetime-dateTimeInputWidget-editField {min-width: 2.5ch !important;}.cte-fieldset-date :not(.mw-widgets-datetime-dateTimeInputWidget-empty) > .mw-widgets-datetime-dateTimeInputWidget-handle {padding-right: 0;}.cte-page-row:not(:last-child),.cte-page-template:not(:last-child),.cte-fieldset-date.oo-ui-actionFieldLayout.oo-ui-fieldLayout-align-top .oo-ui-fieldLayout-header {padding-bottom: 0 !important;}.cte-page-template + .cte-page-row {padding-top: 0 !important;}.copied-template-editor .oo-ui-fieldsetLayout.oo-ui-iconElement > .oo-ui-fieldsetLayout-header {position: relative;}.oo-ui-actionFieldLayout.oo-ui-fieldLayout-align-top .oo-ui-fieldLayout-header {padding-bottom: 6px !important;}.oo-ui-windowManager-modal > .oo-ui-window.oo-ui-dialog.oo-ui-messageDialog {z-index: 200;}";

    var deputyCteEnglish = {
    	"deputy.cte": "Attribution Notice Template Editor",
    	"deputy.cte.edit": "Modify content attribution notices for this page",
    	"deputy.cte.close": "Close",
    	"deputy.cte.save": "Save",
    	"deputy.cte.add": "Add a notice",
    	"deputy.cte.mergeAll": "Merge all notices",
    	"deputy.cte.mergeAll.confirm": "You are about to merge $1 {{PLURAL:$1|notice|notices}}. Continue?",
    	"deputy.cte.reset": "Reset all changes",
    	"deputy.cte.reset.confirm": "This will reset all changes. Proceed?",
    	"deputy.cte.delete": "Delete all notices",
    	"deputy.cte.delete.confirm": "You are about to delete $1 {{PLURAL:$1|notice|notices}}. Continue?",
    	"deputy.cte.nocat.head": "<code>nocat</code> is enabled",
    	"deputy.cte.nocat.help": "This notice has the <code>nocat</code> parameter enabled and, as an effect, is not being tracked in categories. This usually means that the template is for demonstration purposes only.",
    	"deputy.cte.nocat.clear": "Restore tracking",
    	"deputy.cte.demo.head": "<code>demo</code> is enabled",
    	"deputy.cte.demo.help": "This notice has the <code>demo</code> parameter enabled. This usually means that the template is for demonstration purposes only.",
    	"deputy.cte.demo.clear": "Clear demo mode",
    	"deputy.cte.invalid": "Some fields are still invalid.",
    	"deputy.cte.adding": "Adding content attribution notices",
    	"deputy.cte.modifying": "Modifying content attribution notices",
    	"deputy.cte.dirty": "This dialog did not close properly last time. Your changes will be reset.",
    	"deputy.cte.empty.header": "No notices",
    	"deputy.cte.empty.removed": "All notices will be removed from the page. To reset your changes and restore previous templates, press the reset button at the bottom of the dialog.",
    	"deputy.cte.empty.none": "There are currently no notices on the talk page.",
    	"deputy.cte.empty.add": "Add a notice",
    	"deputy.cte.noSpot": "Sorry, but a notice cannot be automatically added. Please contact the developer to possibly add support for this talk page.",
    	"deputy.cte.merge": "Merge",
    	"deputy.cte.merge.title": "Merge notices",
    	"deputy.cte.merge.from.label": "Notices to merge",
    	"deputy.cte.merge.from.select": "Select a notice",
    	"deputy.cte.merge.from.empty": "No notices to merge",
    	"deputy.cte.merge.all": "Merge all",
    	"deputy.cte.merge.all.confirm": "You are about to merge $1 'copied' {{PLURAL:$1|notice|notices}} into this notice. Continue?",
    	"deputy.cte.merge.button": "Merge",
    	"deputy.cte.templateOptions": "Template options",
    	"deputy.cte.copied.label": "Copied $1",
    	"deputy.cte.copied.remove": "Remove notice",
    	"deputy.cte.copied.remove.confirm": "This will destroy $1 {{PLURAL:$1|entry|entries}}. Continue?",
    	"deputy.cte.copied.add": "Add entry",
    	"deputy.cte.copied.entry.label": "Template entry",
    	"deputy.cte.copied.entry.short": "$1 to $2",
    	"deputy.cte.copied.entry.shortTo": "To $1",
    	"deputy.cte.copied.entry.shortFrom": "From $1",
    	"deputy.cte.copied.entry.remove": "Remove entry",
    	"deputy.cte.copied.entry.copy": "Copy attribution edit summary",
    	"deputy.cte.copied.entry.copy.lacking": "Attribution edit summary copied to clipboard with lacking properties. Ensure that `from` is supplied.",
    	"deputy.cte.copied.entry.copy.success": "Attribution edit summary copied to clipboard.",
    	"deputy.cte.copied.collapse": "Collapse",
    	"deputy.cte.copied.small": "Small",
    	"deputy.cte.copied.from.placeholder": "Page A",
    	"deputy.cte.copied.from.label": "Page copied from",
    	"deputy.cte.copied.from.help": "This is the page from which the content was copied from.",
    	"deputy.cte.copied.from_oldid.placeholder": "from_oldid",
    	"deputy.cte.copied.from_oldid.label": "Revision ID",
    	"deputy.cte.copied.from_oldid.help": "The specific revision ID at the time that the content was copied, if known.",
    	"deputy.cte.copied.to.placeholder": "Page B",
    	"deputy.cte.copied.to.label": "Page copied to",
    	"deputy.cte.copied.to.help": "This is the page where the content was copied into.",
    	"deputy.cte.copied.to_diff.placeholder": "to_diff",
    	"deputy.cte.copied.to_diff.label": "Revision ID",
    	"deputy.cte.copied.to_diff.help": "The specific revision ID of the revision that copied content into the target page. If the copying spans multiple revisions, this is the ID of the last revision that copies content into the page.",
    	"deputy.cte.copied.to_oldid.placeholder": "to_oldid",
    	"deputy.cte.copied.to_oldid.label": "Starting revision ID",
    	"deputy.cte.copied.to_oldid.help": "The ID of the revision before any content was copied. This can be omitted unless multiple revisions copied content into the page.",
    	"deputy.cte.copied.diff.placeholder": "https://en.wikipedia.org/w/index.php?diff=123456",
    	"deputy.cte.copied.diff.label": "Diff URL",
    	"deputy.cte.copied.diff.help": "The URL of the diff. Using <code>to_diff</code> and <code>to_oldid</code> is preferred, although supplying this parameter will override both.",
    	"deputy.cte.copied.merge.label": "Merged?",
    	"deputy.cte.copied.merge.help": "Whether the copying was done as a result of merging two pages.",
    	"deputy.cte.copied.afd.placeholder": "AfD page (without Wikipedia:Articles for deletion/)",
    	"deputy.cte.copied.afd.label": "AfD page",
    	"deputy.cte.copied.afd.help": "The AfD page if the copy was made due to an AfD closed as \"merge\".",
    	"deputy.cte.copied.advanced": "Advanced",
    	"deputy.cte.copied.dateInvalid": "The previous date value, \"$1\", was not a valid date.",
    	"deputy.cte.copied.diffDeprecate": "The <code>to_diff</code> and <code>to_oldid</code> parameters are preferred in favor of the <code>diff</code> parameter.",
    	"deputy.cte.copied.diffDeprecate.replace": "The current value of '$1', \"$2\", will be replaced with \"$3\". Continue?",
    	"deputy.cte.copied.diffDeprecate.failed": "Cannot convert `diff` parameter to URL. See your browser console for more details.",
    	"deputy.cte.splitArticle.label": "Split article $1",
    	"deputy.cte.splitArticle.remove": "Remove notice",
    	"deputy.cte.splitArticle.remove.confirm": "This will destroy $1 {{PLURAL:$1|entry|entries}}. Continue?",
    	"deputy.cte.splitArticle.add": "Add entry",
    	"deputy.cte.splitArticle.entry.label": "Template entry",
    	"deputy.cte.splitArticle.entry.remove": "Remove entry",
    	"deputy.cte.splitArticle.entry.short": "$1 on $2",
    	"deputy.cte.splitArticle.collapse": "Collapse",
    	"deputy.cte.splitArticle.from": "From",
    	"deputy.cte.splitArticle.from.help": "This is the page where the content was split from. In most cases, this is the current page, and can be left blank.",
    	"deputy.cte.splitArticle.to.placeholder": "Subpage A",
    	"deputy.cte.splitArticle.to.label": "Page split to",
    	"deputy.cte.splitArticle.to.help": "This is the name of page that material was copied to; the \"merge target\".",
    	"deputy.cte.splitArticle.from_oldid.placeholder": "from_oldid",
    	"deputy.cte.splitArticle.from_oldid.label": "As of revision ID",
    	"deputy.cte.splitArticle.from_oldid.help": "The revision ID of the original page prior to the split. This is the revision that still contains content that will eventually become part of the split, with the following revision (or succeeding revisions) progressively transferring content to the other pages.",
    	"deputy.cte.splitArticle.date.label": "Date of split",
    	"deputy.cte.splitArticle.date.help": "The date that the split occurred.",
    	"deputy.cte.splitArticle.diff.placeholder": "https://en.wikipedia.org/w/index.php?diff=123456&oldid=123455",
    	"deputy.cte.splitArticle.diff.label": "Diff URL",
    	"deputy.cte.splitArticle.diff.help": "The diff URL of the split.",
    	"deputy.cte.mergedFrom.label": "Merged from $1",
    	"deputy.cte.mergedFrom.remove": "Remove notice",
    	"deputy.cte.mergedFrom.article.placeholder": "Page A",
    	"deputy.cte.mergedFrom.article.label": "Original article",
    	"deputy.cte.mergedFrom.article.help": "This is the page where the merged content is or used to be.",
    	"deputy.cte.mergedFrom.date.label": "Date",
    	"deputy.cte.mergedFrom.date.help": "The date (in UTC) when the content was merged into this page.",
    	"deputy.cte.mergedFrom.talk.label": "Link to talk?",
    	"deputy.cte.mergedFrom.talk.help": "Whether to link to the original article's talk page or not.",
    	"deputy.cte.mergedFrom.target.placeholder": "Page B",
    	"deputy.cte.mergedFrom.target.label": "Merge target",
    	"deputy.cte.mergedFrom.target.help": "The page that the content was merged into. Used if the page that the content was merged into was the talk page.",
    	"deputy.cte.mergedFrom.afd.placeholder": "Wikipedia:Articles for deletion/Page A",
    	"deputy.cte.mergedFrom.afd.label": "AfD",
    	"deputy.cte.mergedFrom.afd.help": "The AfD discussion that led to the merge. If this merge was not the result of an AfD discussion, leave this blank.",
    	"deputy.cte.mergedTo.label": "Merged to $1",
    	"deputy.cte.mergedTo.remove": "Remove notice",
    	"deputy.cte.mergedTo.to.placeholder": "Page A",
    	"deputy.cte.mergedTo.to.label": "Target article",
    	"deputy.cte.mergedTo.to.help": "This is the page where content was copied into.",
    	"deputy.cte.mergedTo.date.label": "Date",
    	"deputy.cte.mergedTo.date.help": "The date (in UTC) when the content was merged into this page.",
    	"deputy.cte.mergedTo.small.label": "Small",
    	"deputy.cte.mergedTo.small.help": "If enabled, makes the banner small.",
    	"deputy.cte.backwardsCopy.label": "Backwards copy $1",
    	"deputy.cte.backwardsCopy.remove": "Remove notice",
    	"deputy.cte.backwardsCopy.bot": "This notice was automatically added in by [[User:$1|$1]] ([[User talk:$1|talk]]). Changing this template will remove this warning as it is assumed that you have properly vetted the bot-added parameters.",
    	"deputy.cte.backwardsCopy.entry.label": "Template entry",
    	"deputy.cte.backwardsCopy.entry.short": "Copied in '$1'",
    	"deputy.cte.backwardsCopy.entry.remove": "Remove entry",
    	"deputy.cte.backwardsCopy.comments.placeholder": "Additional information",
    	"deputy.cte.backwardsCopy.comments.label": "Comments",
    	"deputy.cte.backwardsCopy.comments.help": "Additional comments related to the backwards copies.",
    	"deputy.cte.backwardsCopy.id.placeholder": "123456789",
    	"deputy.cte.backwardsCopy.id.label": "Revision ID",
    	"deputy.cte.backwardsCopy.id.help": "The last revision ID of this article that does not contain content that was duplicated by copying media.",
    	"deputy.cte.backwardsCopy.entry.title.placeholder": "Article, journal, or medium name",
    	"deputy.cte.backwardsCopy.entry.title.label": "Publication name",
    	"deputy.cte.backwardsCopy.entry.title.help": "The publication title. This is the title of the medium that copied from Wikipedia",
    	"deputy.cte.backwardsCopy.entry.date.label": "Publishing date",
    	"deputy.cte.backwardsCopy.entry.date.help": "This is the date on which the article was first published.",
    	"deputy.cte.backwardsCopy.entry.author.placeholder": "Add author",
    	"deputy.cte.backwardsCopy.entry.author.label": "Author(s)",
    	"deputy.cte.backwardsCopy.entry.author.help": "A list of the article's authors.",
    	"deputy.cte.backwardsCopy.entry.url.placeholder": "https://example.com/news/a-news-article-that-copies-from-wikipedia",
    	"deputy.cte.backwardsCopy.entry.url.label": "URL",
    	"deputy.cte.backwardsCopy.entry.url.help": "A URL to the published media, if it exists as an online resource. If this is not an online resource (newspaper media, other printed media), leave this blank.",
    	"deputy.cte.backwardsCopy.entry.org.placeholder": "Example Publishing",
    	"deputy.cte.backwardsCopy.entry.org.label": "Publisher",
    	"deputy.cte.backwardsCopy.entry.org.help": "The publisher of the media. This may be a news company or a book publishing company.",
    	"deputy.cte.translatedPage.label": "Translated from $1:$2",
    	"deputy.cte.translatedPage.remove": "Remove notice",
    	"deputy.cte.translatedPage.lang.placeholder": "en, de, fr, es, etc.",
    	"deputy.cte.translatedPage.lang.label": "Language code",
    	"deputy.cte.translatedPage.lang.help": "The language code of the wiki that the page was translated from. This is the \"en\" of the English Wikipedia, or the \"fr\" of the French Wikipedia.",
    	"deputy.cte.translatedPage.page.placeholder": "Page on other wiki",
    	"deputy.cte.translatedPage.page.label": "Source page",
    	"deputy.cte.translatedPage.page.help": "The page on the other wiki that the content was copied from. Do not translate the page title.",
    	"deputy.cte.translatedPage.comments.placeholder": "Additional comments",
    	"deputy.cte.translatedPage.comments.label": "Comments",
    	"deputy.cte.translatedPage.comments.help": "Additional comments that are pertinent to translation.",
    	"deputy.cte.translatedPage.version.placeholder": "123456789",
    	"deputy.cte.translatedPage.version.label": "Source revision ID",
    	"deputy.cte.translatedPage.version.help": "The revision ID of the source page at the time of translation.",
    	"deputy.cte.translatedPage.insertversion.placeholder": "987654321",
    	"deputy.cte.translatedPage.insertversion.label": "Insertion revision ID",
    	"deputy.cte.translatedPage.insertversion.help": "The revision ID of the revision where the translated content was inserted into the page bearing this notice.",
    	"deputy.cte.translatedPage.section.placeholder": "Section name",
    	"deputy.cte.translatedPage.section.label": "Section",
    	"deputy.cte.translatedPage.section.help": "The section of the page that was translated, if a specific section was translated. Leave blank if this does not apply, or if translation was performed on the entire page or more than one section.",
    	"deputy.cte.translatedPage.small.label": "Small?",
    	"deputy.cte.translatedPage.small.help": "Whether to render the template as a small message box or not. By default, a small box is used. If you have a good reason to use a full-sized banner, disable this option.",
    	"deputy.cte.translatedPage.partial.label": "Partial?",
    	"deputy.cte.translatedPage.partial.help": "Whether this translation is a partial translation or not."
    };

    /**
     * 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
     */
    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: 'content', rvslots: 'main', rvlimit: '1' }), extraOptions)).then((data) => {
            return Object.assign(data.query.pages[0].revisions[0].slots.main.content, { contentFormat: data.query.pages[0].revisions[0].slots.main.contentformat });
        });
    }

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

    /**
     * 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, _b;
            return __awaiter(this, void 0, void 0, function* () {
                const lang = (_b = (_a = window.deputyLang) !== null && _a !== void 0 ? _a : mw.config.get('wgUserLanguage')) !== null && _b !== void 0 ? _b : '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(() => {
                        if (window.deputyLang) {
                            // Only show this warning if a language was explicitly requested. There are
                            // cases where Deputy does not yet have a language file (likely due to a lack
                            // of translations), but the user has a 'wgUserLanguage' differing from
                            // English.
                            mw.notify(
                            // No languages to fall back on. Do not translate this string.
                            'Deputy: Could not load requested language file.', { type: 'error' });
                        }
                        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) {
                    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]);
                    }
                }
            });
        }
    }

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

    /**
     * This function handles CTE 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* () {
        window.CopiedTemplateEditor = new CopiedTemplateEditor();
        yield window.CopiedTemplateEditor.preInit();
    }))(window);

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