User:Chlod/Scripts/Deputy.js: Difference between revisions

Content deleted Content added
(bot/CD)
(bot/CD)
 
(11 intermediate revisions by the same user not shown)
Line 28:
*
* https://github.com/ChlodAlejandro/deputy
*
* ------------------------------------------------------------------------
*
* This script compiles with the following dependencies:
* * [https://github.com/Microsoft/tslib tslib] - 0BSD, Microsoft
* * [https://github.com/jakearchibald/idb idb] - ISC, Jake Archibald
* * [https://github.com/JSmith01/broadcastchannel-polyfill broadcastchannel-polyfill] - Unlicense, Joshua Bell
* * [https://github.com/Lusito/tsx-dom tsx-dom] - MIT, Santo Pfingsten
*
*/
// <nowiki>
/*!
* @package idb
* @version 7.1.0
* @license ISC
* @author Jake Archibald
* @url https://github.com/jakearchibald/idb
*//*!
* @package tsx-dom
* @version 1.4.0
* @license MIT
* @author Santo Pfingsten
* @url https://github.com/Lusito/tsx-dom
*//*!
* @package broadcastchannel-polyfill
* @version 1.0.1
* @license Unlicense
* @author Joshua Bell
* @url https://github.com/JSmith01/broadcastchannel-polyfill
*//*!
* @package @chlodalejandro/parsoid
* @version 2.0.1-37ea110
* @license MIT
* @author Chlod Alejandro
* @url https://github.com/ChlodAlejandro/parsoid-document
*/
(function () {
'use strict';
Line 73 ⟶ 56:
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
 
 
function __awaiter(thisArg, _arguments, P, generator) {
Line 113 ⟶ 98:
]));
}
const cursorRequestMap = new WeakMap();
const transactionDoneMap = new WeakMap();
const transactionStoreNamesMap = new WeakMap();
const transformCache = new WeakMap();
const reverseTransformCache = new WeakMap();
Line 135 ⟶ 118:
request.addEventListener('error', error);
});
promise
.then((value) => {
// Since cursoring reuses the IDBRequest (*sigh*), we cache it for later retrieval
// (see wrapFunction).
if (value instanceof IDBCursor) {
cursorRequestMap.set(value, request);
}
// Catching to avoid "Uncaught Promise exceptions"
})
.catch(() => { });
// This mapping exists in reverseTransformCache but doesn't doesn't exist in transformCache. This
// is because we create many promises from a single IDBRequest.
Line 181 ⟶ 154:
if (prop === 'done')
return transactionDoneMap.get(target);
// Polyfill for objectStoreNames because of Edge.
if (prop === 'objectStoreNames') {
return target.objectStoreNames || transactionStoreNamesMap.get(target);
}
// Make tx.store return the only store in the transaction, or undefined if there are many.
if (prop === 'store') {
Line 213 ⟶ 182:
// Due to expected object equality (which is enforced by the caching in `wrap`), we
// only create one new func per func.
// Edge doesn't support objectStoreNames (booo), so we polyfill it here.
if (func === IDBDatabase.prototype.transaction &&
!('objectStoreNames' in IDBTransaction.prototype)) {
return function (storeNames, ...args) {
const tx = func.call(unwrap(this), storeNames, ...args);
transactionStoreNamesMap.set(tx, storeNames.sort ? storeNames.sort() : [storeNames]);
return wrap(tx);
};
}
// Cursor methods are special, as the behaviour is a little more different to standard IDB. In
// IDB, you advance the cursor and wait for a new 'success' on the IDBRequest that gave you the
Line 232 ⟶ 192:
// the original object.
func.apply(unwrap(this), args);
return wrap(cursorRequestMap.get(this).request);
};
}
Line 350 ⟶ 310:
}));
 
const advanceMethodProps = ['continue', 'continuePrimaryKey', 'advance'];
var version = "0.8.0";
varconst gitAbbrevHashmethodMap = "fa06183"{};
varconst gitBranchadvanceResults = "main"new WeakMap();
const ittrProxiedCursorToOriginalProxy = new WeakMap();
var gitDate = "Fri, 7 Jun 2024 23:36:15 +0800";
const cursorIteratorTraps = {
var gitVersion = "0.8.0+gfa06183";
get(target, prop) {
if (!advanceMethodProps.includes(prop))
return target[prop];
let cachedFunc = methodMap[prop];
if (!cachedFunc) {
cachedFunc = methodMap[prop] = function (...args) {
advanceResults.set(this, ittrProxiedCursorToOriginalProxy.get(this)[prop](...args));
};
}
return cachedFunc;
},
};
async function* iterate(...args) {
// tslint:disable-next-line:no-this-assignment
let cursor = this;
if (!(cursor instanceof IDBCursor)) {
cursor = await cursor.openCursor(...args);
}
if (!cursor)
return;
cursor = cursor;
const proxiedCursor = new Proxy(cursor, cursorIteratorTraps);
ittrProxiedCursorToOriginalProxy.set(proxiedCursor, cursor);
// Map this double-proxy back to the original, so other cursor methods work.
reverseTransformCache.set(proxiedCursor, unwrap(cursor));
while (cursor) {
yield proxiedCursor;
// If one of the advancing methods was not called, call continue().
cursor = await (advanceResults.get(proxiedCursor) || cursor.continue());
advanceResults.delete(proxiedCursor);
}
}
function isIteratorProp(target, prop) {
return ((prop === Symbol.asyncIterator &&
instanceOfAny(target, [IDBIndex, IDBObjectStore, IDBCursor])) ||
(prop === 'iterate' && instanceOfAny(target, [IDBIndex, IDBObjectStore])));
}
replaceTraps((oldTraps) => ({
...oldTraps,
get(target, prop, receiver) {
if (isIteratorProp(target, prop))
return iterate;
return oldTraps.get(target, prop, receiver);
},
has(target, prop) {
return isIteratorProp(target, prop) || oldTraps.has(target, prop);
},
}));
 
var version = "0.9.0";
var gitAbbrevHash = "317b503";
var gitBranch = "HEAD";
var gitDate = "Wed, 19 Feb 2025 00:13:53 +0800";
var gitVersion = "0.9.0+g317b503";
 
/**
Line 414 ⟶ 428:
upgrade(db, oldVersion, newVersion) {
let currentVersion = oldVersion;
// Adding new stores? Make sure to also add it in `reset()`!
const upgrader = {
0: () => {
Line 501 ⟶ 516:
}
}
});
}
/**
* Reset the Deputy database. Very dangerous!
*/
reset() {
return __awaiter(this, void 0, void 0, function* () {
yield this.db.clear('keyval');
yield this.db.clear('casePageCache');
yield this.db.clear('diffCache');
yield this.db.clear('diffStatus');
yield this.db.clear('pageStatus');
yield this.db.clear('tagCache');
});
}
Line 880 ⟶ 908:
*/
class DeputyCase {
/**
* @param pageId The page ID of the case page.
* @param title The title of the case page.
*/
constructor(pageId, title) {
this.pageId = pageId;
this.title = title;
}
/**
* @return the title of the case page
Line 933 ⟶ 953:
return new DeputyCase(pageId, title);
});
}
/**
* @param pageId The page ID of the case page.
* @param title The title of the case page.
*/
constructor(pageId, title) {
this.pageId = pageId;
this.title = title;
}
/**
Line 1,059 ⟶ 1,087:
// To determine if we are inside or outside, we keep climbing up until
// we either hit an <hN> or a given stop point.
// The default stop point is,differs byon default,Parsoid `.mw-parser-output`,and whichstandard exists both in aparser:
// - On Parsoid, document`<body>` andwill in standardbe `.mw-body-content.mw-parser -output`. If such an element doesn't
// - On standard parser, we want `div.mw-body-content > div.mw-parser.output`.
// If such an element doesn't
// exist in this document, we just stop at the root element.
ceiling = (_a = ceiling !== null && ceiling !== void 0 ? ceiling : elementNode.ownerDocument.querySelector('.mw-body-content > .mw-parser-output, .mw-body-content.mw-parser-output')) !== null && _a !== void 0 ? _a : elementNode.ownerDocument.documentElement;
// While we haven't hit a heading, keep going up.
while (elementNode !== ceiling) {
Line 1,142 ⟶ 1,172:
*
* @param sectionHeading
* @param sectionHeadingPredicate A function which returns `true` if the section should stop here
* @return Section headings.
*/
Line 1,161 ⟶ 1,191:
*/
class DeputyCasePage extends DeputyCase {
/**
* @param pageId The page ID of the case page.
* @param title The title of the page being accessed
* @param document The document to be used as a reference.
* @param parsoid Whether this is a Parsoid document or not.
* @param lastActive
* @param lastActiveSessions
*/
constructor(pageId, title, document, parsoid, lastActive, lastActiveSessions) {
super(pageId !== null && pageId !== void 0 ? pageId : window.deputy.currentPageId, title !== null && title !== void 0 ? title : window.deputy.currentPage);
/**
* A timestamp of when this case page was last worked on.
*/
this.lastActive = Date.now();
/**
* The sections last worked on for this case page.
*/
this.lastActiveSections = [];
this.document = document !== null && document !== void 0 ? document : window.document;
this.parsoid = parsoid !== null && parsoid !== void 0 ? parsoid : /mw: http:\/\/mediawiki.org\/rdf\//.test(this.document.documentElement.getAttribute('prefix'));
this.wikitext = new DeputyCasePageWikitext(this);
this.lastActive = lastActive !== null && lastActive !== void 0 ? lastActive : Date.now();
this.lastActiveSections = lastActiveSessions !== null && lastActiveSessions !== void 0 ? lastActiveSessions : [];
}
/**
* @param pageId The page ID of the case page.
Line 1,216 ⟶ 1,222:
}
});
}
/**
* @param pageId The page ID of the case page.
* @param title The title of the page being accessed
* @param document The document to be used as a reference.
* @param parsoid Whether this is a Parsoid document or not.
* @param lastActive
* @param lastActiveSessions
*/
constructor(pageId, title, document, parsoid, lastActive, lastActiveSessions) {
super(pageId !== null && pageId !== void 0 ? pageId : window.deputy.currentPageId, title !== null && title !== void 0 ? title : window.deputy.currentPage);
/**
* A timestamp of when this case page was last worked on.
*/
this.lastActive = Date.now();
/**
* The sections last worked on for this case page.
*/
this.lastActiveSections = [];
this.document = document !== null && document !== void 0 ? document : window.document;
this.parsoid = parsoid !== null && parsoid !== void 0 ? parsoid : /mw: http:\/\/mediawiki.org\/rdf\//.test(this.document.documentElement.getAttribute('prefix'));
this.wikitext = new DeputyCasePageWikitext(this);
this.lastActive = lastActive !== null && lastActive !== void 0 ? lastActive : Date.now();
this.lastActiveSections = lastActiveSessions !== null && lastActiveSessions !== void 0 ? lastActiveSessions : [];
}
/**
Line 1,229 ⟶ 1,259:
const heading = normalizeWikiHeading(el);
return heading != null &&
// Require that this heading is already normalized.
// TODO: Remove at some point.
// This shouldn't be required if double-normalization wasn't a thing.
el === heading.h &&
// eslint-disable-next-line security/detect-non-literal-regexp
Line 1,292 ⟶ 1,325:
const heading = normalizeWikiHeading(sectionHeading);
const ceiling = heading.root.parentElement;
return getSectionElements(heading.root, (el) => { var _a, _b; return heading.level >= ((_b = (_a = normalizeWikiHeading(el, ceiling)) === null || _a === void 0 ? void 0 : _a.level) !== null && _b !== void 0 ? _b : Infinity); });
var _a, _b;
// TODO: Avoid double normalization
const norm = normalizeWikiHeading(el, ceiling);
return (heading.level >= ((_a = norm === null || norm === void 0 ? void 0 : norm.level) !== null && _a !== void 0 ? _a : Infinity)) ||
this.isContributionSurveyHeading((_b = norm === null || norm === void 0 ? void 0 : norm.h) !== null && _b !== void 0 ? _b : el);
});
}
/**
Line 2,028 ⟶ 2,067:
class Setting {
/**
*
* @param options
* @param options.serialize Serialization function. See {@link Setting#serialize}
Line 2,160 ⟶ 2,198:
}
 
/**
* Generates serializer and deserializer for serialized <b>string</b> enums.
*
* Trying to use anything that isn't a string enum here (union enum, numeral enum)
* will likely cause serialization/deserialization failures.
*
* @param _enum
* @param defaultValue
* @return An object containing a `serializer` and `deserializer`.
*/
function generateEnumSerializers(_enum, defaultValue) {
return {
serialize: (value) => value === defaultValue ? undefined : value,
deserialize: (value) => value
};
}
/**
* Generates configuration properties for serialized <b>string</b> enums.
Line 2,171 ⟶ 2,225:
*/
function generateEnumConfigurationProperties(_enum, defaultValue) {
return Object.assign(Object.assign({}, generateEnumSerializers(_enum, defaultValue)), { displayOptions: {
return {
serialize: (value) => value === defaultValue ? undefined : value,
deserialize: (value) => value,
displayOptions: {
type: 'radio'
}, allowedValues: fromObjectEntries(Array.from(new Set(Object.keys(_enum)).values())
},
.map((v) => [_enum[v], _enum[v]])), defaultValue: defaultValue });
allowedValues: fromObjectEntries(Array.from(new Set(Object.keys(_enum)).values())
.map((v) => [_enum[v], _enum[v]])),
defaultValue: defaultValue
};
}
var PortletNameView;
Line 2,209 ⟶ 2,257:
ContributionSurveyRowSigningBehavior["Never"] = "never";
})(ContributionSurveyRowSigningBehavior || (ContributionSurveyRowSigningBehavior = {}));
 
var DeputyPageToolbarState;
(function (DeputyPageToolbarState) {
DeputyPageToolbarState[DeputyPageToolbarState["Open"] = 0] = "Open";
DeputyPageToolbarState[DeputyPageToolbarState["Collapsed"] = 1] = "Collapsed";
DeputyPageToolbarState[DeputyPageToolbarState["Hidden"] = 2] = "Hidden";
})(DeputyPageToolbarState || (DeputyPageToolbarState = {}));
 
/**
* A button that performs an action when clicked. Shown in the preferences screen,
* and acts exactly like a setting, but always holds a value of 'null'.
*/
class Action extends Setting {
/**
* @param onClick
* @param displayOptions
*/
constructor(onClick, displayOptions = {}) {
super({
serialize: () => undefined,
deserialize: () => undefined,
displayOptions: Object.assign({}, displayOptions, { type: 'button' })
});
this.onClick = onClick;
}
}
 
/**
Line 2,214 ⟶ 2,288:
*/
class UserConfiguration extends ConfigurationBase {
/**
* @return the configuration from the current wiki.
*/
static load() {
const config = new UserConfiguration();
try {
if (mw.user.options.get(UserConfiguration.optionKey)) {
const decodedOptions = JSON.parse(mw.user.options.get(UserConfiguration.optionKey));
config.deserialize(decodedOptions);
}
}
catch (e) {
error(e, mw.user.options.get(UserConfiguration.optionKey));
mw.hook('deputy.i18nDone').add(function notifyConfigFailure() {
mw.notify(mw.msg('deputy.loadError.userConfig'), {
type: 'error'
});
mw.hook('deputy.i18nDone').remove(notifyConfigFailure);
});
config.save();
}
return config;
}
/**
* Creates a new Configuration.
Line 2,250 ⟶ 2,347:
displayOptions: {
type: 'checkbox'
}
}),
resetDatabase: new Action(() => __awaiter(this, void 0, void 0, function* () {
yield window.deputy.storage.reset();
}), {
disabled: () => !window.deputy,
extraOptions: {
flags: ['destructive']
}
}),
resetPreferences: new Action(() => __awaiter(this, void 0, void 0, function* () {
yield MwApi.action.saveOption(UserConfiguration.optionKey, null);
}), {
extraOptions: {
flags: ['destructive']
}
})
Line 2,324 ⟶ 2,436:
type: 'checkbox'
}
}),
toolbarInitialState: new Setting(Object.assign(Object.assign({}, generateEnumSerializers(DeputyPageToolbarState, DeputyPageToolbarState.Open)), { defaultValue: DeputyPageToolbarState.Open, displayOptions: { hidden: true } }))
};
this.ante = {
Line 2,377 ⟶ 2,490:
});
}
}
/**
* @return the configuration from the current wiki.
*/
static load() {
const config = new UserConfiguration();
try {
if (mw.user.options.get(UserConfiguration.optionKey)) {
const decodedOptions = JSON.parse(mw.user.options.get(UserConfiguration.optionKey));
config.deserialize(decodedOptions);
}
}
catch (e) {
error(e, mw.user.options.get(UserConfiguration.optionKey));
mw.hook('deputy.i18nDone').add(function notifyConfigFailure() {
mw.notify(mw.msg('deputy.loadError.userConfig'), {
type: 'error'
});
mw.hook('deputy.i18nDone').remove(notifyConfigFailure);
});
config.save();
}
return config;
}
/**
Line 2,413 ⟶ 2,503:
UserConfiguration.optionKey = 'userjs-deputy';
 
/* eslint-disable mediawiki/msg-doc */
let InternalConfigurationGroupTabPanel$1;
/**
Line 2,420 ⟶ 2,509:
function initConfigurationGroupTabPanel$1() {
InternalConfigurationGroupTabPanel$1 = class ConfigurationGroupTabPanel extends OO.ui.TabPanelLayout {
/**
* @return The {@Link Setting}s for this group.
*/
get settings() {
return this.config.config.all[this.config.group];
}
/**
* @param config Configuration to be passed to the element.
Line 2,462 ⟶ 2,557:
case 'code':
this.$element.append(this.newCodeField(settingKey, setting, setting.displayOptions.extraOptions));
break;
case 'button':
this.$element.append(this.newButtonField(settingKey, setting, setting.displayOptions.extraOptions));
break;
default:
Line 2,468 ⟶ 2,566:
}
}
}
/**
* @return The {@Link Setting}s for this group.
*/
get settings() {
return this.config.config.all[this.config.group];
}
/**
Line 2,731 ⟶ 2,823:
newCodeField(settingKey, setting, extraFieldOptions) {
return this.newStringLikeField(OO.ui.MultilineTextInputWidget, settingKey, setting, extraFieldOptions);
}
/**
* Creates a new button setting field.
*
* @param settingKey
* @param setting
* @param extraFieldOptions
* @return An HTMLElement of the given setting's field.
*/
newButtonField(settingKey, setting, extraFieldOptions) {
const isDisabled = setting.isDisabled(this.config.config);
const msgPrefix = `deputy.setting.${this.mode}.${this.config.group}.${settingKey}`;
const desc = mw.message(`${msgPrefix}.description`);
const field = new OO.ui.ButtonWidget(Object.assign({ label: this.getSettingMsg(settingKey, 'name'), disabled: isDisabled !== undefined && isDisabled !== false }, extraFieldOptions));
const layout = new OO.ui.FieldLayout(field, {
align: 'top',
label: this.getSettingMsg(settingKey, 'name'),
help: typeof isDisabled === 'string' ?
this.getSettingMsg(settingKey, isDisabled) :
desc.exists() ? desc.text() : undefined,
helpInline: true
});
field.on('click', () => __awaiter(this, void 0, void 0, function* () {
try {
if (yield OO.ui.confirm(mw.msg(`${msgPrefix}.confirm`))) {
yield setting.onClick();
OO.ui.alert(mw.msg(`${msgPrefix}.success`));
}
}
catch (e) {
OO.ui.alert(mw.msg(`${msgPrefix}.failed`));
}
}));
return h_1("div", { class: "deputy-setting" }, unwrapWidget(layout));
}
};
Line 2,747 ⟶ 2,873:
}
 
let windowManager;
/**
* Opens a temporary window. Use this for dialogs that are immediately destroyed
Line 2,758 ⟶ 2,885:
return __awaiter(this, void 0, void 0, function* () {
return new Promise((res) => {
letvar wm = new OO.ui.WindowManager()_a;
if (!windowManager) {
document.getElementsByTagName('body')[0].appendChild(unwrapWidget(wm));
wm windowManager = new OO.addWindowsui.WindowManager([window]);
const parent = (_a = document.getElementById('mw-teleport-target')) !== null && _a !== void 0 ? _a : document.getElementsByTagName('body')[0];
wm.openWindow(window);
wm.on('closing', (win, closed) => {parent.appendChild(unwrapWidget(windowManager));
}
windowManager.addWindows([window]);
windowManager.openWindow(window);
windowManager.on('closing', (win, closed) => {
closed.then(() => {
if (wmwindowManager) {
const _wm = wmwindowManager;
wmwindowManager = null;
removeElement(unwrapWidget(_wm));
_wm.destroy();
Line 2,819 ⟶ 2,950:
"deputy.setting.user.core.dangerMode.name": "Danger mode",
"deputy.setting.user.core.dangerMode.description": "Live on the edge. This disables most confirmations and warnings given by Deputy, only leaving potentially catastrophic actions, such as page edits which break templates. It also adds extra buttons meant for rapid case processing. Intended for clerk use; use with extreme caution.",
"deputy.setting.user.core.resetDatabase.name": "Reset database",
"deputy.setting.user.core.resetDatabase.description": "Resets the Deputy internal database. This clears all cached data, including saved page and revision statuses that haven't been saved on-wiki. This does not clear your Deputy preferences.",
"deputy.setting.user.core.resetDatabase.confirm": "Are you sure you want to reset the Deputy database? This action cannot be undone.",
"deputy.setting.user.core.resetDatabase.success": "Database reset successfully. Please refresh the page to see changes.",
"deputy.setting.user.core.resetDatabase.failed": "Could not reset the database. If this error persists, please contact the Deputy maintainers.",
"deputy.setting.user.core.resetPreferences.name": "Reset preferences",
"deputy.setting.user.core.resetPreferences.description": "Resets your Deputy preferences. This clears all settings you have saved in Deputy across all browsers.",
"deputy.setting.user.core.resetPreferences.confirm": "Are you sure you want to reset your Deputy preferences? This action cannot be undone.",
"deputy.setting.user.core.resetPreferences.success": "Preferences reset successfully. Please refresh the page to see changes.",
"deputy.setting.user.core.resetPreferences.failed": "Could not reset preferences. If this error persists, please contact the Deputy maintainers.",
"deputy.setting.user.cci": "CCI",
"deputy.setting.user.cci.enablePageToolbar.name": "Enable page toolbar",
Line 2,891 ⟶ 3,032:
"deputy.setting.wiki.cci.rootPage.name": "Root page",
"deputy.setting.wiki.cci.rootPage.description": "The main page that holds all subpages containing valid contribution copyright investigation cases.",
"deputy.setting.wiki.cci.headingMatch.name": "Heading title regular expression",
"deputy.setting.wiki.cci.headingMatch.description": "A regular expression that will be used to detect valid contribution surveyor heading. Since its usage is rather technical, this value should be edited by someone with technical knowledge of regular expressions.",
"deputy.setting.wiki.cci.collapseTop.name": "Collapsible wikitext (top)",
"deputy.setting.wiki.cci.collapseTop.description": "Placed just below a section heading when closing a contributor survey section. Use \"$1\" to denote user comments and signature. On the English Wikipedia, this is {{Template:collapse top}}. Other wikis may have an equivalent template. This should go hand in hand with \"{{int:deputy.setting.wiki.cci.collapseBottom.name}}\", as they are used as a pair.",
Line 2,897 ⟶ 3,040:
"deputy.setting.wiki.cci.earwigRoot.name": "Earwig's Copyvio Detector root URL",
"deputy.setting.wiki.cci.earwigRoot.description": "The URL to an instance of Earwig's Copyvio Detector that can handle this wiki. The official copyvio detector (copyvios.toolforge.org) can only handle Wikimedia wikis — you may change this behavior by specifying a custom instance that can process this wiki here.",
"deputy.setting.wiki.cci.resortRows.name": "Resort rows",
"deputy.setting.wiki.cci.resortRows.description": "Resort rows when saving the page. This is useful for cases where rows are added out of order, or when rows are added in a different order than they should be displayed.",
"deputy.setting.wiki.ante": "ANTE",
"deputy.setting.wiki.ante.enabled.name": "Enable the Attribution Notice Template Editor",
Line 2,923 ⟶ 3,068:
"deputy.setting.wiki.ia.hideTemplateBottom.name": "Content hiding wikitext (bottom)",
"deputy.setting.wiki.ia.hideTemplateBottom.description": "Placed at the end of hidden content to hide only part of a page. On the English Wikipedia, this is {{Template:copyvio/bottom}}. Other wikis may have an equivalent template.",
"deputy.setting.wiki.ia.entirePageAppendBottom.name": "Append content hiding wikitext (bottom) when hiding an entire page",
"deputy.setting.wiki.ia.entirePageAppendBottom.description": "If enabled, the content hiding wikitext (bottom) will be appended to the end of the page when hiding the entire page. This avoids the \"missing end tag\" lint error, if the template is properly formatted.",
"deputy.setting.wiki.ia.responses.name": "Responses",
"deputy.setting.wiki.ia.responses.description": "Quick responses for copyright problems listings. Used by clerks to resolve specific listings or provide more information about the progress of a given listing."
Line 2,977 ⟶ 3,124:
DeputyResources.root = {
type: 'url',
url: new URL('https://zoomiebottools-static.toolforgewmflabs.org/deputy/')
};
 
Line 3,127 ⟶ 3,274:
var _a;
InternalConfigurationGroupTabPanel = (_a = class ConfigurationGroupTabPanel extends OO.ui.TabPanelLayout {
/**
* @return The {@Link Setting}s for this group.
*/
get settings() {
return this.config.config.all[this.config.group];
}
/**
*/
Line 3,158 ⟶ 3,311:
unwrapJQ(h_1("p", { style: { fontSize: '0.9em', color: 'darkgray' } }), mw.message('deputy.about.buildInfo', gitVersion, gitBranch, new Date(gitDate).toLocaleString()).parseDom()),
unwrapJQ(h_1("p", { style: { fontSize: '0.9em', color: 'darkgray' } }), mw.message('deputy.about.footer').parseDom())));
}
/**
* @return The {@Link Setting}s for this group.
*/
get settings() {
return this.config.config.all[this.config.group];
}
/**
Line 3,196 ⟶ 3,343:
InternalConfigurationDialog = (_a = class ConfigurationDialog extends OO.ui.ProcessDialog {
/**
*
* @param data
*/
Line 3,229 ⟶ 3,375:
generateGroupLayouts() {
return Object.keys(this.config.all).map((group) => ConfigurationGroupTabPanel({
$overlay: this.$overlay,
config: this.config,
group
Line 3,234 ⟶ 3,381:
}
/**
*
* @param action
* @return An OOUI Process.
Line 3,572 ⟶ 3,718:
class WikiConfiguration extends ConfigurationBase {
/**
* Loads the configuration from a set of possible sources.
*
* @param sourcePage The specific page to load from
* @return A WikiConfiguration object
*/
static load(sourcePage) {
return __awaiter(this, void 0, void 0, function* () {
if (sourcePage) {
// Explicit source given. Do not load from local cache.
return this.loadFromWiki(sourcePage);
}
else {
return this.loadFromLocal();
}
});
}
/**
* Loads the wiki configuration from localStorage and/or MediaWiki
* settings. This allows for faster loads at the expense of a (small)
* chance of outdated configuration.
*
* The localStorage layer allows fast browser-based caching. If a user
* is logging in again on another device, the user configuration
* will automatically be sent to the client, lessening turnaround time.
* If all else fails, the configuration will be loaded from the wiki.
*
* @return A WikiConfiguration object.
*/
static loadFromLocal() {
return __awaiter(this, void 0, void 0, function* () {
let configInfo;
// If `mw.storage.get` returns `false` or `null`, it'll be thrown up.
let rawConfigInfo = mw.storage.get(WikiConfiguration.optionKey);
// Try to grab it from user options, if it exists.
if (!rawConfigInfo) {
rawConfigInfo = mw.user.options.get(WikiConfiguration.optionKey);
}
if (typeof rawConfigInfo === 'string') {
try {
configInfo = JSON.parse(rawConfigInfo);
}
catch (e) {
// Bad local! Switch to non-local.
error('Failed to get Deputy wiki configuration', e);
return this.loadFromWiki();
}
}
else {
log('No locally-cached Deputy configuration, pulling from wiki.');
return this.loadFromWiki();
}
if (configInfo) {
return new WikiConfiguration(new mw.Title(configInfo.title.title, configInfo.title.namespace), JSON.parse(configInfo.wt), configInfo.editable);
}
else {
return this.loadFromWiki();
}
});
}
/**
* Loads the configuration from the current wiki.
*
* @param sourcePage The specific page to load from
* @return A WikiConfiguration object
*/
static loadFromWiki(sourcePage) {
return __awaiter(this, void 0, void 0, function* () {
const configPage = sourcePage ? Object.assign({ title: sourcePage }, yield (() => __awaiter(this, void 0, void 0, function* () {
const content = yield getPageContent(sourcePage, {
prop: 'revisions|info',
intestactions: 'edit',
fallbacktext: '{}'
});
return {
wt: content,
editable: content.page.actions.edit
};
}))()) : yield this.loadConfigurationWikitext();
try {
// Attempt save of configuration to local options (if not explicitly loaded)
if (sourcePage == null) {
mw.storage.set(WikiConfiguration.optionKey, JSON.stringify(configPage));
}
return new WikiConfiguration(configPage.title, JSON.parse(configPage.wt), configPage.editable);
}
catch (e) {
error(e, configPage);
mw.hook('deputy.i18nDone').add(function notifyConfigFailure() {
mw.notify(mw.msg('deputy.loadError.wikiConfig'), {
type: 'error'
});
mw.hook('deputy.i18nDone').remove(notifyConfigFailure);
});
return null;
}
});
}
/**
* Loads the wiki-wide configuration from a set of predefined locations.
* See {@link WikiConfiguration#configLocations} for a full list.
*
* @return The string text of the raw configuration, or `null` if a configuration was not found.
*/
static loadConfigurationWikitext() {
return __awaiter(this, void 0, void 0, function* () {
const response = yield MwApi.action.get({
action: 'query',
prop: 'revisions|info',
rvprop: 'content',
rvslots: 'main',
rvlimit: 1,
intestactions: 'edit',
redirects: true,
titles: WikiConfiguration.configLocations.join('|')
});
const redirects = toRedirectsObject(response.query.redirects, response.query.normalized);
for (const page of WikiConfiguration.configLocations) {
const title = normalizeTitle(redirects[page] || page).getPrefixedText();
const pageInfo = response.query.pages.find((p) => p.title === title);
if (!pageInfo.missing) {
return {
title: normalizeTitle(pageInfo.title),
wt: pageInfo.revisions[0].slots.main.content,
editable: pageInfo.actions.edit
};
}
}
return null;
});
}
/**
* Check if the current page being viewed is a valid configuration page.
*
* @param page
* @return `true` if the current page is a valid configuration page.
*/
static isConfigurationPage(page) {
if (page == null) {
page = new mw.Title(mw.config.get('wgPageName'));
}
return this.configLocations.some((v) => equalTitle(page, normalizeTitle(v)));
}
/**
* @param sourcePage
* @param serializedData
Line 3,641 ⟶ 3,929:
displayOptions: { type: 'text' },
alwaysSave: true
}),
resortRows: new Setting({
defaultValue: true,
displayOptions: { type: 'checkbox' }
})
};
Line 3,709 ⟶ 4,001:
defaultValue: copyvioBottom,
displayOptions: { type: 'code' }
}),
entirePageAppendBottom: new Setting({
defaultValue: true,
displayOptions: { type: 'checkbox' }
}),
responses: new Setting(Object.assign(Object.assign({}, Setting.basicSerializers), { defaultValue: null, displayOptions: { type: 'unimplemented' } }))
Line 3,733 ⟶ 4,029:
});
}
}
/**
* Loads the configuration from a set of possible sources.
*
* @param sourcePage The specific page to load from
* @return A WikiConfiguration object
*/
static load(sourcePage) {
return __awaiter(this, void 0, void 0, function* () {
if (sourcePage) {
// Explicit source given. Do not load from local cache.
return this.loadFromWiki(sourcePage);
}
else {
return this.loadFromLocal();
}
});
}
/**
* Loads the wiki configuration from localStorage and/or MediaWiki
* settings. This allows for faster loads at the expense of a (small)
* chance of outdated configuration.
*
* The localStorage layer allows fast browser-based caching. If a user
* is logging in again on another device, the user configuration
* will automatically be sent to the client, lessening turnaround time.
* If all else fails, the configuration will be loaded from the wiki.
*
* @return A WikiConfiguration object.
*/
static loadFromLocal() {
return __awaiter(this, void 0, void 0, function* () {
let configInfo;
// If `mw.storage.get` returns `false` or `null`, it'll be thrown up.
let rawConfigInfo = mw.storage.get(WikiConfiguration.optionKey);
// Try to grab it from user options, if it exists.
if (!rawConfigInfo) {
rawConfigInfo = mw.user.options.get(WikiConfiguration.optionKey);
}
if (typeof rawConfigInfo === 'string') {
try {
configInfo = JSON.parse(rawConfigInfo);
}
catch (e) {
// Bad local! Switch to non-local.
error('Failed to get Deputy wiki configuration', e);
return this.loadFromWiki();
}
}
else {
log('No locally-cached Deputy configuration, pulling from wiki.');
return this.loadFromWiki();
}
if (configInfo) {
return new WikiConfiguration(new mw.Title(configInfo.title.title, configInfo.title.namespace), JSON.parse(configInfo.wt), configInfo.editable);
}
else {
return this.loadFromWiki();
}
});
}
/**
* Loads the configuration from the current wiki.
*
* @param sourcePage The specific page to load from
* @return A WikiConfiguration object
*/
static loadFromWiki(sourcePage) {
return __awaiter(this, void 0, void 0, function* () {
const configPage = sourcePage ? Object.assign({ title: sourcePage }, yield (() => __awaiter(this, void 0, void 0, function* () {
const content = yield getPageContent(sourcePage, {
prop: 'revisions|info',
intestactions: 'edit',
fallbacktext: '{}'
});
return {
wt: content,
editable: content.page.actions.edit
};
}))()) : yield this.loadConfigurationWikitext();
try {
// Attempt save of configuration to local options (if not explicitly loaded)
if (sourcePage == null) {
mw.storage.set(WikiConfiguration.optionKey, JSON.stringify(configPage));
}
return new WikiConfiguration(configPage.title, JSON.parse(configPage.wt), configPage.editable);
}
catch (e) {
error(e, configPage);
mw.hook('deputy.i18nDone').add(function notifyConfigFailure() {
mw.notify(mw.msg('deputy.loadError.wikiConfig'), {
type: 'error'
});
mw.hook('deputy.i18nDone').remove(notifyConfigFailure);
});
return null;
}
});
}
/**
* Loads the wiki-wide configuration from a set of predefined locations.
* See {@link WikiConfiguration#configLocations} for a full list.
*
* @return The string text of the raw configuration, or `null` if a configuration was not found.
*/
static loadConfigurationWikitext() {
return __awaiter(this, void 0, void 0, function* () {
const response = yield MwApi.action.get({
action: 'query',
prop: 'revisions|info',
rvprop: 'content',
rvslots: 'main',
rvlimit: 1,
intestactions: 'edit',
redirects: true,
titles: WikiConfiguration.configLocations.join('|')
});
const redirects = toRedirectsObject(response.query.redirects, response.query.normalized);
for (const page of WikiConfiguration.configLocations) {
const title = normalizeTitle(redirects[page] || page).getPrefixedText();
const pageInfo = response.query.pages.find((p) => p.title === title);
if (!pageInfo.missing) {
return {
title: normalizeTitle(pageInfo.title),
wt: pageInfo.revisions[0].slots.main.content,
editable: pageInfo.actions.edit
};
}
}
return null;
});
}
/**
* Check if the current page being viewed is a valid configuration page.
*
* @param page
* @return `true` if the current page is a valid configuration page.
*/
static isConfigurationPage(page) {
if (page == null) {
page = new mw.Title(mw.config.get('wgPageName'));
}
return this.configLocations.some((v) => equalTitle(page, normalizeTitle(v)));
}
/**
Line 4,135 ⟶ 4,288:
*/
class ContributionSurveyRow {
/**
* Creates a new contribution survey row from MediaWiki parser output.
*
* @param casePage The case page of this row
* @param wikitext The wikitext of the row
*/
constructor(casePage, wikitext) {
this.data = new ContributionSurveyRowParser(wikitext).parse();
this.type = this.data.type;
this.casePage = casePage;
this.wikitext = wikitext;
this.title = new mw.Title(this.data.page);
this.extras = this.data.extras;
this.comment = this.data.comments;
this.status = this.originalStatus = this.data.comments == null ?
ContributionSurveyRowStatus.Unfinished :
ContributionSurveyRow.identifyCommentStatus(this.data.comments);
if (ContributionSurveyRow.commentMatchRegex[this.status] != null) {
if (cloneRegex$1((ContributionSurveyRow.commentMatchRegex)[this.status], { pre: '^' }).test(this.comment)) {
this.statusIsolated = 'start';
}
else if (cloneRegex$1((ContributionSurveyRow.commentMatchRegex)[this.status], { post: '$' }).test(this.comment)) {
this.statusIsolated = 'end';
}
else {
this.statusIsolated = false;
}
}
}
/**
* Identifies a row's current status based on the comment's contents.
Line 4,189 ⟶ 4,313:
let dateReverseScore = 1;
let byteScore = 1;
let dateStreak = 0;
let dateReverseStreak = 0;
let byteStreak = 0;
for (const diff of diffs) {
if (last == null) {
Line 4,196 ⟶ 4,323:
const diffTimestamp = new Date(diff.timestamp).getTime();
const lastTimestamp = new Date(last.timestamp).getTime();
dateScore// =The (dateScoreuse +of (diffTimestampthe >OR lastTimestampoperator ?here 1has :a 0))specific / 2;purpose:
dateReverseScore// =* (dateReverseScoreOn +the (diffTimestampfirst <iteration, lastTimestampwe ?want 1all :streak 0))values /to 2;be 1
byteScore// =* (byteScoreOn +any (diff.diffsizeother <iteration, last.diffsizewe ?want 1it :to 0))increment /the streak by 1 if a 2;streak
// exists, or set it to 1 if a streak was broken.
dateStreak =
diffTimestamp > lastTimestamp ? dateStreak + 1 : 0;
dateReverseStreak =
diffTimestamp < lastTimestamp ? dateReverseStreak + 1 : 0;
byteStreak =
diff.diffsize <= last.diffsize ? byteStreak + 1 : 0;
dateScore = (dateScore + ((diffTimestamp > lastTimestamp ? 1 : 0) * (1 + dateStreak * 0.3))) / 2;
dateReverseScore = (dateReverseScore + ((diffTimestamp < lastTimestamp ? 1 : 0) * (1 + dateReverseStreak * 0.3))) / 2;
byteScore = (byteScore + ((diff.diffsize <= last.diffsize ? 1 : 0) * (1 + byteStreak * 0.3))) / 2;
last = diff;
}
}
// Multiply by weights to remove ties
dateScore *= 1.105;
dateReverseScore *= 1.05025;
switch (Math.max(dateScore, dateReverseScore, byteScore)) {
case byteScore:
Line 4,265 ⟶ 4,402:
return this.status !== ContributionSurveyRowStatus.Unfinished &&
this.diffs.size === 0;
}
/**
* Creates a new contribution survey row from MediaWiki parser output.
*
* @param casePage The case page of this row
* @param wikitext The wikitext of the row
*/
constructor(casePage, wikitext) {
this.data = new ContributionSurveyRowParser(wikitext).parse();
this.type = this.data.type;
this.casePage = casePage;
this.wikitext = wikitext;
this.title = new mw.Title(this.data.page);
this.extras = this.data.extras;
this.comment = this.data.comments;
this.status = this.originalStatus = this.data.comments == null ?
ContributionSurveyRowStatus.Unfinished :
ContributionSurveyRow.identifyCommentStatus(this.data.comments);
if (ContributionSurveyRow.commentMatchRegex[this.status] != null) {
if (cloneRegex$1((ContributionSurveyRow.commentMatchRegex)[this.status], { pre: '^' }).test(this.comment)) {
this.statusIsolated = 'start';
}
else if (cloneRegex$1((ContributionSurveyRow.commentMatchRegex)[this.status], { post: '$' }).test(this.comment)) {
this.statusIsolated = 'end';
}
else {
this.statusIsolated = false;
}
}
}
/**
Line 4,313 ⟶ 4,479:
amenableparser: true
});
// Sort the rows (if rearranging is enabled)
const sortOrder = ContributionSurveyRow.guessSortOrder(revisionData.values());
if (window.deputy.wikiConfig.cci.resortRows.get()) {
// Sort from most bytes to least.
return this.diffs const sortOrder = new MapContributionSurveyRow.guessSortOrder([...revisionData.entriesvalues()].sort(ContributionSurveyRow.getSorterFunction(sortOrder, 'value')));
return this.diffs = new Map([...revisionData.entries()].sort(ContributionSurveyRow.getSorterFunction(sortOrder, 'value')));
}
else {
return this.diffs = new Map([...revisionData.entries()]);
}
});
}
Line 4,764 ⟶ 4,935:
*/
class DeputyContributionSurveyRevision extends EventTarget {
/**
* @return `true` the current revision has been checked by the user or `false` if not.
*/
get completed() {
var _a, _b;
return (_b = (_a = this.completedCheckbox) === null || _a === void 0 ? void 0 : _a.isSelected()) !== null && _b !== void 0 ? _b : false;
}
/**
* Set the value of the completed checkbox.
*
* @param value The new value
*/
set completed(value) {
var _a;
(_a = this.completedCheckbox) === null || _a === void 0 ? void 0 : _a.setSelected(value);
}
/**
* @return The hash used for autosave keys
*/
get autosaveHash() {
return `CASE--${this.uiRow.row.casePage.title.getPrefixedDb()}+PAGE--${this.uiRow.row.title.getPrefixedDb()}+REVISION--${this.revision.revid}`;
}
/**
* @param revision
Line 4,777 ⟶ 4,970:
* The diff view of the given revision. May also be "loading" text, or
* null if the diff view has not yet been set.
*
* @private
*/
Line 4,789 ⟶ 4,983:
}), 500);
}
}
/**
* @return `true` the current revision has been checked by the user or `false` if not.
*/
get completed() {
var _a, _b;
return (_b = (_a = this.completedCheckbox) === null || _a === void 0 ? void 0 : _a.isSelected()) !== null && _b !== void 0 ? _b : false;
}
/**
* Set the value of the completed checkbox.
*
* @param value The new value
*/
set completed(value) {
var _a;
(_a = this.completedCheckbox) === null || _a === void 0 ? void 0 : _a.setSelected(value);
}
/**
* @return The hash used for autosave keys
*/
get autosaveHash() {
return `CASE--${this.uiRow.row.casePage.title.getPrefixedDb()}+PAGE--${this.uiRow.row.title.getPrefixedDb()}+REVISION--${this.revision.revid}`;
}
/**
Line 4,900 ⟶ 5,072:
const handleDiffToggle = (active) => {
this.diffToggle.setIndicator(active ? 'up' : 'down');
if (!active && this.diff.classList.contains('dp-cs-rev-diff--errored')) {
// Remake this.diff.classList.toggle('dp-cs-rev-diff--hidden', paneltrue);
return;
}
if (this.diff.classList.contains('dp-cs-rev-diff--errored')) {
// Error occurred previously, remake diff panel
this.diff = swapElements(this.diff, h_1("div", { class: "dp-cs-rev-diff" }));
}
else if (loaded) {
this.diff.classList.toggle('dp-cs-rev-diff--hidden', !activefalse);
}
if (active && !loaded) {
Line 4,944 ⟶ 5,120:
}
// Delete all no-change rows (gray rows)
//if !(tr.querySelector('td.diff-markers with a markercontext')) = no change for row{
if (!tr.querySelector('td.diff-marker[data-marker]')) {
removeElement(tr);
}
Line 5,142 ⟶ 5,317:
*/
class DeputyCCIStatusDropdown extends EventTarget {
/**
* @return The currently-selected status of this dropdown.
*/
get status() {
var _a, _b;
return (_b = (_a = this.dropdown.getMenu().findSelectedItem()) === null || _a === void 0 ? void 0 : _a.getData()) !== null && _b !== void 0 ? _b : null;
}
/**
* Sets the currently-selected status of this dropdown.
*/
set status(status) {
this.dropdown.getMenu().selectItemByData(status);
this.setOptionDisabled(ContributionSurveyRowStatus.Unknown, status !== ContributionSurveyRowStatus.Unknown, false);
this.refresh();
}
/**
* Create a new DeputyCCIStatusDropdown object.
Line 5,267 ⟶ 5,457:
unwrapWidget(this.dropdown.getMenu()).style.width = '20em';
});
}
/**
* @return The currently-selected status of this dropdown.
*/
get status() {
var _a, _b;
return (_b = (_a = this.dropdown.getMenu().findSelectedItem()) === null || _a === void 0 ? void 0 : _a.getData()) !== null && _b !== void 0 ? _b : null;
}
/**
* Sets the currently-selected status of this dropdown.
*/
set status(status) {
this.dropdown.getMenu().selectItemByData(status);
this.setOptionDisabled(ContributionSurveyRowStatus.Unknown, status !== ContributionSurveyRowStatus.Unknown, false);
this.refresh();
}
/**
Line 5,460 ⟶ 5,635:
*/
class DeputyContributionSurveyRow extends EventTarget {
/**
* Creates a new DeputyContributionSurveyRow object.
*
* @param row The contribution survey row data
* @param originalElement
* @param originalWikitext
* @param section The section that this row belongs to
*/
constructor(row, originalElement, originalWikitext, section) {
super();
/**
* The state of this element.
*/
this.state = DeputyContributionSurveyRowState.Loading;
/**
* Responder for session requests.
*/
this.statusRequestResponder = this.sendStatusResponse.bind(this);
this.nextRevisionRequestResponder = this.sendNextRevisionResponse.bind(this);
this.row = row;
this.originalElement = originalElement;
this.additionalComments = this.extractAdditionalComments();
this.originalWikitext = originalWikitext;
this.section = section;
}
/**
* @return `true` if:
Line 5,581 ⟶ 5,731:
if (unfinishedDiffs.length > 0) {
diffsText += unfinishedDiffs.map((v) => {
return mw.format(this.row.data.diffTemplate, String(v.revision.revid), v.revision.diffsize == null ?
// For whatever reason, diffsize is missing. Fall back to the text we had
// previously.
v.uiRow.row.data.revidText[v.revision.revid] :
String(v.revision.diffsize > 0 ?
'+' + v.revision.diffsize : v.revision.diffsize));
}).join('');
Line 5,653 ⟶ 5,803:
*/
get autosaveHash() {
return `CASE--${this.row.casePage.title.getPrefixedDb()}+H--${this.section.headingName}-${this.section.headingN}+PAGE--${this.row.title.getPrefixedDb()}`;
}
/**
* Creates a new DeputyContributionSurveyRow object.
*
* @param row The contribution survey row data
* @param originalElement
* @param originalWikitext
* @param section The section that this row belongs to
*/
constructor(row, originalElement, originalWikitext, section) {
super();
/**
* The state of this element.
*/
this.state = DeputyContributionSurveyRowState.Loading;
/**
* Responder for session requests.
*/
this.statusRequestResponder = this.sendStatusResponse.bind(this);
this.nextRevisionRequestResponder = this.sendNextRevisionResponse.bind(this);
this.row = row;
this.originalElement = originalElement;
this.additionalComments = this.extractAdditionalComments();
this.originalWikitext = originalWikitext;
this.section = section;
}
/**
Line 5,797 ⟶ 5,972:
*/
getSavedStatus() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
return (_a = yield window.deputy.storage.db.get('pageStatus', this.autosaveHash);) !== null && _a !== void 0 ? _a :
// Old hash (< v0.9.0)
yield window.deputy.storage.db.get('pageStatus', `CASE--${this.row.casePage.title.getPrefixedDb()}+PAGE--${this.row.title.getPrefixedDb()}`);
});
}
Line 6,080 ⟶ 6,258:
}
/**
*
* @param diffs
* @param content
Line 6,158 ⟶ 6,335:
}
/**
*
* @param event
*/
Line 6,495 ⟶ 6,671:
children.forEach(child => container.appendChild(child.cloneNode(true)));
return container;
}
 
/**
* What it says on the tin. Attempt to parse out a `title`, `diff`,
* or `oldid` from a URL. This is useful for converting diff URLs into actual
* diff information, and especially useful for {{copied}} templates.
*
* If diff parameters were not found (no `diff` or `oldid`), they will be `null`.
*
* @param url The URL to parse
* @return Parsed info: `diff` or `oldid` revision IDs, and/or the page title.
*/
function parseDiffUrl(url) {
if (typeof url === 'string') {
url = new URL(url);
}
// 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');
let title = url.searchParams.get('title');
// Attempt to get information from this URL.
tryConvert: {
if (title && oldid && diff) {
// Skip if there's nothing else we need to get.
break tryConvert;
}
// Attempt to get values from Special:Diff short-link
const diffSpecialPageCheck =
// eslint-disable-next-line security/detect-unsafe-regex
/\/wiki\/Special:Diff\/(prev|next|\d+)(?:\/(prev|next|\d+))?/i.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];
}
break tryConvert;
}
// Attempt to get values from Special:PermanentLink short-link
const permanentLinkCheck = /\/wiki\/Special:Perma(nent)?link\/(\d+)/i.exec(url.pathname);
if (permanentLinkCheck != null) {
oldid = permanentLinkCheck[2];
break tryConvert;
}
// Attempt to get values from article path with ?oldid or ?diff
// eslint-disable-next-line security/detect-non-literal-regexp
const articlePathRegex = new RegExp(mw.util.getUrl('(.*)'))
.exec(url.pathname);
if (articlePathRegex != null) {
title = decodeURIComponent(articlePathRegex[1]);
break tryConvert;
}
}
// Convert numbers to numbers
if (oldid != null && !isNaN(+oldid)) {
oldid = +oldid;
}
if (diff != null && !isNaN(+diff)) {
diff = +diff;
}
// Try to convert a page title
try {
title = new mw.Title(title).getPrefixedText();
}
catch (e) {
warn('Failed to normalize page title during diff URL conversion.');
}
return {
diff: diff,
oldid: oldid,
title: title
};
}
 
Line 6,504 ⟶ 6,758:
*/
class DeputyContributionSurveySection {
/**
* Creates a DeputyContributionSurveySection from a given heading.
*
* @param casePage
* @param heading
*/
constructor(casePage, heading) {
this.casePage = casePage;
this.heading = normalizeWikiHeading(heading);
this.sectionNodes = casePage.getContributionSurveySection(heading);
}
/**
* @return `true` if this section has been modified
Line 6,694 ⟶ 6,937:
get headingN() {
return sectionHeadingN(this.heading);
}
/**
* Creates a DeputyContributionSurveySection from a given heading.
*
* @param casePage
* @param heading
*/
constructor(casePage, heading) {
this.casePage = casePage;
this.heading = normalizeWikiHeading(heading);
this.sectionNodes = casePage.getContributionSurveySection(heading);
}
/**
Line 6,748 ⟶ 7,002:
// Avoid enlisting if the anchor can't be found (invalid row).
if (anchor) {
rowElements[const anchorLinkTarget = parseDiffUrl(new mw.TitleURL(anchor.innerText).getPrefixedTextgetAttribute('href')], =window.___location.href)).title;
if (!anchorLinkTarget) li;{
warn('Could not parse target of anchor', anchor);
}
else {
rowElements[new mw.Title(anchorLinkTarget).getPrefixedText()] =
li;
}
}
}
Line 6,771 ⟶ 7,031:
else {
// Element somehow not in list. Just keep line as-is.
warn(`Could not find row element for "${csr.title.getPrefixedText()}"`);
rowElement = line;
}
Line 6,844 ⟶ 7,105:
* Toggle section elements. Removes the section elements (but preservers them in
* `this.sectionElements`) if `false`, re-appends them to the DOM if `true`.
*
* @param toggle
*/
Line 7,008 ⟶ 7,270:
const sectionId = yield getSectionId(this.casePage.title, this.headingName, this.headingN);
yield this.save(sectionId).then((result) => __awaiter(this, void 0, void 0, function* () {
var _a, _b;
if (result) {
mw.notify(mw.msg('deputy.session.section.saved'));
Line 7,024 ⟶ 7,286:
const insertRef = (_a = heading.nextSibling) !== null && _a !== void 0 ? _a : null;
for (const child of Array.from(element.childNodes)) {
if (!this.casePage.isContributionSurveyHeading((_b = normalizeWikiHeading(child)), {
// We're using elements that aren't currently appended to the
// DOM, so we have to manually set the ceiling. Otherwise, we'll
// get the wrong element and ceiling checks will always be false.
element)) === null || _b === void 0 ? void 0 : _b.h)) {
heading.parentNode.insertBefore(child, insertRef);
this.sectionNodes.push(child);
Line 7,201 ⟶ 7,467:
*/
class DeputyRootSession {
/*
* =========================================================================
* INSTANCE AND ACTIVE SESSION FUNCTIONS
* =========================================================================
*/
/**
* @param session
* @param casePage
*/
constructor(session, casePage) {
/**
* Responder for session requests.
*/
this.sessionRequestResponder = this.sendSessionResponse.bind(this);
this.sessionStopResponder = this.handleStopRequest.bind(this);
this.session = session;
this.casePage = casePage;
}
/*
* =========================================================================
Line 7,369 ⟶ 7,617:
return __awaiter(this, void 0, void 0, function* () {
const casePage = _casePage !== null && _casePage !== void 0 ? _casePage : yield DeputyCasePage.build();
returnyield mw.loader.using(['oojs-ui-core', 'oojs-ui.styles.icons-content'], () => {
const firstHeading = casePage.findFirstContributionSurveyHeadingElement();
if (firstHeading) {
Line 7,465 ⟶ 7,713:
return (yield window.deputy.storage.setKV('session', session)) ? session : null;
});
}
/*
* =========================================================================
* INSTANCE AND ACTIVE SESSION FUNCTIONS
* =========================================================================
*/
/**
* @param session
* @param casePage
*/
constructor(session, casePage) {
/**
* Responder for session requests.
*/
this.sessionRequestResponder = this.sendSessionResponse.bind(this);
this.sessionStopResponder = this.handleStopRequest.bind(this);
this.session = session;
this.casePage = casePage;
}
/**
Line 7,703 ⟶ 7,969:
*/
class FakeDocument {
/**
* @param data Data to include in the iframe
*/
constructor(data) {
this.ready = false;
this.iframe = document.createElement('iframe');
this.iframe.style.display = 'none';
this.iframe.addEventListener('load', () => {
this.ready = true;
});
this.iframe.src = URL.createObjectURL(data instanceof Blob ? data : new Blob(data));
// Disables JavaScript, modals, popups, etc., but allows same-origin access.
this.iframe.setAttribute('sandbox', 'allow-same-origin');
document.getElementsByTagName('body')[0].appendChild(this.iframe);
}
/**
* Creates a fake document and waits for the `document` to be ready.
Line 7,735 ⟶ 7,986:
get document() {
return this.iframe.contentDocument;
}
/**
* @param data Data to include in the iframe
*/
constructor(data) {
this.ready = false;
this.iframe = document.createElement('iframe');
this.iframe.style.display = 'none';
this.iframe.addEventListener('load', () => {
this.ready = true;
});
this.iframe.src = URL.createObjectURL(data instanceof Blob ? data : new Blob(data));
// Disables JavaScript, modals, popups, etc., but allows same-origin access.
this.iframe.setAttribute('sandbox', 'allow-same-origin');
document.getElementsByTagName('body')[0].appendChild(this.iframe);
}
/**
Line 8,047 ⟶ 8,313:
constructor(options) {
var _a;
this.state = DeputyPageToolbarState.Open;
this.instanceId = generateId();
this.revisionStatusUpdateListener = this.onRevisionStatusUpdate.bind(this);
Line 8,053 ⟶ 8,320:
this.revision = (_a = options.revision) !== null && _a !== void 0 ? _a : mw.config.get('wgRevisionId');
}
this.state = window.deputy.config.cci.toolbarInitialState.get();
this.runAsyncJobs();
}
Line 8,298 ⟶ 8,566:
this.renderMenu(mw.msg('deputy.session.page.analysis'), deputyPageAnalysisOptions()),
this.renderMenu(mw.msg('deputy.session.page.tools'), deputyPageTools())));
}
/**
* Rends the page toolbar actions and main section, if the dropdown is open.
*/
renderOpen() {
return [
h_1("div", { class: "dp-pageToolbar-actions" },
h_1("div", { class: "dp-pageToolbar-close", role: "button", title: mw.msg('deputy.session.page.close'), onClick: () => this.setState(DeputyPageToolbarState.Hidden) }),
h_1("div", { class: "dp-pageToolbar-collapse", role: "button", title: mw.msg('deputy.session.page.collapse'), onClick: () => this.setState(DeputyPageToolbarState.Collapsed) })),
h_1("div", { class: "dp-pageToolbar-main" },
this.renderStatusDropdown(),
this.renderCaseInfo(),
this.renderRevisionInfo(),
this.revisionNavigationSection =
this.renderRevisionNavigationButtons(),
this.renderMenus())
];
}
/**
* Renders the collapsed toolbar button.
*
* @return The render button, to be included in the main toolbar.
*/
renderCollapsed() {
return h_1("div", { class: "dp-pageToolbar-collapsed", role: "button", title: mw.msg('deputy.session.page.expand'), onClick: () => this.setState(DeputyPageToolbarState.Open) });
}
/**
Line 8,303 ⟶ 8,596:
*/
render() {
console.log(this.state);
if (this.state === DeputyPageToolbarState.Hidden) {
const portletLink = mw.util.addPortletLink('p-tb', '#', mw.msg('deputy.session.page.open'), 'pt-dp-pt', mw.msg('deputy.session.page.open.tooltip'));
portletLink.querySelector('a').addEventListener('click', (event) => {
event.preventDefault();
this.setState(DeputyPageToolbarState.Open);
return false;
});
// Placeholder element
return this.element = h_1("div", { class: "deputy" });
}
else {
const toolbar = document.getElementById('pt-dp-pt');
if (toolbar) {
removeElement(toolbar);
}
}
return this.element = h_1("div", { class: "deputy dp-pageToolbar" },
this.renderStatusDropdownstate === DeputyPageToolbarState.Open && this.renderOpen(),
this.renderCaseInfostate === DeputyPageToolbarState.Collapsed && this.renderCollapsed(),);
this.renderRevisionInfo(),
this.revisionNavigationSection =
this.renderRevisionNavigationButtons(),
this.renderMenus());
}
/**
Line 8,322 ⟶ 8,628:
(_c = this.previousRevisionButton) === null || _c === void 0 ? void 0 : _c.setDisabled(disabled);
(_d = this.nextRevisionButton) === null || _d === void 0 ? void 0 : _d.setDisabled(disabled);
}
/**
* Sets the display state of the toolbar. This will also set the
* initial state configuration option for the user.
*
* @param state
*/
setState(state) {
this.state = state;
window.deputy.config.cci.toolbarInitialState.set(state);
window.deputy.config.save();
swapElements(this.element, this.render());
}
/**
Line 8,381 ⟶ 8,699:
}
/**
*
* @param data
*/
Line 8,945 ⟶ 9,262:
}
return new InternalPageLatestRevisionGetButton(config);
}
 
/**
* What it says on the tin. Attempt to parse out a `title`, `diff`,
* or `oldid` from a URL. This is useful for converting diff URLs into actual
* diff information, and especially useful for {{copied}} templates.
*
* @param url The URL to parse
* @return Parsed info: `diff` or `oldid` revision IDs, and/or the page title.
*/
function parseDiffUrl(url) {
if (typeof url === 'string') {
url = new URL(url);
}
// 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');
let title = url.searchParams.get('title');
// Attempt to get information from this URL.
tryConvert: {
if (title && oldid && diff) {
// Skip if there's nothing else we need to get.
break tryConvert;
}
// Attempt to get values from Special:Diff short-link
const diffSpecialPageCheck =
// eslint-disable-next-line security/detect-unsafe-regex
/\/wiki\/Special:Diff\/(prev|next|\d+)(?:\/(prev|next|\d+))?/i.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];
}
break tryConvert;
}
// Attempt to get values from Special:PermanentLink short-link
const permanentLinkCheck = /\/wiki\/Special:Perma(nent)?link\/(\d+)/i.exec(url.pathname);
if (permanentLinkCheck != null) {
oldid = permanentLinkCheck[2];
break tryConvert;
}
// Attempt to get values from article path with ?oldid or ?diff
// eslint-disable-next-line security/detect-non-literal-regexp
const articlePathRegex = new RegExp(mw.util.getUrl('(.*)'))
.exec(url.pathname);
if (articlePathRegex != null) {
title = articlePathRegex[1];
break tryConvert;
}
}
// Convert numbers to numbers
if (oldid != null && !isNaN(+oldid)) {
oldid = +oldid;
}
if (diff != null && !isNaN(+diff)) {
diff = +diff;
}
// Try to convert a page title
try {
title = new mw.Title(title).getPrefixedText();
}
catch (e) {
warn('Failed to normalize page title during diff URL conversion.');
}
return {
diff: diff,
oldid: oldid,
title: title
};
}
 
Line 9,519 ⟶ 9,760:
*/
class AttributionNoticeRow {
/**
*
* @param parent
*/
constructor(parent) {
this._parent = parent;
const r = window.btoa((Math.random() * 10000).toString()).slice(0, 6);
this.name = this.parent.name + '#' + r;
this.id = window.btoa(parent.node.getTarget().wt) + '-' + this.name;
}
/**
* @return The parent of this attribution notice row.
Line 9,545 ⟶ 9,776:
newParent.addRow(this);
this._parent = newParent;
}
/**
*
* @param parent
*/
constructor(parent) {
this._parent = parent;
const r = window.btoa((Math.random() * 10000).toString()).slice(0, 6);
this.name = this.parent.name + '#' + r;
this.id = window.btoa(parent.node.getTarget().wt) + '-' + this.name;
}
/**
Line 9,995 ⟶ 10,236:
*/
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 = window.btoa(node.getTarget().wt) + '-' + this.name;
this.parse();
}
/**
* @return The ParsoidDocument handling this notice (specifically its node).
Line 10,027 ⟶ 10,254:
get i() {
return this.node.i;
}
/**
* 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 = window.btoa(node.getTarget().wt) + '-' + this.name;
this.parse();
}
/**
Line 12,186 ⟶ 12,427:
value: this.translatedPageTemplate.version,
placeholder: mw.msg('deputy.ante.translatedPage.version.placeholder'),
validate: /^\d+*$/gi
}),
insertversion: new OO.ui.TextInputWidget({
value: this.translatedPageTemplate.insertversion,
placeholder: mw.msg('deputy.ante.translatedPage.insertversion.placeholder'),
validate: /^[\d/]+*$/gi
}),
section: new OO.ui.TextInputWidget({
Line 14,005 ⟶ 14,246:
}
}) :
// TODO: i18n
OO.ui.alert('There are no templates to merge.');
});
Line 14,173 ⟶ 14,415:
if (unwrapWidget(this.layout)
.querySelector('.oo-ui-flaggedElement-invalid') != null) {
return new OO.ui.Processalert(mw.msg('deputy.ante.invalid')) => {;
return OO.ui.alert(mw.msg('deputy.ante.invalid'))process;
});
}
// Saves the page.
Line 14,490 ⟶ 14,731:
*/
class DeputyModule {
/**
*
* @param deputy
*/
constructor(deputy) {
this.deputy = deputy;
}
/**
* @return The responsible window manager for this class.
Line 14,533 ⟶ 14,767:
get wikiConfig() {
return this.deputy ? this.deputy.wikiConfig : this._wikiConfig;
}
/**
*
* @param deputy
*/
constructor(deputy) {
this.deputy = deputy;
}
/**
Line 14,760 ⟶ 15,001:
];
 
var deputyStyles = "/*=============================================================================== GLOBAL DEPUTY CLASSES===============================================================================*/* > .deputy.dp-heading {position: absolute;opacity: 0;pointer-events: none;}*:hover > .deputy.dp-heading:not(.dp-heading--active) {opacity: 1;pointer-events: all;}.dp-loadingDots-1, .dp-loadingDots-2, .dp-loadingDots-3 {display: inline-block;margin: 0.1em 0.6em 0.1em 0.1em;width: 0.8em;height: 0.8em;background-color: rgba(0, 0, 0, 50%);animation: dp-loadingDots linear 3s infinite;border-radius: 50%;}@keyframes dp-loadingDots {0% {background-color: rgba(0, 0, 0, 10%);}16% {background-color: rgba(0, 0, 0, 40%);}32% {background-color: rgba(0, 0, 0, 10%);}100% {background-color: rgba(0, 0, 0, 10%);}}.dp-loadingDots-1 {animation-delay: -1s;}.dp-loadingDots-2 {animation-delay: -0.5s;}#mw-content-text.dp-reloading {opacity: 0.2;pointer-events: none;}p.dp-messageWidget-message {margin: 0 0 0.5em 0;}.dp-messageWidget-actions .oo-ui-buttonElement {margin-top: 0;}.oo-ui-image-destructive.oo-ui-icon-checkAll, .oo-ui-image-destructive.mw-ui-icon-checkAll::before {background-image: url(\"data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2220%22 height=%2220%22 viewBox=%220 0 20 20%22%3E%3Ctitle%3E check all %3C/title%3E%3Cpath fill=%22%23d73333%22 d=%22m.29 12.71 1.42-1.42 2.22 2.22 8.3-10.14 1.54 1.26-9.7 11.86zM12 10h5v2h-5zm-3 4h5v2H9zm6-8h5v2h-5z%22/%3E%3C/svg%3E\");}/*=============================================================================== DEPUTY REVIEW DIALOG (DeputyReviewDialog)===============================================================================*/.dp-review-progress {flex: 1;width: 60%;min-width: 300px;}/*=============================================================================== DEPUTY ENTRY POINTS (DeputyCCISessionStartLink, etc.)===============================================================================*/.deputy.dp-sessionStarter {font-size: small;font-weight: normal;margin-left: 0.25em;vertical-align: baseline;line-height: 1em;font-family: sans-serif;}.deputy.dp-sessionStarter::before {content: '\\200B';}.mw-content-ltr .deputy.dp-sessionStarter .dp-sessionStarter-bracket:first-of-type,.mw-content-rtl .deputy.dp-sessionStarter .dp-sessionStarter-bracket:not(:first-of-type) {margin-right: 0.25em;color: #54595d;}.client-js .deputy.dp-sessionStarter .dp-sessionStarter-bracket:first-of-type,.client-js .deputy.dp-sessionStarter .dp-sessionStarter-bracket:not(:first-of-type) {margin-left: 0.25em;color: #54595d}.dp-cs-section-add {position: absolute;top: 0;/* -1.6em derived from MediaWiki list margins. */left: -1.6em;width: calc(100% + 1.6em);background-color: rgba(255, 255, 255, 75%);display: flex;justify-content: center;align-items: center;}.dp-cs-section-add .dp-cs-section-addButton {opacity: 0;transition: opacity 0.2s ease-in-out;}.dp-cs-section-add:hover .dp-cs-section-addButton {opacity: 1;}/*=============================================================================== DEPUTY CONTRIBUTION SURVEY SECTION===============================================================================*/.dp-cs-section-archived .dp-cs-row-content {background-color: rgba(255, 0, 0, 6%);}.dp-cs-session-notice {margin-top: 8px;position: sticky;top: 8px;z-index: 50;}.skin-vector-2022.vector-sticky-header-visible .dp-cs-session-notice {top: calc(3.125rem + 8px);}.dp-cs-section-footer {position: relative;padding: 8px;}.dp-cs-section-danger--separator {flex-basis: 100%;margin: 8px 0;border-bottom: 1px solid #d73333;color: #d73333;font-weight: bold;font-size: 0.7em;text-align: right;text-transform: uppercase;line-height: 0.7em;padding-bottom: 0.2em;}.dp-cs-section-closing {margin: 1em 1.75em;}.dp-cs-section-progress {margin-top: 8px;max-height: 0;transition: max-height 0.2s ease-in-out;display: flex;justify-content: center;align-items: center;overflow: hidden;}.dp-cs-section-progress.active {max-height: 50px;}.dp-cs-section-progress .oo-ui-progressBarWidget {flex: 1}.dp-cs-section-closingCommentsField {margin-top: 8px;}.dp-cs-extraneous {border: 1px solid rgba(0, 159, 255, 40%);background-color: rgba(0, 159, 255, 10%);margin-bottom: 8px;padding: 16px;}.dp-cs-extraneous > dl {margin-left: -1.6em;}.dp-cs-extraneous > :first-child {margin-top: 0 !important;}.dp-cs-extraneous > :last-child {margin-bottom: 0 !important;}.dp-cs-section-archived-warn, .dp-cs-row, .dp-cs-extraneous {margin-bottom: 8px;}.dp-cs-row .dp--loadingDots {display: flex;align-items: center;justify-content: center;padding: 0.4em;}.dp-cs-row-status {max-width: 5.4em;}.dp-cs-row-status .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {width: 0;opacity: 0;}.dp-cs-row-status .dp-cs-row-status--unknown:not(.oo-ui-optionWidget-selected) {display: none;}.dp-cs-row-head > * {vertical-align: middle;}.dp-cs-row-comments {padding: 16px;background-color: rgba(0, 159, 255, 10%);margin: 4px 0;}.dp-cs-row-comments > b {letter-spacing: 0.1em;font-weight: bold;text-transform: uppercase;color: rgba(0, 0, 0, 0.5);}.dp-cs-row-comments hr {border-color: rgb(0, 31, 51);}body.mediawiki.ltr .dp-cs-row-head > :not(:first-child):not(:last-child),body.mediawiki.ltr .dp-cs-row-head > :not(:first-child):not(:last-child) {margin-right: 16px;}body.mediawiki.rtl .dp-cs-row-head > :not(:first-child):not(:last-child),body.mediawiki.rtl .dp-cs-row-head > :not(:first-child):not(:last-child) {margin-left: 16px;}.dp-cs-row-links {margin-right: 0 !important;}.dp-cs-row-links > :not(:last-child) {margin-right: 8px !important;}.dp-cs-row-title {font-weight: bold;font-size: 1.2em;vertical-align: middle;}.dp-cs-row-details {color: #4a5054;font-weight: bold;}.dp-cs-row-toggle .oo-ui-iconElement-icon {background-size: 1em;}.dp-cs-row-toggle .oo-ui-buttonElement-button {border-radius: 50%;}.dp-cs-row .history-user,.dp-cs-row :not(.newpage) + .mw-changeslist-date {margin-left: 0.4em;margin-right: 0.2em;}.dp-cs-row .newpage {margin-left: 0.4em;}.dp-cs-row-content {padding: 16px;background-color: rgba(0, 0, 0, 6%);margin: 4px 0;}.dp-cs-row-content.dp-cs-row-content-empty {display: none !important;}.dp-cs-row-unfinishedWarning {margin-bottom: 8px;}.dp-cs-section-unfinishedWarning {margin-top: 8px;}.dp-cs-row-closeComments {font-family: monospace, monospace;font-size: small;}.dp-cs-row-closeComments:not(:last-child) {margin-bottom: 8px;}.dp-cs-row-finished .oo-ui-fieldLayout:first-child {margin-top: 0;}.dp-cs-row-finished .oo-ui-fieldLayout {margin-top: 8px;}.dp-cs-row-revisions .mw-tag-markers .mw-tag-marker:not(:first-child),.dp-cs-row-detail:not(:first-child) {margin-left: 0.2em;}.dp-cs-rev-checkbox {margin-right: 4px;}.dp-cs-rev-toggleDiff {vertical-align: baseline;margin-right: 4px;}.dp-cs-rev-diff {background-color: white;position: relative;}.dp-cs-rev-diff--loaded {margin: 4px 0;padding: 8px 14px;}.dp-cs-rev-diff--hidden {display: none;}.dp-cs-rev-toggleDiff > .oo-ui-buttonElement-button {padding: 0;min-height: 1em;background-color: unset !important;}.dp-cs-rev-toggleDiff .oo-ui-indicatorElement-indicator {top: -1px;}/*=============================================================================== DEPUTY PAGE TOOLBAR===============================================================================*/.dp-pageToolbar {position: fixed;bottom: 8px;left: 8px;z-index: 100;padding: 8px;background-color: #fff;border: 1px solid gray;font-size: 0.9rem;display: flex;}.dp-pageToolbar .dp-pageToolbar-main {padding: 8px;display: flex;align-items: center;}.dp-pageToolbar-actions {width: 12px;display: flex;flex-direction: column;font-size: 12px;line-height: 1em;}.dp-pageToolbar-close {cursor: pointer;height: 12px;text-align: center;background-color: rgba(0, 0, 0, 0.25);}.dp-pageToolbar-close:hover {transition: background-color 0.1s ease-in-out;background-color: rgba(0, 0, 0, 0.4);}.dp-pageToolbar-close::before {content: '×';vertical-align: middle;position: relative;right: 1px;}.dp-pageToolbar-collapse {cursor: pointer;flex: 1;background-color: rgba(0, 0, 0, 0.125);text-align: center;writing-mode: vertical-rl;position: relative;}.dp-pageToolbar-collapse:hover {transition: background-color 0.1s ease-in-out;background-color: rgba(0, 0, 0, 0.25);}.dp-pageToolbar-collapse::before {content: '»';position: absolute;vertical-align: middle;width: 12px;left: 0;bottom: 2px;}.dp-pageToolbar-collapsed {cursor: pointer;width: 32px;height: 32px;/* logo-white.svg */background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMDgwIDEwODAiIHdpZHRoPSIxMDgwIiBoZWlnaHQ9IjEwODAiPjxkZWZzPjxzdHlsZT4uY2xzLTF7ZmlsbDojMDI5N2IxO308L3N0eWxlPjwvZGVmcz48cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik04NzcuNTQsNDM4LjY5YTQzNy41NCw0MzcuNTQsMCwwLDEtODQuMzgsMjU4Ljc4bDI2Ny4wNiwyNjcuMjJhNjcuNTYsNjcuNTYsMCwxLDEtOTUuNTYsOTUuNTRMNjk3LjYsNzkzYTQzNi4yNyw0MzYuMjcsMCwwLDEtMjU4LjgzLDg0LjM2QzE5Ni4zOSw4NzcuMzcsMCw2ODEsMCw0MzguNjlTMTk2LjM5LDAsNDM4Ljc3LDAsODc3LjU0LDE5Ni4zNSw4NzcuNTQsNDM4LjY5Wk00MzguNzcsNzQyLjM5YzE2Ny43LDAsMzAzLjc3LTEzNiwzMDMuNzctMzAzLjdTNjA2LjQ3LDEzNSw0MzguNzcsMTM1LDEzNSwyNzEsMTM1LDQzOC42OSwyNzEuMDcsNzQyLjM5LDQzOC43Nyw3NDIuMzlaIi8+PHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNTI0LjgzLDUxNS4yOGExMDQuODUsMTA0Ljg1LDAsMCwxLTE0OS40MSwwYy00MS41LTQxLjg3LTQxLjUtMTEwLDAtMTUxLjk1YTEwNC45MiwxMDQuOTIsMCwwLDEsMTQ5LjUxLjA4QTUwLjc2LDUwLjc2LDAsMCwwLDU5NywyOTEuOWEyMDYuNDQsMjA2LjQ0LDAsMCwwLTI5My41NSwwYy04MC41Nyw4MS4yNy04MC41NywyMTMuNTMsMCwyOTQuODNBMjA1LjI1LDIwNS4yNSwwLDAsMCw0NTAuMTgsNjQ4aDBBMjA1LjMsMjA1LjMsMCwwLDAsNTk3LDU4Ni43MWE1MC43Nyw1MC43NywwLDAsMC03Mi4xNi03MS40M1oiLz48L3N2Zz4=') no-repeat center;background-size: 24px;}@media only screen and (max-width: 768px) {.dp-pageToolbar {flex-wrap: wrap;bottom: 0;left: 0;border-left: 0;border-bottom: 0;border-right: 0;width: 100%;}}.dp-pt-section {display: inline-block;white-space: nowrap;}.dp-pt-section .oo-ui-popupWidget-popup {/** Avoid preventing line breaks in popups */white-space: initial;}.dp-pt-section + .dp-pt-section {/* TODO: Recheck RTL compatibility */margin-left: 16px;padding-left: 16px;border-left: 1px solid gray;}.dp-pt-section:last-child {/* TODO: Recheck RTL compatibility */margin-right: 8px;}.dp-pt-section-label {font-weight: bold;font-size: 0.6rem;color: #4a5054;text-transform: uppercase;}.dp-pt-section-content .oo-ui-buttonElement:last-child {margin-right: 0;}.dp-pt-caseInfo {font-weight: bold;font-size: 1.3rem;pointer-events: none;}.dp-pt-missingRevision {white-space: normal;}.dp-pageToolbar .dp-cs-row-status {width: 5.4em;}.dp-pt-menu .oo-ui-menuSelectWidget {min-width: 300px;}.dp-pt-menu .oo-ui-menuOptionWidget {padding-top: 8px;padding-bottom: 8px;}";
 
var deputyCoreEnglish = {
Line 14,847 ⟶ 15,088:
"deputy.session.revision.new.tooltip": "This edit created a new page.",
"deputy.session.revision.missing": "The revision [[Special:Diff/$1|$1]] could not be found. It may have been deleted or suppressed.",
"deputy.session.page.close": "Minimize the toolbar to the sidebar",
"deputy.session.page.collapse": "Collapse the toolbar",
"deputy.session.page.expand": "Expand the toolbar",
"deputy.session.page.open": "Open Deputy toolbar",
"deputy.session.page.open.tooltip": "Open the Deputy page toolbar",
"deputy.session.page.diff.previous": "Navigate to the previous unassessed revision",
"deputy.session.page.diff.next": "Navigate to the next unassessed revision",
Line 14,986 ⟶ 15,232:
*/
class CopyrightProblemsPage {
/**
* Private constructor. Use `get` instead to avoid cache misses.
*
* @param listingPage
* @param revid
*/
constructor(listingPage, revid) {
this.title = listingPage;
this.main = CopyrightProblemsPage.rootPage.getPrefixedText() ===
listingPage.getPrefixedText();
this.revid = revid;
}
/**
* @return See {@link WikiConfiguration#ia}.rootPage.
Line 15,047 ⟶ 15,281:
return page;
}
}
/**
* Private constructor. Use `get` instead to avoid cache misses.
*
* @param listingPage
* @param revid
*/
constructor(listingPage, revid) {
this.title = listingPage;
this.main = CopyrightProblemsPage.rootPage.getPrefixedText() ===
listingPage.getPrefixedText();
this.revid = revid;
}
/**
Line 15,290 ⟶ 15,536:
*/
class CopyrightProblemsListing {
/**
* Creates a new listing object.
*
* @param data Additional data about the page
* @param listingPage The page that this listing is on. This is not necessarily the page that
* the listing's wikitext is on, nor is it necessarily the root page.
* @param i A discriminator used to avoid collisions when a page is listed multiple times.
*/
constructor(data, listingPage, i = 1) {
this.listingPage = listingPage !== null && listingPage !== void 0 ? listingPage : CopyrightProblemsPage.get(data.listingPage);
this.i = Math.max(1, i); // Ensures no value below 1.
this.basic = data.basic;
this.title = data.title;
this.element = data.element;
if (data.basic === false) {
this.id = data.id;
this.anchor = data.anchor;
this.plainlinks = data.plainlinks;
}
}
/**
* Responsible for determining listings on a page. This method allows for full-metadata
Line 15,502 ⟶ 15,728:
get anchorId() {
return this.id + (this.i > 1 ? `-${this.i}` : '');
}
/**
* Creates a new listing object.
*
* @param data Additional data about the page
* @param listingPage The page that this listing is on. This is not necessarily the page that
* the listing's wikitext is on, nor is it necessarily the root page.
* @param i A discriminator used to avoid collisions when a page is listed multiple times.
*/
constructor(data, listingPage, i = 1) {
this.listingPage = listingPage !== null && listingPage !== void 0 ? listingPage : CopyrightProblemsPage.get(data.listingPage);
this.i = Math.max(1, i); // Ensures no value below 1.
this.basic = data.basic;
this.title = data.title;
this.element = data.element;
if (data.basic === false) {
this.id = data.id;
this.anchor = data.anchor;
this.plainlinks = data.plainlinks;
}
}
/**
Line 15,623 ⟶ 15,869:
id: this.id,
title: {
namespace: this.title.namespacegetNamespaceId(),
title: this.title.titlegetMainText(),
fragment: this.title.getFragment()
},
listingPage: {
namespace: this.listingPage.title.namespacegetNamespaceId(),
title: this.listingPage.title.titlegetMainText(),
fragment: this.listingPage.title.getFragment()
},
Line 15,642 ⟶ 15,888:
*/
class ListingResponsePanel extends EventTarget {
/**
*
* @param originLink
* @param listing
*/
constructor(originLink, listing) {
super();
// TODO: types-mediawiki limitation
this.reloadPreviewThrottled = mw.util.throttle(this.reloadPreview, 500);
this.originLink = originLink;
this.listing = listing;
}
/**
* @return A set of possible copyright problems responses.
Line 15,661 ⟶ 15,895:
}
/**
*
* @param response
* @param locale
Line 15,675 ⟶ 15,908:
response.label :
((_c = (_b = response.label[locale]) !== null && _b !== void 0 ? _b : response.label[locale1]) !== null && _c !== void 0 ? _c : response.label[0]);
}
/**
* @param originLink
* @param listing
*/
constructor(originLink, listing) {
super();
// TODO: types-mediawiki limitation
this.reloadPreviewThrottled = mw.util.throttle(this.reloadPreview, 500);
this.originLink = originLink;
this.listing = listing;
}
/**
Line 15,849 ⟶ 16,093:
}
return this.prefill ?
mw.format(this.prefill.template, this.listing.title.getPrefixedText(), (_c = this.comments) !== null && _c !== void 0 ? _c : '') :
this.comments;
}
Line 16,311 ⟶ 16,555:
if (this.data.entirePage) {
finalPageContent = copyvioWikitext + '\n' + this.wikitext;
if (wikiConfig.entirePageAppendBottom.get()) {
finalPageContent += '\n' + wikiConfig.hideTemplateBottom.get();
}
}
else {
Line 16,853 ⟶ 17,100:
}
 
var iaStyles = ".ia-listing-action {display: inline-block;}body.ltr .ia-listing-action {margin-left: 0.5em;}body.ltr .ia-listing-action--bracket:first-child,body.rtl .ia-listing-action--bracket:first-child {margin-right: 0.2em;}body.rtl .ia-listing-action {margin-right: 0.5em;}body.ltr .ia-listing-action--bracket:last-child,body.rtl .ia-listing-action--bracket:last-child {margin-left: 0.2em;}.ia-listing-action--link[disabled] {color: gray;pointer-events: none;}@keyframes ia-newResponse {from { background-color: #ffe29e }to { background-color: rgba( 0, 0, 0, 0 ); }}.ia-newResponse {animation: ia-newResponse 2s ease-out;}.ia-listing-response, .ia-listing-new {max-width: 50em;}.ia-listing-response {margin-top: 0.4em;margin-bottom: 0.4em;}.mw-content-ltr .ia-listing-response, .mw-content-rtl .mw-content-ltr .ia-listing-response {margin-left: 1.6em;margin-right: 0;}.mw-content-rtl .ia-listing-response, .mw-content-ltr .mw-content-rtl .ia-listing-response {margin-left: 0;margin-right: 1.6em;}.ia-listing-response > div {margin-bottom: 8px;}.ia-listing--preview {box-sizing: border-box;background: #f6f6f6;padding: 0.5em 1em;overflow: hidden;}/** \"Preview\" */.ia-listing--preview::before {content: attr(data-label);color: #808080;display: block;margin-bottom: 0.2em;}.ia-listing-response--submit {text-align: right;}/** * NEW LISTINGS */.ia-listing-newPanel {margin-top: 0.5em;}.ia-listing-new {display: flex;align-items: end;margin-top: 0.5em;padding: 1em;}.ia-listing-new--field {flex: 1;}.ia-listing-new--cancel {margin-left: 0.5em;}.ia-batchListing-new {padding: 1em;max-width: 50em;}.ia-batchListing-new--buttons {display: flex;justify-content: end;margin-top: 12px;}.ia-batchListing-new .ia-listing--preview {margin-top: 12px;}/** * REPORTING DIALOG */.ia-report-intro {font-size: 0.8rem;padding-bottom: 12px;border-bottom: 1px solid gray;margin-bottom: 12px;}.ia-report-intro b {display: block;font-size: 1rem;}.ia-report-submit {padding-top: 12px;display: flex;justify-content: flex-end;}/** * COPYVIO PREVIEWS */.copyvio.deputy-show {display: inherit !important;border: 0.2em solid #f88;padding: 1em;}.dp-hiddenVio {display: flex;flex-direction: row;margin: 1em 0;}.dp-hiddenVio-message {flex: 1;}.dp-hiddenVio-actions {flex: 0;margin-left: 1em;display: flex;flex-direction: column;justify-content: center;}";
 
/**
Line 16,860 ⟶ 17,107:
class HiddenViolationUI {
/**
*
* @param el
*/
Line 16,874 ⟶ 17,120:
attach() {
this.vioElement.insertAdjacentElement('beforebegin', h_1("div", { class: "deputy dp-hiddenVio" },
h_1("div", null{ class: "dp-hiddenVio-message" }, this.renderMessage()),
h_1("div", { class: "dp-hiddenVio-actions" }, this.renderButton())));
this.vioElement.classList.add('deputy-upgraded');
Line 16,964 ⟶ 17,210:
// Query parameter-based autostart disable (i.e. don't start if param exists)
if (!/[?&]ia-autostart(=(0|no|false|off)?(&|$)|$)/.test(window.___location.search)) {
returnyield mw.loader.using(InfringementAssistant.dependencies, () => __awaiter(this, void 0, void 0, function* () {
yield this.init();
}));
return true;
}
return true;
Line 17,009 ⟶ 17,256:
}
/**
* Opens the workflow dialog.
*/
openWorkflowDialog() {
return __awaiter(this, void 0, void 0, function* () {
returnyield mw.loader.using(InfringementAssistant.dependencies, () => __awaiter(this, void 0, void 0, function* () {
yield DeputyLanguage.loadMomentLocale();
if (!this.dialog) {
Line 17,052 ⟶ 17,299:
var _a;
const page = normalizeTitle();
if (page.namespacegetNamespaceId() === nsId('special') ||
page.namespacegetNamespaceId() === nsId('media')) {
// Don't save virtual namespaces.
return;
Line 17,475 ⟶ 17,722:
*/
class Deputy {
/**
* @return An OOUI window manager
*/
get windowManager() {
if (!this._windowManager) {
this._windowManager = new OO.ui.WindowManager();
document.body.appendChild(unwrapWidget(this._windowManager));
}
return this._windowManager;
}
/**
* Initialize Deputy. This static function attaches Deputy to the `window.deputy`
* object and initializes that instance.
*/
static init() {
return __awaiter(this, void 0, void 0, function* () {
Deputy.instance = new Deputy();
window.deputy = Deputy.instance;
return window.deputy.init();
});
}
/**
* Private constructor. To access Deputy, use `window.deputy` or Deputy.instance.
Line 17,513 ⟶ 17,781:
this.ia = new InfringementAssistant(this);
/* ignored */
}
/**
* @return An OOUI window manager
*/
get windowManager() {
if (!this._windowManager) {
this._windowManager = new OO.ui.WindowManager();
document.body.appendChild(unwrapWidget(this._windowManager));
}
return this._windowManager;
}
/**
* Initialize Deputy. This static function attaches Deputy to the `window.deputy`
* object and initializes that instance.
*/
static init() {
return __awaiter(this, void 0, void 0, function* () {
Deputy.instance = new Deputy();
window.deputy = Deputy.instance;
return window.deputy.init();
});
}
/**