This run took 97 seconds.
From f0c6c54c98623e394760b1476804c4fd6074c993 Mon Sep 17 00:00:00 2001
From: libraryupgrader <tools.libraryupgrader@tools.wmflabs.org>
Date: Tue, 18 Nov 2025 18:52:13 +0000
Subject: [PATCH] build: Updating npm dependencies
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* eslint-config-wikimedia: 0.32.1 → 0.32.2
* js-yaml: 3.14.1, 4.1.0 → 3.14.2, 4.1.1
* https://github.com/advisories/GHSA-mh29-5h37-fv8m
Change-Id: I1bad3f404b4f0eb1f17139277c6b665cc6d2fa08
---
package-lock.json | 62 +++++++++++++++++++++++------------------------
package.json | 2 +-
2 files changed, 32 insertions(+), 32 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 00d6548..5671c6f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,7 @@
"": {
"name": "UploadWizard",
"devDependencies": {
- "eslint-config-wikimedia": "0.32.1",
+ "eslint-config-wikimedia": "0.32.2",
"grunt": "1.6.1",
"grunt-banana-checker": "0.13.0",
"grunt-contrib-watch": "1.1.0",
@@ -294,9 +294,9 @@
"dev": true
},
"node_modules/@eslint/eslintrc/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
@@ -1339,9 +1339,9 @@
"dev": true
},
"node_modules/cosmiconfig/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
@@ -1783,9 +1783,9 @@
}
},
"node_modules/eslint-config-wikimedia": {
- "version": "0.32.1",
- "resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.1.tgz",
- "integrity": "sha512-gPvhyVFNlpKFOcJfoVTNlzg3A0b6qjhAbjjBIJ9xp5m+om0oqix5gkqIIEav5BaGxdDxYNmrY4ge3DAPP3u/lg==",
+ "version": "0.32.2",
+ "resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.2.tgz",
+ "integrity": "sha512-vAGz50AJPk23qQ701sL4tAgaF8FEAkP/E3kgojSTVrGgmDqjnRvq8z3EItDNI/EAkb5Ys15WPPFsoBH8YhTdSg==",
"dev": true,
"dependencies": {
"@stylistic/eslint-plugin": "^3.1.0",
@@ -2262,9 +2262,9 @@
"dev": true
},
"node_modules/eslint/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
@@ -3389,9 +3389,9 @@
"dev": true
},
"node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
"dependencies": {
"argparse": "^1.0.7",
@@ -6015,9 +6015,9 @@
"dev": true
},
"js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"requires": {
"argparse": "^2.0.1"
@@ -6732,9 +6732,9 @@
"dev": true
},
"js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"requires": {
"argparse": "^2.0.1"
@@ -7067,9 +7067,9 @@
"dev": true
},
"js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"requires": {
"argparse": "^2.0.1"
@@ -7096,9 +7096,9 @@
}
},
"eslint-config-wikimedia": {
- "version": "0.32.1",
- "resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.1.tgz",
- "integrity": "sha512-gPvhyVFNlpKFOcJfoVTNlzg3A0b6qjhAbjjBIJ9xp5m+om0oqix5gkqIIEav5BaGxdDxYNmrY4ge3DAPP3u/lg==",
+ "version": "0.32.2",
+ "resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.2.tgz",
+ "integrity": "sha512-vAGz50AJPk23qQ701sL4tAgaF8FEAkP/E3kgojSTVrGgmDqjnRvq8z3EItDNI/EAkb5Ys15WPPFsoBH8YhTdSg==",
"dev": true,
"requires": {
"@stylistic/eslint-plugin": "^3.1.0",
@@ -8237,9 +8237,9 @@
"dev": true
},
"js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
diff --git a/package.json b/package.json
index 6baa68b..2792669 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"test": "grunt test"
},
"devDependencies": {
- "eslint-config-wikimedia": "0.32.1",
+ "eslint-config-wikimedia": "0.32.2",
"grunt": "1.6.1",
"grunt-banana-checker": "0.13.0",
"grunt-contrib-watch": "1.1.0",
--
2.47.3
$ date
--- stdout ---
Tue Nov 18 18:50:53 UTC 2025
--- end ---
$ git clone file:///srv/git/mediawiki-extensions-UploadWizard.git repo --depth=1 -b master
--- stderr ---
Cloning into 'repo'...
--- stdout ---
--- end ---
$ git config user.name libraryupgrader
--- stdout ---
--- end ---
$ git config user.email tools.libraryupgrader@tools.wmflabs.org
--- stdout ---
--- end ---
$ git submodule update --init
--- stdout ---
--- end ---
$ grr init
--- stdout ---
Installed commit-msg hook.
--- end ---
$ git show-ref refs/heads/master
--- stdout ---
5eae7d89c16c293822934660b7cb50de84d209a5 refs/heads/master
--- end ---
$ /usr/bin/npm audit --json
--- stdout ---
{
"auditReportVersion": 2,
"vulnerabilities": {
"js-yaml": {
"name": "js-yaml",
"severity": "moderate",
"isDirect": false,
"via": [
{
"source": 1109801,
"name": "js-yaml",
"dependency": "js-yaml",
"title": "js-yaml has prototype pollution in merge (<<)",
"url": "https://github.com/advisories/GHSA-mh29-5h37-fv8m",
"severity": "moderate",
"cwe": [
"CWE-1321"
],
"cvss": {
"score": 5.3,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"
},
"range": "<3.14.2"
},
{
"source": 1109802,
"name": "js-yaml",
"dependency": "js-yaml",
"title": "js-yaml has prototype pollution in merge (<<)",
"url": "https://github.com/advisories/GHSA-mh29-5h37-fv8m",
"severity": "moderate",
"cwe": [
"CWE-1321"
],
"cvss": {
"score": 5.3,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"
},
"range": ">=4.0.0 <4.1.1"
}
],
"effects": [],
"range": "<3.14.2 || >=4.0.0 <4.1.1",
"nodes": [
"node_modules/@eslint/eslintrc/node_modules/js-yaml",
"node_modules/cosmiconfig/node_modules/js-yaml",
"node_modules/eslint/node_modules/js-yaml",
"node_modules/js-yaml"
],
"fixAvailable": true
}
},
"metadata": {
"vulnerabilities": {
"info": 0,
"low": 0,
"moderate": 1,
"high": 0,
"critical": 0,
"total": 1
},
"dependencies": {
"prod": 1,
"dev": 484,
"optional": 0,
"peer": 1,
"peerOptional": 0,
"total": 484
}
}
}
--- end ---
$ /usr/bin/composer install
--- stderr ---
No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.
Loading composer repositories with package information
Updating dependencies
Lock file operations: 38 installs, 0 updates, 0 removals
- Locking composer/pcre (3.3.2)
- Locking composer/semver (3.4.4)
- Locking composer/spdx-licenses (1.5.9)
- Locking composer/xdebug-handler (3.0.5)
- Locking dealerdirect/phpcodesniffer-composer-installer (v1.2.0)
- Locking doctrine/deprecations (1.1.5)
- Locking felixfbecker/advanced-json-rpc (v3.2.1)
- Locking mediawiki/mediawiki-codesniffer (v48.0.0)
- Locking mediawiki/mediawiki-phan-config (0.17.0)
- Locking mediawiki/minus-x (1.1.3)
- Locking mediawiki/phan-taint-check-plugin (7.0.0)
- Locking microsoft/tolerant-php-parser (v0.1.2)
- Locking netresearch/jsonmapper (v4.5.0)
- Locking phan/phan (5.5.1)
- Locking php-parallel-lint/php-console-color (v1.0.1)
- Locking php-parallel-lint/php-console-highlighter (v1.0.0)
- Locking php-parallel-lint/php-parallel-lint (v1.4.0)
- Locking phpcsstandards/phpcsextra (1.4.0)
- Locking phpcsstandards/phpcsutils (1.1.1)
- Locking phpdocumentor/reflection-common (2.2.0)
- Locking phpdocumentor/reflection-docblock (5.6.3)
- Locking phpdocumentor/type-resolver (1.10.1)
- Locking phpstan/phpdoc-parser (2.3.0)
- Locking psr/container (2.0.2)
- Locking psr/log (3.0.2)
- Locking sabre/event (5.1.7)
- Locking squizlabs/php_codesniffer (3.13.2)
- Locking symfony/console (v7.3.6)
- Locking symfony/deprecation-contracts (v3.6.0)
- Locking symfony/polyfill-ctype (v1.33.0)
- Locking symfony/polyfill-intl-grapheme (v1.33.0)
- Locking symfony/polyfill-intl-normalizer (v1.33.0)
- Locking symfony/polyfill-mbstring (v1.33.0)
- Locking symfony/polyfill-php80 (v1.33.0)
- Locking symfony/service-contracts (v3.6.1)
- Locking symfony/string (v7.3.4)
- Locking tysonandre/var_representation_polyfill (0.1.3)
- Locking webmozart/assert (1.12.1)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 38 installs, 0 updates, 0 removals
0 [>---------------------------] 0 [->--------------------------]
- Installing squizlabs/php_codesniffer (3.13.2): Extracting archive
- Installing dealerdirect/phpcodesniffer-composer-installer (v1.2.0): Extracting archive
- Installing composer/pcre (3.3.2): Extracting archive
- Installing phpcsstandards/phpcsutils (1.1.1): Extracting archive
- Installing phpcsstandards/phpcsextra (1.4.0): Extracting archive
- Installing symfony/polyfill-mbstring (v1.33.0): Extracting archive
- Installing composer/spdx-licenses (1.5.9): Extracting archive
- Installing composer/semver (3.4.4): Extracting archive
- Installing mediawiki/mediawiki-codesniffer (v48.0.0): Extracting archive
- Installing tysonandre/var_representation_polyfill (0.1.3): Extracting archive
- Installing symfony/polyfill-php80 (v1.33.0): Extracting archive
- Installing symfony/polyfill-intl-normalizer (v1.33.0): Extracting archive
- Installing symfony/polyfill-intl-grapheme (v1.33.0): Extracting archive
- Installing symfony/polyfill-ctype (v1.33.0): Extracting archive
- Installing symfony/string (v7.3.4): Extracting archive
- Installing symfony/deprecation-contracts (v3.6.0): Extracting archive
- Installing psr/container (2.0.2): Extracting archive
- Installing symfony/service-contracts (v3.6.1): Extracting archive
- Installing symfony/console (v7.3.6): Extracting archive
- Installing sabre/event (5.1.7): Extracting archive
- Installing netresearch/jsonmapper (v4.5.0): Extracting archive
- Installing microsoft/tolerant-php-parser (v0.1.2): Extracting archive
- Installing webmozart/assert (1.12.1): Extracting archive
- Installing phpstan/phpdoc-parser (2.3.0): Extracting archive
- Installing phpdocumentor/reflection-common (2.2.0): Extracting archive
- Installing doctrine/deprecations (1.1.5): Extracting archive
- Installing phpdocumentor/type-resolver (1.10.1): Extracting archive
- Installing phpdocumentor/reflection-docblock (5.6.3): Extracting archive
- Installing felixfbecker/advanced-json-rpc (v3.2.1): Extracting archive
- Installing psr/log (3.0.2): Extracting archive
- Installing composer/xdebug-handler (3.0.5): Extracting archive
- Installing phan/phan (5.5.1): Extracting archive
- Installing mediawiki/phan-taint-check-plugin (7.0.0): Extracting archive
- Installing mediawiki/mediawiki-phan-config (0.17.0): Extracting archive
- Installing mediawiki/minus-x (1.1.3): Extracting archive
- Installing php-parallel-lint/php-console-color (v1.0.1): Extracting archive
- Installing php-parallel-lint/php-console-highlighter (v1.0.0): Extracting archive
- Installing php-parallel-lint/php-parallel-lint (v1.4.0): Extracting archive
0/36 [>---------------------------] 0%
28/36 [=====================>------] 77%
36/36 [============================] 100%
1 package suggestions were added by new dependencies, use `composer suggest` to see details.
Generating autoload files
17 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
--- stdout ---
PHP CodeSniffer Config installed_paths set to ../../mediawiki/mediawiki-codesniffer,../../phpcsstandards/phpcsextra,../../phpcsstandards/phpcsutils
--- end ---
Upgrading n:eslint-config-wikimedia from 0.32.1 -> 0.32.2
$ /usr/bin/npm install
--- stderr ---
npm WARN deprecated @humanwhocodes/config-array@0.13.0: Use @eslint/config-array instead
npm WARN deprecated @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead
npm WARN deprecated eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options.
--- stdout ---
added 484 packages, and audited 485 packages in 5s
107 packages are looking for funding
run `npm fund` for details
1 moderate severity vulnerability
To address all issues, run:
npm audit fix
Run `npm audit` for details.
--- end ---
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json
--- end ---
$ /usr/bin/npm install grunt-eslint@24.3.0 --save-exact
--- stdout ---
up to date, audited 485 packages in 1s
107 packages are looking for funding
run `npm fund` for details
1 moderate severity vulnerability
To address all issues, run:
npm audit fix
Run `npm audit` for details.
--- end ---
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json
--- end ---
$ ./node_modules/.bin/eslint . --fix
--- stdout ---
/src/repo/resources/controller/uw.controller.Deed.js
23:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
47:3 warning Prefer .then to .done no-jquery/no-done-fail
94:22 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
293:10 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
305:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/resources/controller/uw.controller.Details.js
23:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
104:3 warning ES2025 'Iterator.prototype.some' method is forbidden es-x/no-iterator-prototype-some
164:3 warning Prefer .then to .done no-jquery/no-done-fail
176:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/resources/controller/uw.controller.Step.js
25:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
76:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
82:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
95:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
105:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
/src/repo/resources/controller/uw.controller.Thanks.js
23:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
/src/repo/resources/controller/uw.controller.Tutorial.js
24:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
61:3 warning Prefer .then to .done no-jquery/no-done-fail
61:3 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/resources/controller/uw.controller.Upload.js
24:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
/src/repo/resources/deed/uw.deed.Abstract.js
48:14 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
83:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
160:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
173:1 warning The type 'uw.PatentDialog' is undefined jsdoc/no-undefined-types
190:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/resources/deed/uw.deed.OwnWork.js
478:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
683:1 warning The type 'uw.PatentDialog' is undefined jsdoc/no-undefined-types
/src/repo/resources/deed/uw.deed.ThirdParty.js
139:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
266:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/details/uw.CampaignDetailsWidget.js
6:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
/src/repo/resources/details/uw.CategoriesDetailsWidget.js
130:40 warning ES2025 'Iterator.prototype.some' method is forbidden es-x/no-iterator-prototype-some
213:34 warning Prefer .then to .fail no-jquery/no-done-fail
242:36 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/resources/details/uw.DateDetailsWidget.js
6:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
16:31 warning OO.ui.ButtonWidget has no label. Even icon-only buttons should set a label with invisibleLabel set to true mediawiki/no-unlabeled-buttonwidget
164:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
171:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
179:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
187:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
204:25 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/details/uw.LanguageDropdownWidget.js
14:53 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
/src/repo/resources/details/uw.LocationDetailsWidget.js
6:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
67:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/details/uw.MultipleLanguageInputWidget.js
7:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
134:10 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
181:10 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/details/uw.OtherDetailsWidget.js
7:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
/src/repo/resources/details/uw.SingleLanguageInputWidget.js
6:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
45:23 warning OO.ui.ButtonWidget has no label. Even icon-only buttons should set a label with invisibleLabel set to true mediawiki/no-unlabeled-buttonwidget
/src/repo/resources/details/uw.StatementWidget.js
70:1 warning The type 'dataValues.DataValue' is undefined jsdoc/no-undefined-types
105:1 warning The type 'datamodel.StatementList' is undefined jsdoc/no-undefined-types
108:22 warning ES2025 'Iterator.prototype.toArray' method is forbidden es-x/no-iterator-prototype-toarray
117:1 warning The type 'datamodel.StatementList' is undefined jsdoc/no-undefined-types
131:1 warning The type 'dataValues.DataValue' is undefined jsdoc/no-undefined-types
132:1 warning The type 'datamodel.Statement' is undefined jsdoc/no-undefined-types
145:1 warning The type 'datamodel.StatementList' is undefined jsdoc/no-undefined-types
/src/repo/resources/details/uw.TitleDetailsWidget.js
10:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
112:1 warning Missing JSDoc @param "thorough" type jsdoc/require-param-type
113:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
161:30 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
207:10 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
215:27 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
217:21 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/details/uw.UlsWidget.js
30:3 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
99:1 warning The type 'uw.UlsWidget' is undefined jsdoc/no-undefined-types
/src/repo/resources/mw.DestinationChecker.js
14:3 warning Found more than one @return declaration jsdoc/require-returns
14:3 warning Found more than one @return declaration jsdoc/require-returns-check
35:3 warning Found more than one @return declaration jsdoc/require-returns
35:3 warning Found more than one @return declaration jsdoc/require-returns-check
80:3 warning Found more than one @return declaration jsdoc/require-returns
80:3 warning Found more than one @return declaration jsdoc/require-returns-check
/src/repo/resources/mw.FlickrChecker.js
4:1 warning Missing JSDoc @param "ui" type jsdoc/require-param-type
5:1 warning Missing JSDoc @param "selectButton" type jsdoc/require-param-type
212:1 warning The type 'getPhotos' is undefined jsdoc/no-undefined-types
226:17 warning 'data' is already declared in the upper scope on line 219 column 15 no-shadow
246:1 warning The type 'getCollection' is undefined jsdoc/no-undefined-types
304:45 warning 'data' is already declared in the upper scope on line 293 column 15 no-shadow
314:1 warning The type 'getPhotos' is undefined jsdoc/no-undefined-types
331:1 warning The type 'getPhotos' is undefined jsdoc/no-undefined-types
375:10 warning Prefer .then to .fail no-jquery/no-done-fail
468:5 warning Prefer .then to .done no-jquery/no-done-fail
519:10 warning Prefer .then to .fail no-jquery/no-done-fail
581:4 warning Prefer .then to .done no-jquery/no-done-fail
582:46 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
678:4 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
696:39 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
/src/repo/resources/mw.UploadWizard.js
4:1 warning Missing JSDoc @param "uw" type jsdoc/require-param-type
9:1 warning Missing JSDoc @param "config" type jsdoc/require-param-type
112:16 warning 'steps' is already declared in the upper scope on line 86 column 10 no-shadow
/src/repo/resources/mw.UploadWizardDeedChooser.js
32:42 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
40:17 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
44:21 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/mw.UploadWizardDetails.js
498:5 warning Prefer .then to .done no-jquery/no-done-fail
555:9 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
560:14 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
665:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
678:1 warning Missing JSDoc @param "thorough" type jsdoc/require-param-type
715:4 warning Prefer .then to .done no-jquery/no-done-fail
844:20 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
935:3 warning JSDoc @return declaration present but return expression not available in function jsdoc/require-returns-check
1017:9 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
1018:45 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
/src/repo/resources/mw.UploadWizardLicenseInput.js
50:20 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
61:41 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
69:49 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
123:24 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
287:28 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
/src/repo/resources/mw.UploadWizardUpload.js
24:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
224:16 warning ES2015 'Uint8Array' is forbidden es-x/no-typed-arrays
380:3 warning Prefer .then to .done no-jquery/no-done-fail
380:3 warning Prefer .then to .fail no-jquery/no-done-fail
445:3 warning Prefer .then to .done no-jquery/no-done-fail
445:3 warning Prefer .then to .fail no-jquery/no-done-fail
766:3 warning Prefer .then to .done no-jquery/no-done-fail
766:3 warning Prefer .then to .fail no-jquery/no-done-fail
774:6 warning Prefer .then to .done no-jquery/no-done-fail
777:7 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/mw.UploadWizardUploadInterface.js
110:3 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
215:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/transports/mw.FormDataTransport.js
166:5 warning Prefer .then to .done no-jquery/no-done-fail
167:6 warning Prefer .then to .done no-jquery/no-done-fail
167:6 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/resources/ui/steps/uw.ui.Deed.js
23:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
51:3 warning Prefer .then to .done no-jquery/no-done-fail
86:5 warning Prefer .then to .done no-jquery/no-done-fail
108:6 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/ui/steps/uw.ui.Details.js
23:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
108:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/ui/steps/uw.ui.Thanks.js
23:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
150:3 warning Prefer .then to .done no-jquery/no-done-fail
180:4 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
/src/repo/resources/ui/steps/uw.ui.Tutorial.js
50:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
126:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/ui/steps/uw.ui.Upload.js
23:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
263:39 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
268:33 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
334:6 warning Prefer .then to .done no-jquery/no-done-fail
344:3 warning Prefer .then to .done no-jquery/no-done-fail
528:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/ui/uw.ui.Step.js
111:3 warning Prefer .then to .done no-jquery/no-done-fail
120:3 warning Prefer .then to .done no-jquery/no-done-fail
166:12 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
180:7 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
197:5 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/ui/uw.ui.Wizard.js
128:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
141:25 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
166:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
167:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
/src/repo/resources/uw.LicenseGroup.js
148:48 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
181:56 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
423:18 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
435:14 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
443:5 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
451:5 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
496:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
619:3 warning Prefer .then to .done no-jquery/no-done-fail
619:3 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/resources/uw.ValidatableElement.js
36:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/resources/uw.ValidationMessageElement.js
10:1 warning The type 'uw.ValidatableElement' is undefined jsdoc/no-undefined-types
48:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
60:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
92:10 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
92:10 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
/src/repo/resources/uw.ValidationStatus.js
11:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
20:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
29:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
38:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
47:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
56:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
85:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
92:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
99:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
100:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
115:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
116:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/tests/qunit/controller/uw.controller.Details.test.js
127:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/tests/qunit/transports/mw.FormDataTransport.test.js
142:3 warning Prefer .then to .fail no-jquery/no-done-fail
175:10 warning Prefer .then to .done no-jquery/no-done-fail
194:10 warning Prefer .then to .done no-jquery/no-done-fail
214:3 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/tests/qunit/uw.ConcurrentQueue.test.js
54:5 warning Prefer .then to .fail no-jquery/no-done-fail
✖ 180 problems (0 errors, 180 warnings)
--- end ---
$ ./node_modules/.bin/eslint . -f json
--- stdout ---
[{"filePath":"/src/repo/.eslintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/.stylelintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/Gruntfile.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-len","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/composer.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/extension.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/i18n/api/en.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/i18n/api/qqq.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/i18n/en.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/i18n/qqq.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/jsdoc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/package-lock.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/package.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/.eslintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/controller/uw.controller.Deed.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.controller.Step' is undefined.","line":23,"column":1,"nodeType":"Block","endLine":23,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":47,"column":3,"nodeType":"CallExpression","endLine":49,"endColumn":69},{"ruleId":"es-x/no-iterator-prototype-reduce","severity":1,"message":"ES2025 'Iterator.prototype.reduce' method is forbidden.","line":94,"column":22,"nodeType":"MemberExpression","messageId":"forbidden","endLine":94,"endColumn":41},{"ruleId":"es-x/no-iterator-prototype-reduce","severity":1,"message":"ES2025 'Iterator.prototype.reduce' method is forbidden.","line":293,"column":10,"nodeType":"MemberExpression","messageId":"forbidden","endLine":293,"endColumn":24},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":305,"column":1,"nodeType":"Block","endLine":305,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\t/**\n\t * Deed step controller.\n\t *\n\t * @class\n\t * @extends uw.controller.Step\n\t * @param {mw.Api} api\n\t * @param {Object} config UploadWizard config object.\n\t */\n\tuw.controller.Deed = function UWControllerDeed( api, config ) {\n\t\tuw.controller.Step.call(\n\t\t\tthis,\n\t\t\tnew uw.ui.Deed(),\n\t\t\tapi,\n\t\t\tconfig\n\t\t);\n\n\t\tthis.stepName = 'deeds';\n\t};\n\tOO.inheritClass( uw.controller.Deed, uw.controller.Step );\n\n\tuw.controller.Deed.prototype.moveNext = function () {\n\t\tconst deedChoosers = this.getUniqueDeedChoosers( this.uploads );\n\n\t\tif ( deedChoosers.length === 0 ) {\n\t\t\tuw.controller.Step.prototype.moveNext.call( this );\n\t\t\treturn;\n\t\t}\n\n\t\tthis.validate( true )\n\t\t\t.always( () => this.ui.updateErrorSummary() )\n\t\t\t.done( () => uw.controller.Step.prototype.moveNext.call( this ) );\n\t};\n\n\tuw.controller.Deed.prototype.unload = function () {\n\t\tconst deedChoosers = this.getUniqueDeedChoosers( this.uploads );\n\t\tuw.controller.Step.prototype.unload.call( this );\n\n\t\t// serialize the first deed so we can use it to pre-populate the choices if someone is\n\t\t// uploading files in batches\n\t\tthis.uploadedDeedSerialization =\n\t\t\tdeedChoosers[ Object.keys( deedChoosers )[ 0 ] ].getSerialized();\n\n\t\tdeedChoosers.forEach( ( deedChooser ) => {\n\t\t\tdeedChooser.remove();\n\t\t} );\n\t};\n\n\t/**\n\t * Move to this step.\n\t *\n\t * @param {mw.UploadWizardUpload[]} uploads\n\t */\n\tuw.controller.Deed.prototype.load = function ( uploads ) {\n\t\t// select \"provide same information for all files\" by default\n\t\tlet defaultDeedInterface = 'common';\n\t\tconst localUploads = uploads.filter( ( upload ) => {\n\t\t\t\tlet deed;\n\t\t\t\tif ( upload.file.fromURL ) {\n\t\t\t\t\t// external uploads should get a custom deed...\n\t\t\t\t\tdeed = new uw.deed.Custom( this.config, upload );\n\t\t\t\t\tupload.deedChooser = new mw.UploadWizardDeedChooser(\n\t\t\t\t\t\tthis.config,\n\t\t\t\t\t\t{ [ deed.name ]: deed },\n\t\t\t\t\t\t[ upload ]\n\t\t\t\t\t);\n\t\t\t\t\tupload.deedChooser.selectDeed( deed );\n\t\t\t\t\t// ... and be filtered out of the list for which to select a license\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\treturn true;\n\t\t\t} ),\n\t\t\t// figure out how many unique deed choosers there were, so\n\t\t\t// we can restore the same (common/individual) interface\n\t\t\tuniqueExistingDeedChoosers = this.getUniqueDeedChoosers( localUploads ),\n\t\t\t// grab a serialized copy of previous deeds' details (if any)\n\t\t\tserializedDeeds = localUploads.reduce( ( map, upload ) => {\n\t\t\t\tif ( upload.deedChooser ) {\n\t\t\t\t\tmap[ upload.getFilename() ] = upload.deedChooser.getSerialized();\n\t\t\t\t}\n\t\t\t\treturn map;\n\t\t\t}, {} ),\n\t\t\tshowDeed = localUploads.length > 0,\n\t\t\tfromStepName = uploads[ 0 ].state;\n\n\t\tuw.controller.Step.prototype.load.call( this, uploads );\n\n\t\t// If all of the uploads are from URLs, then we know the licenses\n\t\t// already, we don't need this step.\n\t\tif ( !showDeed ) {\n\t\t\t// this is a bit of a hack: when images from flickr are uploaded, we\n\t\t\t// don't get to choose the license anymore, and this step will be\n\t\t\t// skipped ... but we could reach this step from either direction\n\t\t\tif ( fromStepName === 'details' ) {\n\t\t\t\tthis.movePrevious();\n\t\t\t} else {\n\t\t\t\tthis.moveNext();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst multiDeedRadio = new OO.ui.RadioSelectWidget( {\n\t\t\tclasses: [ 'mwe-upwiz-source-multiple' ],\n\t\t\titems: [\n\t\t\t\tnew OO.ui.RadioOptionWidget( {\n\t\t\t\t\tlabel: mw.message(\n\t\t\t\t\t\t'mwe-upwiz-source-multiple-label-common',\n\t\t\t\t\t\tlocalUploads.length,\n\t\t\t\t\t\tmw.user\n\t\t\t\t\t).parse(),\n\t\t\t\t\tdata: 'common'\n\t\t\t\t} ),\n\t\t\t\tnew OO.ui.RadioOptionWidget( {\n\t\t\t\t\tlabel: mw.message(\n\t\t\t\t\t\t'mwe-upwiz-source-multiple-label-individual',\n\t\t\t\t\t\tlocalUploads.length,\n\t\t\t\t\t\tmw.user\n\t\t\t\t\t).parse(),\n\t\t\t\t\tdata: 'individual'\n\t\t\t\t} )\n\t\t\t]\n\t\t} );\n\n\t\t// if we have multiple uploads, also give them the option to set\n\t\t// licenses individually\n\t\tif ( localUploads.length > 1 && this.shouldShowIndividualDeed( this.config ) ) {\n\t\t\tthis.ui.showMultiDeedRadio( multiDeedRadio );\n\n\t\t\tif ( uniqueExistingDeedChoosers.length > 1 ) {\n\t\t\t\t// we also had more than 1 deed in the past, so default\n\t\t\t\t// to loading the individual deed selection\n\t\t\t\tdefaultDeedInterface = 'individual';\n\t\t\t}\n\t\t}\n\n\t\t// wire up handler to toggle common/individual deed selection forms\n\t\tmultiDeedRadio.on( 'select', ( selectedOption ) => {\n\t\t\tif ( selectedOption.getData() === 'common' ) {\n\t\t\t\tthis.loadCommon( localUploads );\n\t\t\t} else if ( selectedOption.getData() === 'individual' ) {\n\t\t\t\tthis.loadIndividual( localUploads );\n\t\t\t}\n\t\t\tthis.emit( 'change' );\n\t\t} );\n\n\t\tmultiDeedRadio.selectItemByData( defaultDeedInterface );\n\n\t\t// restore serialized data (if any)\n\t\tuploads.forEach( ( upload ) => {\n\t\t\tif ( serializedDeeds[ upload.getFilename() ] ) {\n\t\t\t\tupload.deedChooser.setSerialized( serializedDeeds[ upload.getFilename() ] );\n\t\t\t} else if ( this.uploadedDeedSerialization ) {\n\t\t\t\tupload.deedChooser.setSerialized( this.uploadedDeedSerialization );\n\t\t\t}\n\t\t} );\n\t};\n\n\t/**\n\t * Loads the deed form for providing information for a single file, or multiple\n\t * files all at once.\n\t *\n\t * @param {mw.UploadWizardUpload[]} uploads\n\t */\n\tuw.controller.Deed.prototype.loadCommon = function ( uploads ) {\n\t\tconst deeds = this.getLicensingDeeds( uploads ),\n\t\t\tdeedChooser = new mw.UploadWizardDeedChooser(\n\t\t\t\tthis.config,\n\t\t\t\tdeeds,\n\t\t\t\tuploads\n\t\t\t);\n\n\t\tuploads.forEach( ( upload ) => {\n\t\t\tupload.deedChooser = deedChooser;\n\t\t} );\n\n\t\tthis.ui.showCommonForm( deedChooser );\n\n\t\t// reveal next button when deed has been chosen\n\t\tdeedChooser.on( 'choose', this.enableNextIfAllDeedsChosen.bind( this ) );\n\t\tthis.enableNextIfAllDeedsChosen();\n\n\t\t// aggregate change events within\n\t\tdeedChooser.on( 'choose', () => this.emit( 'change' ) );\n\t\tObject.keys( deeds ).forEach( ( name ) => {\n\t\t\tdeeds[ name ].on( 'change', () => this.emit( 'change' ) );\n\t\t} );\n\t};\n\n\t/**\n\t * Loads the deed form for providing individual license information per file.\n\t *\n\t * @param {mw.UploadWizardUpload[]} uploads\n\t */\n\tuw.controller.Deed.prototype.loadIndividual = function ( uploads ) {\n\t\tuploads.forEach( ( upload ) => {\n\t\t\tconst deeds = this.getLicensingDeeds( uploads ),\n\t\t\t\tdeedChooser = new mw.UploadWizardDeedChooser(\n\t\t\t\t\tthis.config,\n\t\t\t\t\tdeeds,\n\t\t\t\t\t[ upload ]\n\t\t\t\t);\n\n\t\t\tupload.deedChooser = deedChooser;\n\n\t\t\t// reveal next button when deeds for all files have been chosen\n\t\t\tdeedChooser.on( 'choose', this.enableNextIfAllDeedsChosen.bind( this ) );\n\n\t\t\t// aggregate change events within\n\t\t\tdeedChooser.on( 'choose', () => this.emit( 'change' ) );\n\t\t\tObject.keys( deeds ).forEach( ( name ) => {\n\t\t\t\tdeeds[ name ].on( 'change', () => this.emit( 'change' ) );\n\t\t\t} );\n\t\t} );\n\n\t\tthis.ui.showIndividualForm( this.getUniqueDeedChoosers( uploads ) );\n\n\t\tthis.enableNextIfAllDeedsChosen();\n\t};\n\n\t/**\n\t * Check whether we should give the user the option to choose licenses for\n\t * individual files on the details step.\n\t *\n\t * @private\n\t * @param {Object} config\n\t * @return {boolean}\n\t */\n\tuw.controller.Deed.prototype.shouldShowIndividualDeed = function ( config ) {\n\t\tlet ownWork;\n\t\tif ( config.licensing.ownWorkDefault === 'choice' ) {\n\t\t\treturn true;\n\t\t} else if ( config.licensing.ownWorkDefault === 'own' ) {\n\t\t\townWork = config.licensing.ownWork;\n\t\t\treturn ownWork.licenses.length > 1;\n\t\t} else {\n\t\t\treturn true; // TODO: might want to have similar behaviour here\n\t\t}\n\t};\n\n\t/**\n\t * Get the own work and third party licensing deeds if they are needed.\n\t *\n\t * @param {mw.UploadWizardUpload[]} uploads\n\t * @return {mw.deed.Abstract[]}\n\t */\n\tuw.controller.Deed.prototype.getLicensingDeeds = function ( uploads ) {\n\t\tconst deeds = {};\n\t\tlet doOwnWork = false,\n\t\t\tdoThirdParty = false;\n\n\t\tif ( this.config.licensing.ownWorkDefault === 'choice' ) {\n\t\t\tdoOwnWork = doThirdParty = true;\n\t\t} else if ( this.config.licensing.ownWorkDefault === 'own' ) {\n\t\t\tdoOwnWork = true;\n\t\t} else {\n\t\t\tdoThirdParty = true;\n\t\t}\n\n\t\tif ( doOwnWork ) {\n\t\t\tconst deed = new uw.deed.OwnWork( this.config, uploads, this.api );\n\t\t\tdeeds[ deed.name ] = deed;\n\t\t}\n\t\tif ( doThirdParty ) {\n\t\t\tconst deed = new uw.deed.ThirdParty( this.config, uploads, this.api );\n\t\t\tdeeds[ deed.name ] = deed;\n\t\t}\n\n\t\treturn deeds;\n\t};\n\n\t/**\n\t * @param {mw.UploadWizardUpload[]} uploads\n\t * @return {mw.UploadWizardDeedChooser[]}\n\t */\n\tuw.controller.Deed.prototype.getUniqueDeedChoosers = function ( uploads ) {\n\t\treturn uploads.reduce( ( uniques, upload ) => {\n\t\t\tif ( upload.deedChooser && !uniques.includes( upload.deedChooser ) ) {\n\t\t\t\tuniques.push( upload.deedChooser );\n\t\t\t}\n\t\t\treturn uniques;\n\t\t}, [] );\n\t};\n\n\t/**\n\t * Checks deeds for validity.\n\t *\n\t * @param {boolean} thorough\n\t * @return {jQuery.Promise<uw.ValidationStatus>}\n\t */\n\tuw.controller.Deed.prototype.validate = function ( thorough ) {\n\t\tconst deedChoosers = this.getUniqueDeedChoosers( this.uploads ),\n\t\t\tdeedPromises = this.getUniqueDeedChoosers( this.uploads ).map( ( deedChooser ) => deedChooser.validate( thorough ) ),\n\t\t\tmergedPromise = uw.ValidationStatus.mergePromises( ...deedPromises );\n\n\t\treturn mergedPromise.then(\n\t\t\t( status ) => {\n\t\t\t\tif ( thorough !== true ) {\n\t\t\t\t\treturn status;\n\t\t\t\t}\n\n\t\t\t\t// if (and only if) deed choosers are selected, we'll validate their contents\n\t\t\t\tconst fieldPromises = [];\n\t\t\t\tdeedChoosers.forEach( ( deedChooser ) => {\n\t\t\t\t\tdeedChooser.deed.getFields().forEach( ( fieldLayout ) => {\n\t\t\t\t\t\tfieldPromises.push( fieldLayout.validate( thorough ) );\n\t\t\t\t\t} );\n\t\t\t\t} );\n\t\t\t\treturn uw.ValidationStatus.mergePromises( mergedPromise, ...fieldPromises );\n\t\t\t}\n\t\t);\n\t};\n\n\t/**\n\t * Enable/disable the next button based on whether all deeds have been chosen.\n\t */\n\tuw.controller.Deed.prototype.enableNextIfAllDeedsChosen = function () {\n\t\t// Note: wrapping this inside setTimeout to ensure this is added\n\t\t// at the end of the call stack; otherwise, due to how this is all\n\t\t// set up, timing can be unpredictable: when re-loading the form\n\t\t// after coming back from a later step, this ends being called more\n\t\t// than once - both uninitialized (where it should not be visible),\n\t\t// and initialized (after resetting the serialized state), where\n\t\t// the event handlers end up invoking this.\n\t\t// Stuffing this inside a setTimeout helps ensure that things\n\t\t// actually execute in the order they're supposed to; i.e. the order\n\t\t// they've been called in.\n\t\tsetTimeout( () => {\n\t\t\tthis.validate( false ).always( ( status ) => {\n\t\t\t\tthis.ui.updateErrorSummary();\n\t\t\t\tthis.ui.toggleNext( status.getErrors().length === 0 );\n\t\t\t} );\n\t\t} );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/controller/uw.controller.Details.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.controller.Step' is undefined.","line":23,"column":1,"nodeType":"Block","endLine":23,"endColumn":1},{"ruleId":"es-x/no-iterator-prototype-some","severity":1,"message":"ES2025 'Iterator.prototype.some' method is forbidden.","line":104,"column":3,"nodeType":"MemberExpression","messageId":"forbidden","endLine":104,"endColumn":20},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":164,"column":3,"nodeType":"CallExpression","endLine":169,"endColumn":7},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":176,"column":1,"nodeType":"Block","endLine":176,"endColumn":1}],"suppressedMessages":[{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":156,"column":56,"nodeType":"CallExpression","endLine":156,"endColumn":85,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\t/**\n\t * Represents the details step in the wizard.\n\t *\n\t * @class\n\t * @extends uw.controller.Step\n\t * @param {mw.Api} api\n\t * @param {Object} config UploadWizard config object.\n\t */\n\tuw.controller.Details = function UWControllerDetails( api, config ) {\n\t\tuw.controller.Step.call(\n\t\t\tthis,\n\t\t\tnew uw.ui.Details()\n\t\t\t\t.on( 'start-details', this.startDetails.bind( this ) )\n\t\t\t\t.on( 'finalize-details-after-removal', this.moveNext.bind( this ) ),\n\t\t\tapi,\n\t\t\tconfig\n\t\t);\n\n\t\tthis.stepName = 'details';\n\t\tthis.finishState = 'complete';\n\n\t\tthis.queue = new uw.ConcurrentQueue( {\n\t\t\tcount: this.config.maxSimultaneousConnections,\n\t\t\taction: this.transitionOne.bind( this )\n\t\t} );\n\t};\n\n\tOO.inheritClass( uw.controller.Details, uw.controller.Step );\n\n\t/**\n\t * Move to this step.\n\t *\n\t * @param {mw.UploadWizardUpload[]} uploads List of uploads being carried forward.\n\t */\n\tuw.controller.Details.prototype.load = function ( uploads ) {\n\t\tuw.controller.Step.prototype.load.call( this, uploads );\n\n\t\t// make sure queue is empty before starting this step\n\t\tthis.queue.abortExecuting();\n\n\t\tthis.uploads.forEach( ( upload ) => {\n\t\t\t// get existing details\n\t\t\tconst serialized = upload.details ? upload.details.getSerialized() : null;\n\n\t\t\tthis.createDetails( upload );\n\t\t\tupload.details.attach();\n\t\t\tupload.details.on( 'change', () => this.emit( 'change' ) );\n\n\t\t\t// restore earlier details (user may have started inputting details,\n\t\t\t// then went back some steps, and now got here again)\n\t\t\tif ( serialized ) {\n\t\t\t\tupload.details.setSerialized( serialized );\n\t\t\t}\n\t\t} );\n\n\t\t// Show the widget allowing to copy selected metadata if there's more than one successful upload\n\t\tif ( this.config.copyMetadataFeature ) {\n\t\t\tthis.addCopyMetadataFeature();\n\t\t}\n\t};\n\n\tuw.controller.Details.prototype.moveNext = function () {\n\t\tthis.removeErrorUploads();\n\n\t\tuw.controller.Step.prototype.moveNext.call( this );\n\t};\n\n\tuw.controller.Details.prototype.addCopyMetadataFeature = function () {\n\t\t// uploads can only be edited when they're in a certain state:\n\t\t// a flat out upload failure or a completed upload can not be edited\n\t\tconst invalidStates = [ 'aborted', 'error', 'complete' ],\n\t\t\tinvalids = this.getUploadStatesCount( invalidStates ),\n\t\t\tvalids = this.uploads.length - invalids;\n\n\t\t// no point in having this feature if there's no target to copy to\n\t\tif ( valids < 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet first;\n\t\t// The first upload is not necessarily the one we want to copy from\n\t\t// E.g. the first upload could've gone through successfully, but the\n\t\t// rest failed because of abusefilter (or another recoverable error), in\n\t\t// which case we'll want the \"copy\" feature to appear below the 2nd\n\t\t// upload (or the first not-yet-completed not flat-out-failed upload)\n\t\tthis.uploads.some( ( upload ) => {\n\t\t\tif ( upload && !invalidStates.includes( upload.state ) ) {\n\t\t\t\tfirst = upload;\n\t\t\t\treturn true; // Break Array.some loop\n\t\t\t}\n\t\t\treturn false;\n\t\t} );\n\n\t\t// could not find a source upload to copy from\n\t\tif ( !first ) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.copyMetadataWidget = new uw.CopyMetadataWidget( {\n\t\t\tcopyFrom: first,\n\t\t\t// Include the \"source\" upload in the targets too\n\t\t\tcopyTo: this.uploads,\n\t\t\tcaptionsAvailable: this.config.wikibase.enabled && this.config.wikibase.captions\n\t\t} );\n\t\tthis.copyMetadataField = new uw.FieldLayout( this.copyMetadataWidget, {\n\t\t\tlabel: $( '<span>' ).append(\n\t\t\t\tnew OO.ui.IconWidget( { icon: 'expand' } ).$element,\n\t\t\t\tnew OO.ui.IconWidget( { icon: 'collapse' } ).$element,\n\t\t\t\t' ',\n\t\t\t\tmw.msg( 'mwe-upwiz-copy-metadata-text' )\n\t\t\t),\n\t\t\tclasses: [\n\t\t\t\t'mwe-upwiz-fieldLayout-additional-info', 'mwe-upwiz-copyMetadataWidget',\n\t\t\t\t'mwe-upwiz-fieldLayout-additional-info-clickable'\n\t\t\t]\n\t\t} );\n\t\tthis.copyMetadataWidget.$element.makeCollapsible( {\n\t\t\tcollapsed: true,\n\t\t\t$customTogglers: this.copyMetadataField.$element.find( '.oo-ui-fieldLayout-header' )\n\t\t} );\n\t\t// the field isn't actually required, but we want to hide the \"optional\" text\n\t\tthis.copyMetadataField.setRequired( true );\n\n\t\tfirst.details.$form.append( this.copyMetadataField.$element );\n\t};\n\n\tuw.controller.Details.prototype.removeCopyMetadataFeature = function () {\n\t\tif ( this.copyMetadataField ) {\n\t\t\tthis.copyMetadataField.$element.remove();\n\t\t}\n\t};\n\n\t/**\n\t * @param {mw.UploadWizardUpload} upload\n\t */\n\tuw.controller.Details.prototype.createDetails = function ( upload ) {\n\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\tupload.details = new mw.UploadWizardDetails( upload, $( '#mwe-upwiz-macro-files' ) );\n\t};\n\n\t/**\n\t * Start details submit.\n\t * TODO move the rest of the logic here from mw.UploadWizard\n\t */\n\tuw.controller.Details.prototype.startDetails = function () {\n\t\tthis.validate( true )\n\t\t\t.always( () => this.updateErrorSummary() )\n\t\t\t.done( () => {\n\t\t\t\tthis.ui.hideEndButtons();\n\t\t\t\tthis.submit();\n\t\t\t} );\n\t};\n\n\t/**\n\t * Check details for validity.\n\t *\n\t * @param {boolean} thorough\n\t * @return {jQuery.Promise<uw.ValidationStatus>}\n\t */\n\tuw.controller.Details.prototype.validate = function ( thorough ) {\n\t\tconst titles = [],\n\t\t\tfieldPromises = [];\n\n\t\tthis.uploads.forEach( ( upload ) => {\n\t\t\t// Seen this title before?\n\t\t\tlet title = upload.details.getTitle();\n\t\t\tif ( title ) {\n\t\t\t\ttitle = title.getName() + '.' + mw.Title.normalizeExtension( title.getExtension() );\n\t\t\t\tupload.details.titleDetails.setIsDuplicate( title in titles );\n\t\t\t\ttitles[ title ] = true;\n\t\t\t}\n\n\t\t\tupload.details.getAllFields().forEach( ( fieldLayout ) => {\n\t\t\t\tfieldPromises.push( fieldLayout.validate( thorough ) );\n\t\t\t} );\n\t\t} );\n\n\t\treturn uw.ValidationStatus.mergePromises( ...fieldPromises );\n\t};\n\n\tuw.controller.Details.prototype.canTransition = function ( upload ) {\n\t\treturn (\n\t\t\tuw.controller.Step.prototype.canTransition.call( this, upload ) &&\n\t\t\tupload.state === this.stepName\n\t\t);\n\t};\n\n\t/**\n\t * Perform this step's changes on one upload.\n\t *\n\t * @param {mw.UploadWizardUpload} upload\n\t * @return {jQuery.Promise}\n\t */\n\tuw.controller.Details.prototype.transitionOne = function ( upload ) {\n\t\treturn upload.details.submit();\n\t};\n\n\t/**\n\t * Perform this step's changes on all uploads.\n\t *\n\t * @return {jQuery.Promise}\n\t */\n\tuw.controller.Details.prototype.transitionAll = function () {\n\t\tconst deferred = $.Deferred();\n\n\t\tthis.uploads.forEach( ( upload ) => {\n\t\t\tif ( this.canTransition( upload ) ) {\n\t\t\t\tthis.queue.addItem( upload );\n\t\t\t}\n\t\t} );\n\n\t\tthis.queue.on( 'complete', deferred.resolve );\n\t\tthis.queue.startExecuting();\n\n\t\treturn deferred.promise();\n\t};\n\n\t/**\n\t * Submit details to the API.\n\t *\n\t * @return {jQuery.Promise}\n\t */\n\tuw.controller.Details.prototype.submit = function () {\n\t\tthis.uploads.forEach( ( upload ) => {\n\t\t\t// Clear error state\n\t\t\tif ( upload.state === 'error' || upload.state === 'recoverable-error' ) {\n\t\t\t\tupload.state = this.stepName;\n\t\t\t}\n\n\t\t\t// Set details view to have correct title\n\t\t\tupload.details.setVisibleTitle( upload.details.getTitle().getMain() );\n\t\t} );\n\n\t\t// Disable edit interface\n\t\tthis.ui.disableEdits();\n\t\tthis.removeCopyMetadataFeature();\n\n\t\treturn this.transitionAll().then( () => {\n\t\t\tif ( this.showNext() ) {\n\t\t\t\tthis.moveNext();\n\t\t\t}\n\t\t} );\n\t};\n\n\t/**\n\t * Show errors, warnings & notices in the form.\n\t * See UI class for more.\n\t */\n\tuw.controller.Details.prototype.updateErrorSummary = function () {\n\t\tthis.ui.enableEdits();\n\n\t\tthis.removeCopyMetadataFeature();\n\t\tthis.addCopyMetadataFeature();\n\n\t\tthis.ui.updateErrorSummary();\n\t};\n\n\t/**\n\t * Handler for when an upload is removed.\n\t *\n\t * @param {mw.UploadWizardUpload} upload\n\t */\n\tuw.controller.Details.prototype.removeUpload = function ( upload ) {\n\t\tuw.controller.Step.prototype.removeUpload.call( this, upload );\n\n\t\tthis.queue.removeItem( upload );\n\n\t\tif ( upload.details && upload.details.$div ) {\n\t\t\tupload.details.$div.remove();\n\t\t}\n\n\t\tif ( this.uploads.length === 0 ) {\n\t\t\t// If we have no more uploads, go to the \"Upload\" step. (This will go to \"Thanks\" step,\n\t\t\t// which will skip itself in load() because there are no uploads left.)\n\t\t\tthis.moveNext();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.removeCopyMetadataFeature();\n\t\t// Make sure we still have more multiple uploads adding the\n\t\t// copy feature again\n\t\tif ( this.config.copyMetadataFeature ) {\n\t\t\tthis.addCopyMetadataFeature();\n\t\t}\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/controller/uw.controller.Step.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ui.Step' is undefined.","line":25,"column":1,"nodeType":"Block","endLine":25,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.controller.Step' is undefined.","line":76,"column":1,"nodeType":"Block","endLine":76,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.controller.Step' is undefined.","line":82,"column":1,"nodeType":"Block","endLine":82,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.controller.Step' is undefined.","line":95,"column":1,"nodeType":"Block","endLine":95,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.controller.Step' is undefined.","line":105,"column":1,"nodeType":"Block","endLine":105,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\t/**\n\t * Represents a step in the wizard.\n\t *\n\t * @class\n\t * @mixes OO.EventEmitter\n\t * @abstract\n\t * @param {uw.ui.Step} ui The UI object that controls this step.\n\t * @param {mw.Api} api\n\t * @param {Object} config UploadWizard config object.\n\t */\n\tuw.controller.Step = function UWControllerStep( ui, api, config ) {\n\t\tOO.EventEmitter.call( this );\n\n\t\t/**\n\t\t * @property {Object} config\n\t\t */\n\t\tthis.config = Object.assign(\n\t\t\t{\n\t\t\t\tshowInBreadcrumb: true\n\t\t\t},\n\t\t\tconfig\n\t\t);\n\t\t/**\n\t\t * @property {mw.Api} api\n\t\t */\n\t\tthis.api = api;\n\n\t\tthis.ui = ui;\n\n\t\tthis.uploads = [];\n\n\t\t// children are expected to override this with the actual step name\n\t\tthis.stepName = new Error( 'Undefined stepName' );\n\n\t\t/**\n\t\t * Upload object event handlers to be bound on load & unbound on unload.\n\t\t * This is an object literal where the keys are callback names, and\n\t\t * values all callback. These callbacks will be called with the\n\t\t * controller as content (`this`), and the upload as first argument.\n\t\t * This'll effectively be:\n\t\t * `upload.on( <key>, <value>.bind( this, upload ) );`\n\t\t *\n\t\t * @property {Object}\n\t\t */\n\t\tthis.uploadHandlers = {\n\t\t\t'remove-upload': this.removeUpload\n\t\t};\n\n\t\tthis.ui.on( 'next-step', () => {\n\t\t\tthis.moveNext();\n\t\t} );\n\n\t\tthis.ui.on( 'previous-step', () => {\n\t\t\tthis.movePrevious();\n\t\t} );\n\n\t\t/**\n\t\t * @property {uw.controller.Step} nextStep\n\t\t * The next step in the process.\n\t\t */\n\t\tthis.nextStep = null;\n\n\t\t/**\n\t\t * @property {uw.controller.Step} previousStep\n\t\t * The previous step in the process.\n\t\t */\n\t\tthis.previousStep = null;\n\n\t\tthis.showInBreadcrumb = this.config.showInBreadcrumb;\n\t};\n\n\tOO.mixinClass( uw.controller.Step, OO.EventEmitter );\n\n\t/**\n\t * Set the next step in the process.\n\t *\n\t * @param {uw.controller.Step} step\n\t */\n\tuw.controller.Step.prototype.setNextStep = function ( step ) {\n\t\tthis.nextStep = step;\n\t\tthis.ui.enableNextButton();\n\t};\n\n\t/**\n\t * Set the previous step in the process.\n\t *\n\t * @param {uw.controller.Step} step\n\t */\n\tuw.controller.Step.prototype.setPreviousStep = function ( step ) {\n\t\tthis.previousStep = step;\n\t\tthis.ui.enablePreviousButton();\n\t};\n\n\t/**\n\t * Initialize this step.\n\t *\n\t * @param {mw.UploadWizardUpload[]} uploads List of uploads being carried forward.\n\t */\n\tuw.controller.Step.prototype.load = function ( uploads ) {\n\t\tthis.emit( 'load' );\n\n\t\tthis.uploads = uploads || [];\n\n\t\t// prevent the window from being closed as long as we have data\n\t\tthis.allowCloseWindow = mw.confirmCloseWindow( {\n\t\t\ttest: this.hasData.bind( this )\n\t\t} );\n\n\t\tthis.uploads.forEach( ( upload ) => {\n\t\t\tupload.state = this.stepName;\n\n\t\t\tthis.bindUploadHandlers( upload );\n\t\t} );\n\n\t\tthis.ui.load( uploads );\n\t};\n\n\t/**\n\t * Cleanup this step.\n\t */\n\tuw.controller.Step.prototype.unload = function () {\n\t\tthis.uploads.forEach( ( upload ) => {\n\t\t\tthis.unbindUploadHandlers( upload );\n\t\t} );\n\n\t\tthis.allowCloseWindow.release();\n\t\tthis.ui.unload();\n\n\t\tthis.emit( 'unload' );\n\t};\n\n\t/**\n\t * Move to the next step.\n\t */\n\tuw.controller.Step.prototype.moveNext = function () {\n\t\tthis.unload();\n\n\t\tif ( this.nextStep ) {\n\t\t\tthis.nextStep.load( this.uploads );\n\t\t}\n\t};\n\n\t/**\n\t * Move to the previous step.\n\t */\n\tuw.controller.Step.prototype.movePrevious = function () {\n\t\tthis.unload();\n\n\t\tif ( this.previousStep ) {\n\t\t\tthis.previousStep.load( this.uploads );\n\t\t}\n\t};\n\n\t/**\n\t * Attaches controller-specific upload event handlers.\n\t *\n\t * @param {mw.UploadWizardUpload} upload\n\t */\n\tuw.controller.Step.prototype.bindUploadHandlers = function ( upload ) {\n\t\tObject.keys( this.uploadHandlers ).forEach( ( event ) => {\n\t\t\tconst callback = this.uploadHandlers[ event ];\n\t\t\tupload.on( event, callback, [ upload ], this );\n\t\t} );\n\t};\n\n\t/**\n\t * Removes controller-specific upload event handlers.\n\t *\n\t * @param {mw.UploadWizardUpload} upload\n\t */\n\tuw.controller.Step.prototype.unbindUploadHandlers = function ( upload ) {\n\t\tObject.keys( this.uploadHandlers ).forEach( ( event ) => {\n\t\t\tconst callback = this.uploadHandlers[ event ];\n\t\t\tupload.off( event, callback, this );\n\t\t} );\n\t};\n\n\t/**\n\t * Check if upload is able to be put through this step's changes.\n\t *\n\t * @return {boolean}\n\t */\n\tuw.controller.Step.prototype.canTransition = function () {\n\t\treturn true;\n\t};\n\n\t/**\n\t * Figure out what to do and what options to show after the uploads have stopped.\n\t * Uploading has stopped for one of the following reasons:\n\t * 1) The user removed all uploads before they completed, in which case we are at upload.length === 0. We should start over and allow them to add new ones\n\t * 2) All succeeded - show link to next step\n\t * 3) Some failed, some succeeded - offer them the chance to retry the failed ones or go on to the next step\n\t * 4) All failed -- have to retry, no other option\n\t * In principle there could be other configurations, like having the uploads not all in error or stashed state, but\n\t * we trust that this hasn't happened.\n\t *\n\t * For uploads that have succeeded, now is the best time to add the relevant previews and details to the DOM\n\t * in the right order.\n\t *\n\t * @return {boolean} Whether all of the uploads are in a successful state.\n\t */\n\tuw.controller.Step.prototype.showNext = function () {\n\t\tconst okCount = this.getUploadStatesCount( this.finishState );\n\n\t\t// abort if all uploads have been removed\n\t\tif ( this.uploads.length === 0 ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.updateProgressBarCount( okCount );\n\n\t\tconst $buttons = this.ui.$div.find( '.mwe-upwiz-buttons' ).show();\n\t\t$buttons.find( '.mwe-upwiz-file-next-all-ok, .mwe-upwiz-file-next-some-failed, .mwe-upwiz-file-next-all-failed' )\n\t\t\t.hide();\n\n\t\tif ( okCount === this.uploads.length ) {\n\t\t\t$buttons.find( '.mwe-upwiz-file-next-all-ok' ).show();\n\t\t\treturn true;\n\t\t}\n\n\t\tif ( this.getUploadStatesCount( [ 'error', 'recoverable-error' ] ) === this.uploads.length ) {\n\t\t\t$buttons.find( '.mwe-upwiz-file-next-all-failed' ).show();\n\t\t} else if ( this.getUploadStatesCount( 'transporting' ) === 0 ) {\n\t\t\t$buttons.find( '.mwe-upwiz-file-next-some-failed' ).show();\n\t\t}\n\n\t\treturn false;\n\t};\n\n\t/**\n\t * @param {string|string[]} states List of upload states we want the count for\n\t * @return {number}\n\t */\n\tuw.controller.Step.prototype.getUploadStatesCount = function ( states ) {\n\t\tlet count = 0;\n\n\t\t// normalize to array of states, even though input can be 1 string\n\t\tstates = Array.isArray( states ) ? states : [ states ];\n\n\t\tthis.uploads.forEach( ( upload ) => {\n\t\t\tif ( states.includes( upload.state ) ) {\n\t\t\t\tcount++;\n\t\t\t}\n\t\t} );\n\n\t\treturn count;\n\t};\n\n\t/**\n\t * Function used by some steps to update progress bar for the whole\n\t * batch of uploads.\n\t */\n\tuw.controller.Step.prototype.updateProgressBarCount = function () {};\n\n\t/**\n\t * Check if this step has data, to test if the window can be close (i.e. if\n\t * content is going to be lost)\n\t *\n\t * @return {boolean}\n\t */\n\tuw.controller.Step.prototype.hasData = function () {\n\t\treturn this.uploads.length !== 0;\n\t};\n\n\t/**\n\t * Add an upload.\n\t *\n\t * @param {mw.UploadWizardUpload} upload\n\t */\n\tuw.controller.Step.prototype.addUpload = function ( upload ) {\n\t\tthis.uploads.push( upload );\n\t};\n\n\t/**\n\t * Remove an upload.\n\t *\n\t * @param {mw.UploadWizardUpload} upload\n\t */\n\tuw.controller.Step.prototype.removeUpload = function ( upload ) {\n\t\t// remove the upload from the uploads array\n\t\tconst index = this.uploads.indexOf( upload );\n\t\tif ( index !== -1 ) {\n\t\t\tthis.uploads.splice( index, 1 );\n\t\t}\n\n\t\t// let the upload object cleanup itself!\n\t\tupload.remove();\n\t};\n\n\t/**\n\t * Remove multiple uploads.\n\t *\n\t * @param {mw.UploadWizardUpload[]} uploads\n\t */\n\tuw.controller.Step.prototype.removeUploads = function ( uploads ) {\n\t\t// clone the array of uploads, just to be sure it's not a reference\n\t\t// to this.uploads, which will be modified (and we can't have that\n\t\t// while we're looping it)\n\t\tconst copy = uploads.slice();\n\n\t\tfor ( let i = 0; i < copy.length; i++ ) {\n\t\t\tthis.removeUpload( copy[ i ] );\n\t\t}\n\t};\n\n\t/**\n\t * Clear out uploads that are in error mode, perhaps before proceeding to the next step\n\t */\n\tuw.controller.Step.prototype.removeErrorUploads = function () {\n\t\t// We must not remove items from an array while iterating over it with $.each (it causes the\n\t\t// next item to be skipped). Find and queue them first, then remove them.\n\t\tconst toRemove = [];\n\t\tthis.uploads.forEach( ( upload ) => {\n\t\t\tif ( upload.state === 'error' || upload.state === 'recoverable-error' ) {\n\t\t\t\ttoRemove.push( upload );\n\t\t\t}\n\t\t} );\n\n\t\tthis.removeUploads( toRemove );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/controller/uw.controller.Thanks.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.controller.Step' is undefined.","line":23,"column":1,"nodeType":"Block","endLine":23,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\t/**\n\t * The thanks step.\n\t *\n\t * @class\n\t * @extends uw.controller.Step\n\t * @param {mw.Api} api\n\t * @param {Object} config UploadWizard config object.\n\t */\n\tuw.controller.Thanks = function UWControllerThanks( api, config ) {\n\t\tuw.controller.Step.call(\n\t\t\tthis,\n\t\t\tnew uw.ui.Thanks( config ),\n\t\t\tapi,\n\t\t\tconfig\n\t\t);\n\n\t\tthis.stepName = 'thanks';\n\t};\n\n\tOO.inheritClass( uw.controller.Thanks, uw.controller.Step );\n\n\tuw.controller.Thanks.prototype.load = function ( uploads ) {\n\t\tuw.controller.Step.prototype.load.call( this, uploads );\n\n\t\tif ( uploads.length === 0 ) {\n\t\t\t// We got here after the user removed all uploads; just restart from \"Upload\" step\n\t\t\tthis.moveNext();\n\t\t\treturn;\n\t\t}\n\n\t\tuploads.forEach( ( upload ) => {\n\t\t\tthis.ui.addUpload( upload );\n\t\t} );\n\n\t\tthis.emit( 'finished' );\n\t};\n\n\tuw.controller.Thanks.prototype.moveNext = function () {\n\t\t// remove all existing uploads before moving on\n\t\tmw.UploadWizardUpload.prototype.count = 0;\n\t\tthis.removeUploads( this.uploads );\n\n\t\tuw.controller.Step.prototype.moveNext.call( this );\n\t};\n\n\tuw.controller.Thanks.prototype.hasData = function () {\n\t\treturn false;\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/controller/uw.controller.Tutorial.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.controller.Step' is undefined.","line":24,"column":1,"nodeType":"Block","endLine":24,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":61,"column":3,"nodeType":"CallExpression","endLine":67,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":61,"column":3,"nodeType":"CallExpression","endLine":69,"endColumn":6}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\n\t/**\n\t * Tutorial step controller.\n\t *\n\t * @class\n\t * @extends uw.controller.Step\n\t * @param {mw.Api} api\n\t * @param {Object} config UploadWizard config object.\n\t */\n\tuw.controller.Tutorial = function UWControllerTutorial( api, config ) {\n\t\tthis.skipPreference = Boolean( mw.user.options.get( 'upwiz_skiptutorial' ) );\n\t\tthis.newSkipPreference = this.skipPreference;\n\t\tthis.skipped = false;\n\n\t\tuw.controller.Step.call(\n\t\t\tthis,\n\t\t\tnew uw.ui.Tutorial()\n\t\t\t\t.on( 'skip-tutorial-click', ( skipped ) => {\n\t\t\t\t\t// indicate that the skip preference has changed, so we can\n\t\t\t\t\t// alter the preference when we move to another step\n\t\t\t\t\tthis.newSkipPreference = skipped;\n\t\t\t\t\tthis.emit( 'change' );\n\t\t\t\t} ),\n\t\t\tapi,\n\t\t\tconfig\n\t\t);\n\n\t\tthis.stepName = 'tutorial';\n\n\t\tthis.ui.setSelected( this.skipPreference );\n\t};\n\n\tOO.inheritClass( uw.controller.Tutorial, uw.controller.Step );\n\n\t/**\n\t * Set the skip tutorial user preference via the options API\n\t *\n\t * @param {boolean} skip\n\t */\n\tuw.controller.Tutorial.prototype.setSkipPreference = function ( skip ) {\n\t\tconst allowCloseWindow = mw.confirmCloseWindow();\n\n\t\tthis.api.postWithToken( 'options', {\n\t\t\taction: 'options',\n\t\t\tchange: skip ? 'upwiz_skiptutorial=1' : 'upwiz_skiptutorial'\n\t\t} ).done( () => {\n\t\t\tallowCloseWindow.release();\n\t\t\tthis.skipPreference = skip;\n\t\t} ).fail( ( code, err ) => {\n\t\t\tmw.notify( err.textStatus );\n\t\t} );\n\t};\n\n\tuw.controller.Tutorial.prototype.load = function ( uploads ) {\n\t\t// tutorial can be skipped via preference, or config (e.g. campaign config)\n\t\tconst shouldSkipTutorial = this.skipPreference || ( this.config.tutorial && this.config.tutorial.skip );\n\n\t\tuw.controller.Step.prototype.load.call( this, uploads );\n\n\t\t// we only want to skip the tutorial once - if we come back to it, we\n\t\t// don't want it to get auto-skipped again\n\t\tif ( !this.skipped && shouldSkipTutorial ) {\n\t\t\tthis.skipped = true;\n\t\t\tthis.moveNext();\n\t\t}\n\t};\n\n\tuw.controller.Tutorial.prototype.moveNext = function () {\n\t\tuw.controller.Step.prototype.moveNext.call( this );\n\t};\n\n\tuw.controller.Tutorial.prototype.unload = function () {\n\t\tif ( this.skipPreference !== this.newSkipPreference ) {\n\t\t\tthis.setSkipPreference( this.newSkipPreference );\n\t\t}\n\n\t\tuw.controller.Step.prototype.unload.call( this );\n\t};\n\n\tuw.controller.Tutorial.prototype.hasData = function () {\n\t\treturn false;\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/controller/uw.controller.Upload.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.controller.Step' is undefined.","line":24,"column":1,"nodeType":"Block","endLine":24,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\n\t/**\n\t * Upload step controller.\n\t *\n\t * @class\n\t * @extends uw.controller.Step\n\t * @param {mw.Api} api\n\t * @param {Object} config UploadWizard config object.\n\t */\n\tuw.controller.Upload = function UWControllerUpload( api, config ) {\n\t\tuw.controller.Step.call(\n\t\t\tthis,\n\t\t\tnew uw.ui.Upload( config )\n\t\t\t\t.connect( this, {\n\t\t\t\t\tretry: 'retry'\n\t\t\t\t} ),\n\t\t\tapi,\n\t\t\tconfig\n\t\t);\n\n\t\tthis.stepName = 'file';\n\t\tthis.finishState = 'stashed';\n\n\t\tthis.queue = new uw.ConcurrentQueue( {\n\t\t\tcount: this.config.maxSimultaneousConnections,\n\t\t\taction: this.transitionOne.bind( this )\n\t\t} );\n\t\tthis.queue.on( 'complete', this.showNext.bind( this ) );\n\t\tthis.queue.on( 'change', () => this.emit( 'change' ) );\n\t\tthis.queue.on( 'progress', () => this.emit( 'change' ) );\n\t\tthis.queue.on( 'complete', () => this.emit( 'change' ) );\n\n\t\tthis.ui.on( 'files-added', ( files ) => {\n\t\t\tconst totalFiles = files.length + this.uploads.length,\n\t\t\t\ttooManyFiles = totalFiles > this.config.maxUploads;\n\n\t\t\tif ( tooManyFiles ) {\n\t\t\t\tthis.ui.showTooManyFilesError( totalFiles );\n\t\t\t} else {\n\t\t\t\tthis.addFiles( files );\n\t\t\t}\n\t\t\tthis.emit( 'change' );\n\t\t} );\n\t};\n\n\tOO.inheritClass( uw.controller.Upload, uw.controller.Step );\n\n\t/**\n\t * Updates the upload step data when a file is added or removed.\n\t */\n\tuw.controller.Upload.prototype.updateFileCounts = function () {\n\t\tconst max = this.config.maxUploads;\n\n\t\tconst haveUploads = this.uploads.length > 0;\n\t\tconst fewerThanMax = this.uploads.length < max;\n\n\t\tthis.updateProgressBarCount( this.uploads.length );\n\t\tthis.ui.updateFileCounts( haveUploads, fewerThanMax );\n\t};\n\n\tuw.controller.Upload.prototype.load = function ( uploads ) {\n\t\tuw.controller.Step.prototype.load.call( this, uploads );\n\t\tthis.updateFileCounts();\n\t\tthis.startProgressBar();\n\n\t\t// make sure queue is empty before starting this step\n\t\tthis.queue.abortExecuting();\n\n\t\tif ( uploads.length > 0 ) {\n\t\t\t/*\n\t\t\t * If we have uploads already, we'll want to to update the \"next\"\n\t\t\t * buttons accordingly. showNext() does that, but relies on upload\n\t\t\t * state being set correctly.\n\t\t\t * Since every step overwrites the upload state, we'll need to reset\n\t\t\t * it to reflect the correct upload success state.\n\t\t\t * If other files are to be added, the showNext() callback will deal\n\t\t\t * with new uploads, and still understand the existing files that\n\t\t\t * we've just reset the state for.\n\t\t\t */\n\t\t\tuploads.forEach( ( upload ) => {\n\t\t\t\tupload.state = upload.fileKey === undefined ? 'error' : this.finishState;\n\t\t\t} );\n\n\t\t\tthis.showNext();\n\t\t}\n\t};\n\n\tuw.controller.Upload.prototype.moveNext = function () {\n\t\tthis.removeErrorUploads();\n\n\t\tuw.controller.Step.prototype.moveNext.call( this );\n\t};\n\n\t/**\n\t * Starts the upload progress bar.\n\t */\n\tuw.controller.Upload.prototype.startProgressBar = function () {\n\t\tthis.ui.showProgressBar();\n\t\tthis.progressBar = new mw.GroupProgressBar( this.ui.$progress,\n\t\t\tthis.uploads,\n\t\t\t[ 'stashed' ],\n\t\t\t[ 'error' ],\n\t\t\t'transportProgress',\n\t\t\t'transportWeight' );\n\t\tthis.progressBar.start();\n\t};\n\n\t/**\n\t * Starts progress bar if there's not an existing one.\n\t */\n\tuw.controller.Upload.prototype.maybeStartProgressBar = function () {\n\t\tif ( this.progressBarEmptyOrFinished() ) {\n\t\t\tthis.startProgressBar();\n\t\t}\n\t};\n\n\t/**\n\t * Check if there is a vacancy for a new progress bar.\n\t *\n\t * @return {boolean}\n\t */\n\tuw.controller.Upload.prototype.progressBarEmptyOrFinished = function () {\n\t\treturn !this.progressBar || this.progressBar.finished === true;\n\t};\n\n\t/**\n\t * Update success count on the progress bar.\n\t *\n\t * @param {number} okCount\n\t */\n\tuw.controller.Upload.prototype.updateProgressBarCount = function ( okCount ) {\n\t\tif ( this.progressBar ) {\n\t\t\tthis.progressBar.showCount( okCount );\n\t\t}\n\t};\n\n\tuw.controller.Upload.prototype.canTransition = function ( upload ) {\n\t\treturn (\n\t\t\tuw.controller.Step.prototype.canTransition.call( this, upload ) &&\n\t\t\tupload.state === 'new'\n\t\t);\n\t};\n\n\t/**\n\t * Perform this step's changes on one upload.\n\t *\n\t * @param {mw.UploadWizardUpload} upload\n\t * @return {jQuery.Promise}\n\t */\n\tuw.controller.Upload.prototype.transitionOne = function ( upload ) {\n\t\tconst promise = upload.start();\n\t\tthis.maybeStartProgressBar();\n\t\treturn promise;\n\t};\n\n\t/**\n\t * Queue an upload object to be uploaded.\n\t *\n\t * @param {mw.UploadWizardUpload} upload\n\t */\n\tuw.controller.Upload.prototype.queueUpload = function ( upload ) {\n\t\tif ( this.canTransition( upload ) ) {\n\t\t\tthis.queue.addItem( upload );\n\t\t}\n\t};\n\n\t/**\n\t * Kick off the upload processes.\n\t */\n\tuw.controller.Upload.prototype.startQueuedUploads = function () {\n\t\tthis.queue.startExecuting();\n\t};\n\n\tuw.controller.Upload.prototype.retry = function () {\n\t\tthis.uploads.forEach( ( upload ) => {\n\t\t\tif ( upload.state === 'error' ) {\n\t\t\t\t// reset any uploads in error state back to be shiny & new\n\t\t\t\tupload.state = 'new';\n\t\t\t\tupload.ui.clearStatus();\n\t\t\t\t// and queue them\n\t\t\t\tthis.queueUpload( upload );\n\t\t\t}\n\t\t} );\n\n\t\tthis.startQueuedUploads();\n\t};\n\n\t/**\n\t * Create the upload interface, a handler to transport it to the server, and UI for the upload\n\t * itself; and immediately fill it with a file and add it to the list of uploads.\n\t *\n\t * @param {File} file\n\t * @return {mw.UploadWizardUpload|boolean} The new upload, or false if it can't be added\n\t */\n\tuw.controller.Upload.prototype.addFile = function ( file ) {\n\t\tif ( this.uploads.length >= this.config.maxUploads ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst upload = new mw.UploadWizardUpload( this, file );\n\n\t\tif ( !this.validateFile( upload ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tupload.fileChangedOk();\n\n\t\t// attach controller-specific event handlers (they're automatically\n\t\t// bound on load already, but we've only just added these files...)\n\t\tthis.bindUploadHandlers( upload );\n\n\t\tthis.setUploadFilled( upload );\n\n\t\treturn upload;\n\t};\n\n\t/**\n\t * Do everything that needs to be done to start uploading a file. Calls #addFile, then appends\n\t * each mw.UploadWizardUploadInterface to the DOM and queues thumbnails to be generated.\n\t *\n\t * @param {FileList} files\n\t */\n\tuw.controller.Upload.prototype.addFiles = function ( files ) {\n\t\tconst uploadObjs = [];\n\n\t\tfor ( let i = 0; i < files.length; i++ ) {\n\t\t\tconst file = files[ i ];\n\t\t\tconst uploadObj = this.addFile( file );\n\t\t\tif ( uploadObj ) {\n\t\t\t\tuploadObjs.push( uploadObj );\n\t\t\t}\n\t\t}\n\n\t\tthis.ui.displayUploads( uploadObjs );\n\t\tthis.updateFileCounts();\n\t};\n\n\t/**\n\t * Remove an upload from our array of uploads, and the HTML UI\n\t * We can remove the HTML UI directly, as jquery will just get the parent.\n\t * We need to grep through the array of uploads, since we don't know the current index.\n\t * We need to update file counts for obvious reasons.\n\t *\n\t * @param {mw.UploadWizardUpload} upload\n\t */\n\tuw.controller.Upload.prototype.removeUpload = function ( upload ) {\n\t\tuw.controller.Step.prototype.removeUpload.call( this, upload );\n\n\t\tthis.queue.removeItem( upload );\n\n\t\tthis.updateFileCounts();\n\n\t\t// check all uploads, if they're complete, show the next button\n\t\tthis.showNext();\n\t};\n\n\t/**\n\t * When an upload is filled with a real file, accept it in the list of uploads\n\t * and set up some other interfaces\n\t *\n\t * @param {mw.UploadWizardUpload} upload\n\t */\n\tuw.controller.Upload.prototype.setUploadFilled = function ( upload ) {\n\t\tthis.addUpload( upload );\n\t\t// Start uploads now, no reason to wait--leave the remove button alone\n\t\tthis.queueUpload( upload );\n\t\tthis.startQueuedUploads();\n\t};\n\n\t/**\n\t * Checks for file validity.\n\t *\n\t * @param {mw.UploadWizardUpload} upload\n\t * @return {boolean} Error in [code, info] format, or empty [] for no errors\n\t */\n\tuw.controller.Upload.prototype.validateFile = function ( upload ) {\n\t\tconst actualMaxSize = mw.UploadWizard.config.maxMwUploadSize,\n\n\t\t\t// Check if filename is acceptable\n\t\t\t// TODO sanitize filename\n\t\t\tfilename = upload.getFilename(),\n\t\t\tbasename = upload.getBasename();\n\n\t\t// check to see if this file has already been selected for upload\n\t\tfor ( let i = 0; i < this.uploads.length; i++ ) {\n\t\t\tif ( upload !== this.uploads[ i ] && filename === this.uploads[ i ].getFilename() ) {\n\t\t\t\tthis.ui.showDuplicateError( filename, basename );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// check if the filename is valid\n\t\tupload.setTitle( basename );\n\t\tif ( !upload.title ) {\n\t\t\tif ( !basename.includes( '.' ) ) {\n\t\t\t\tthis.ui.showMissingExtensionError( filename );\n\t\t\t\treturn false;\n\t\t\t} else {\n\t\t\t\tthis.ui.showUnparseableFilenameError( filename );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// check if extension is acceptable\n\t\tconst extension = upload.title.getExtension();\n\t\tif ( !extension ) {\n\t\t\tthis.ui.showMissingExtensionError( filename );\n\t\t\treturn false;\n\t\t}\n\n\t\tif (\n\t\t\tmw.UploadWizard.config.fileExtensions !== null &&\n\t\t\t!mw.UploadWizard.config.fileExtensions.includes( extension.toLowerCase() )\n\t\t) {\n\t\t\tthis.ui.showBadExtensionError( filename, extension );\n\t\t\treturn false;\n\t\t}\n\n\t\t// make sure the file isn't too large\n\t\t// TODO need a way to find the size of the Flickr image\n\t\tif ( upload.file.size ) {\n\t\t\tupload.transportWeight = upload.file.size;\n\t\t\tif ( upload.transportWeight > actualMaxSize ) {\n\t\t\t\tthis.ui.showFileTooLargeError( actualMaxSize, upload.transportWeight );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/controller/uw.controller.base.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/deed/dialog/uw.deed.dialog.PatentDialog.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/deed/dialog/uw.deed.dialog.base.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/deed/uw.deed.Abstract.js","messages":[{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":48,"column":14,"nodeType":"CallExpression","endLine":52,"endColumn":8},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.FieldLayout' is undefined.","line":83,"column":1,"nodeType":"Block","endLine":83,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.FieldLayout' is undefined.","line":160,"column":1,"nodeType":"Block","endLine":160,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.PatentDialog' is undefined.","line":173,"column":1,"nodeType":"Block","endLine":173,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":190,"column":1,"nodeType":"Block","endLine":190,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\t/**\n\t * Represents a generic deed.\n\t *\n\t * @class\n\t * @param {string} name The name of this step\n\t * @param {Object} config The UW config\n\t * @param {mw.UploadWizardUpload[]} uploads Array of uploads that this deed refers to\n\t */\n\tuw.deed.Abstract = function UWDeedInterface( name, config, uploads ) {\n\t\tlet tcName, details, field, input;\n\t\tthis.name = name;\n\t\tthis.config = config;\n\t\tuw.deed.Abstract.prototype.instanceCount++;\n\t\tthis.instanceCount = uw.deed.Abstract.prototype.instanceCount;\n\n\t\tthis.templateOptions = {};\n\t\tif ( config.templateOptions && config.templateOptions[ name ] ) {\n\t\t\tfor ( tcName in this.config.templateOptions[ name ] ) {\n\t\t\t\tdetails = this.config.templateOptions[ name ][ tcName ];\n\t\t\t\tinput = new OO.ui.CheckboxInputWidget( {\n\t\t\t\t\tname: tcName,\n\t\t\t\t\tvalue: details.template\n\t\t\t\t} );\n\t\t\t\tinput.on( 'change', () => this.emit( 'change' ) );\n\t\t\t\tuw.ValidatableElement.decorate( input );\n\t\t\t\tfield = new uw.FieldLayout(\n\t\t\t\t\tinput,\n\t\t\t\t\t{\n\t\t\t\t\t\tclasses: [ 'mwe-upwiz-details-templateoption' ],\n\t\t\t\t\t\tlabel: mw.message(\n\t\t\t\t\t\t\tdetails.label,\n\t\t\t\t\t\t\tuploads.length,\n\t\t\t\t\t\t\tmw.user\n\t\t\t\t\t\t).parse(),\n\t\t\t\t\t\talign: 'inline',\n\t\t\t\t\t\trequired: true // not really required, set true so \"optional\" won't display\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\tthis.templateOptions[ tcName ] = {\n\t\t\t\t\tfield: field,\n\t\t\t\t\tinput: input\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tOO.EventEmitter.call( this );\n\t};\n\tOO.mixinClass( uw.deed.Abstract, OO.EventEmitter );\n\n\t/**\n\t * @type {number}\n\t */\n\tuw.deed.Abstract.prototype.instanceCount = 0;\n\n\tuw.deed.Abstract.prototype.unload = function () {};\n\n\t/**\n\t * @return {number}\n\t */\n\tuw.deed.Abstract.prototype.getInstanceCount = function () {\n\t\treturn this.instanceCount;\n\t};\n\n\t/**\n\t * @return {uw.FieldLayout[]} Fields that need validation\n\t */\n\tuw.deed.Abstract.prototype.getFields = function () {\n\t\treturn [];\n\t};\n\n\t/**\n\t * @param {jQuery} $selector\n\t */\n\tuw.deed.Abstract.prototype.setFormFields = function () {};\n\n\t/**\n\t * @method\n\t * @abstract\n\t * @param {mw.UploadWizardUpload} upload\n\t * @return {string}\n\t */\n\tuw.deed.Abstract.prototype.getSourceWikiText = null;\n\n\t/**\n\t * @method\n\t * @abstract\n\t * @param {mw.UploadWizardUpload} upload\n\t * @return {string}\n\t */\n\tuw.deed.Abstract.prototype.getAuthorWikiText = null;\n\n\t/**\n\t * Get wikitext representing the licenses selected in the license object\n\t *\n\t * @method\n\t * @abstract\n\t * @param {mw.UploadWizardUpload} upload\n\t * @return {string} wikitext of all applicable license templates.\n\t */\n\tuw.deed.Abstract.prototype.getLicenseWikiText = null;\n\n\t/**\n\t * @return {Object}\n\t */\n\tuw.deed.Abstract.prototype.getSerialized = function () {\n\t\tconst selectedTemplateOptions = [];\n\t\tfor ( const name in this.templateOptions ) {\n\t\t\tif ( this.templateOptions[ name ].input.isSelected() ) {\n\t\t\t\tselectedTemplateOptions.push( name );\n\t\t\t}\n\t\t}\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tselectedTemplateOptions: selectedTemplateOptions\n\t\t};\n\t};\n\n\t/**\n\t * @param {Object} serialized\n\t */\n\tuw.deed.Abstract.prototype.setSerialized = function ( serialized ) {\n\t\tif ( serialized.name ) {\n\t\t\tthis.name = serialized.name;\n\t\t}\n\t\tserialized.selectedTemplateOptions.forEach( ( name ) => {\n\t\t\tthis.templateOptions[ name ].input.setSelected( true );\n\t\t} );\n\t};\n\n\t/**\n\t * @param {mw.UploadWizardUpload} upload\n\t * @return {boolean}\n\t */\n\tuw.deed.Abstract.prototype.needsPatentAgreement = function ( upload ) {\n\t\tconst extensions = this.config.patents ? this.config.patents.extensions : [];\n\n\t\treturn extensions.includes( upload.title.getExtension().toLowerCase() );\n\t};\n\n\t/**\n\t * @param {mw.UploadWizardUpload[]} uploads\n\t * @return {uw.FieldLayout}\n\t */\n\tuw.deed.Abstract.prototype.getPatentAgreementField = function ( uploads ) {\n\t\tconst field = new OO.ui.HiddenInputWidget();\n\t\tfield.on( 'change', () => this.emit( 'change' ) );\n\t\tuw.ValidatableElement.decorate( field );\n\t\tfield.validate = this.validatePatentAgreement.bind( this, field, uploads );\n\n\t\treturn new uw.FieldLayout( field );\n\t};\n\n\t/**\n\t * @param {mw.UploadWizardUpload[]} uploads\n\t * @return {uw.PatentDialog}\n\t */\n\tuw.deed.Abstract.prototype.getPatentDialog = function ( uploads ) {\n\t\tconst config = { panels: [ 'warranty' ] };\n\n\t\t// Only show filename list when in \"details\" step & we're showing the dialog for individual files\n\t\tif ( uploads[ 0 ] && uploads[ 0 ].state === 'details' ) {\n\t\t\tconfig.panels.unshift( 'filelist' );\n\t\t}\n\n\t\treturn new uw.PatentDialog( config, this.config, uploads );\n\t};\n\n\t/**\n\t * @param {OO.ui.InputWidget} input\n\t * @param {mw.UploadWizardUpload[]} uploads\n\t * @param {boolean} thorough\n\t * @return {jQuery.Promise<uw.ValidationStatus>}\n\t */\n\tuw.deed.Abstract.prototype.validatePatentAgreement = function ( input, uploads, thorough ) {\n\t\tconst status = new uw.ValidationStatus();\n\n\t\t// We only want to test this on submit\n\t\tif ( !thorough ) {\n\t\t\treturn status.resolve();\n\t\t}\n\n\t\tif ( this.patentAgreed !== true ) {\n\t\t\tconst deferred = $.Deferred();\n\t\t\tconst windowManager = new OO.ui.WindowManager();\n\t\t\tconst dialog = this.getPatentDialog( uploads );\n\n\t\t\t$( document.body ).append( windowManager.$element );\n\t\t\twindowManager.addWindows( [ dialog ] );\n\t\t\twindowManager.openWindow( dialog );\n\n\t\t\tdialog.on( 'disagree', () => {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-patent-disagree' ) );\n\t\t\t\tdeferred.reject();\n\t\t\t} );\n\t\t\tdialog.on( 'agree', () => {\n\t\t\t\tthis.patentAgreed = true;\n\t\t\t\tinput.emit( 'change' );\n\t\t\t\tdeferred.resolve();\n\t\t\t} );\n\n\t\t\treturn deferred.promise().then(\n\t\t\t\t() => status.resolve(),\n\t\t\t\t() => status.reject()\n\t\t\t);\n\t\t} else {\n\t\t\treturn status.resolve();\n\t\t}\n\t};\n\n\t/**\n\t * @return {string}\n\t */\n\tuw.deed.Abstract.prototype.getTemplateOptionsWikiText = function () {\n\t\tlet name, option, wikitext = '';\n\t\tfor ( name in this.templateOptions ) {\n\t\t\toption = this.templateOptions[ name ].input;\n\t\t\tif ( option.isSelected() ) {\n\t\t\t\twikitext += option.getValue();\n\t\t\t}\n\t\t}\n\t\treturn wikitext;\n\t};\n\n\t/**\n\t * Only implemented for OwnWork\n\t */\n\tuw.deed.Abstract.prototype.getAiPromptWikitext = function () {};\n\n\t/**\n\t * Only implemented for ThirdParty\n\t */\n\tuw.deed.Abstract.prototype.getStructuredDataFromSource = function () {};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/deed/uw.deed.Custom.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/deed/uw.deed.OwnWork.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.FieldLayout' is undefined.","line":478,"column":1,"nodeType":"Block","endLine":478,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.PatentDialog' is undefined.","line":683,"column":1,"nodeType":"Block","endLine":683,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n( function ( uw ) {\n\t/**\n\t * Set up the form and deed object for the deed option that says these uploads are all the user's own work.\n\t *\n\t * @class\n\t * @param {Object} config The UW config\n\t * @param {mw.UploadWizardUpload[]} uploads Array of uploads that this deed refers to\n\t * @param {mw.Api} api API object - useful for doing previews\n\t */\n\tuw.deed.OwnWork = function UWDeedOwnWork( config, uploads, api ) {\n\t\tlet prefAuthName = mw.user.options.get( 'upwiz_licensename' );\n\t\tconst revealOptionContent = ( $parent, $child ) => {\n\t\t\t// hide sub-content for all options\n\t\t\t$parent\n\t\t\t\t.find( '.mwe-upwiz-deed-radio-reveal' )\n\t\t\t\t// .mwe-upwiz-deed-radio-reveal reveals content when an option is\n\t\t\t\t// selected, but we need it to ignore nested instances in order not\n\t\t\t\t// to reveal content from sub-options until they've been selected\n\t\t\t\t.filter( ( i, el ) => $( el ).parentsUntil( $parent, '.mwe-upwiz-deed-radio-reveal' ).length === 0 )\n\t\t\t\t.hide();\n\t\t\t// and reveal only in the selected option\n\t\t\t$child\n\t\t\t\t.find( '.mwe-upwiz-deed-radio-reveal' )\n\t\t\t\t.filter( ( i, el ) => $( el ).parentsUntil( $child, '.mwe-upwiz-deed-radio-reveal' ).length === 0 )\n\t\t\t\t.show();\n\t\t};\n\n\t\tuw.deed.Abstract.call( this, 'ownwork', config, uploads );\n\n\t\tthis.uploadCount = uploads.length;\n\n\t\tif ( !prefAuthName ) {\n\t\t\tprefAuthName = mw.config.get( 'wgUserName' );\n\t\t}\n\n\t\t// Author, hidden\n\t\tthis.authorInput = new OO.ui.HiddenInputWidget( {\n\t\t\tname: 'author',\n\t\t\tvalue: /^({{.*}}|\\[\\[.*\\]\\])$/.test( prefAuthName.trim() ) ?\n\t\t\t\tprefAuthName :\n\t\t\t\t'[[User:' + mw.config.get( 'wgUserName' ) + '|' + prefAuthName + ']]'\n\t\t} );\n\t\tuw.ValidatableElement.decorate( this.authorInput );\n\t\tthis.authorInput.validate = ( thorough ) => {\n\t\t\tconst\n\t\t\t\tstatus = new uw.ValidationStatus(),\n\t\t\t\tminLength = this.config.minAuthorLength,\n\t\t\t\tmaxLength = this.config.maxAuthorLength,\n\t\t\t\ttext = this.authorInput.getValue().trim();\n\n\t\t\tif ( thorough !== true ) {\n\t\t\t\t// `thorough` is the strict checks executed on submit, but we don't want errors\n\t\t\t\t// to change/display every change event\n\t\t\t\treturn status.resolve();\n\t\t\t}\n\n\t\t\tif ( text === '' ) {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-signature-blank' ) );\n\t\t\t} else if ( text.length < minLength ) {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-signature-too-short', minLength ) );\n\t\t\t} else if ( text.length > maxLength ) {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-signature-too-long', maxLength ) );\n\t\t\t}\n\n\t\t\treturn status.getErrors().length === 0 ? status.resolve() : status.reject();\n\t\t};\n\t\tthis.authorInput.on( 'change', () => this.emit( 'change' ) );\n\n\t\t// Origin text input for \"work generated by an AI\" option\n\t\tthis.aiTextInput = new OO.ui.MultilineTextInputWidget( {\n\t\t\trows: 1,\n\t\t\tautosize: true,\n\t\t\tminLength: this.config.minAiInputLength,\n\t\t\t// If the maximum length is reached, the widget will stop further input addition:\n\t\t\t// add + 1, otherwise the expected error message will never display.\n\t\t\tmaxLength: this.config.maxAiInputLength + 1\n\t\t} );\n\t\tthis.aiTextInput.$element.find( 'textarea' ).on( 'click', function () {\n\t\t\t// Note: I have not fully figured out exactly why or what is the culprit,\n\t\t\t// but it appears that some node is preventing clicks from propagating,\n\t\t\t// and it's making it impossible to access this input by mouse;\n\t\t\t// this is just a workaround to resolve that\n\t\t\t$( this ).trigger( 'focus' );\n\t\t} );\n\t\tthis.aiTextInput.on( 'change', ( value ) => {\n\t\t\tthis.setAuthorInputValue( value );\n\t\t\t// let's also emit a 'change' event on the parent radio to satisfy the listener\n\t\t\t// that checks and shows/hides an error message\n\t\t\tthis.originRadio.emit( 'change' );\n\t\t\tthis.emit( 'change' );\n\t\t} );\n\t\tuw.ValidatableElement.decorate( this.aiTextInput );\n\t\tthis.aiTextInput.validate = ( thorough ) => {\n\t\t\tconst status = new uw.ValidationStatus();\n\t\t\tif ( thorough !== true ) {\n\t\t\t\t// `thorough` is the strict checks executed on submit, but we don't want errors\n\t\t\t\t// to change/display every change event\n\t\t\t\treturn status.resolve();\n\t\t\t}\n\t\t\tif ( this.originRadio.findSelectedItem().getData() !== 'ai' ) {\n\t\t\t\treturn status.resolve();\n\t\t\t}\n\t\t\tconst aiInputValue = this.aiTextInput.getValue().trim();\n\t\t\tif ( aiInputValue === '' ) {\n\t\t\t\treturn status\n\t\t\t\t\t.addError( mw.message( 'mwe-upwiz-error-question-blank' ) )\n\t\t\t\t\t.reject();\n\t\t\t}\n\t\t\tif ( aiInputValue.length < this.config.minAiInputLength ) {\n\t\t\t\treturn status\n\t\t\t\t\t.addError( mw.message( 'mwe-upwiz-error-too-short', this.config.minAiInputLength ) )\n\t\t\t\t\t.reject();\n\t\t\t}\n\t\t\tif ( aiInputValue.length > this.config.maxAiInputLength ) {\n\t\t\t\treturn status\n\t\t\t\t\t.addError( mw.message( 'mwe-upwiz-error-too-long', this.config.maxAiInputLength ) )\n\t\t\t\t\t.reject();\n\t\t\t}\n\t\t\treturn status.resolve();\n\t\t};\n\t\tthis.aiTextInputField = new uw.FieldLayout( this.aiTextInput, {\n\t\t\tlabel: $( '<div>' )\n\t\t\t\t.addClass( 'mwe-upwiz-deed-title' )\n\t\t\t\t.msg(\n\t\t\t\t\t'mwe-upwiz-source-ownwork-origin-option-ai-instruction',\n\t\t\t\t\tthis.uploadCount,\n\t\t\t\t\tmw.user\n\t\t\t\t),\n\t\t\trequired: true,\n\t\t\thelp: new OO.ui.HtmlSnippet(\n\t\t\t\tmw.message( 'mwe-upwiz-source-ownwork-origin-option-ai-description' ).parse()\n\t\t\t)\n\t\t} );\n\n\t\t// Prompt text input for \"work generated by an AI\" option\n\t\tthis.aiPromptTextInput = new OO.ui.MultilineTextInputWidget( {\n\t\t\tautosize: true,\n\t\t\t// If the maximum length is reached, the widget will stop further input addition:\n\t\t\t// add + 1, otherwise the expected error message will never display.\n\t\t\tmaxLength: this.config.maxAiInputLength + 1\n\t\t} );\n\t\tthis.aiPromptTextInput.on( 'change', () => {\n\t\t\t// let's also emit a 'change' event on the parent radio to satisfy the listener\n\t\t\t// that checks and shows/hides an error message\n\t\t\tthis.originRadio.emit( 'change' );\n\t\t\tthis.emit( 'change' );\n\t\t} );\n\t\tuw.ValidatableElement.decorate( this.aiPromptTextInput );\n\t\tthis.aiPromptTextInput.validate = ( thorough ) => {\n\t\t\tconst status = new uw.ValidationStatus();\n\t\t\tif ( thorough !== true ) {\n\t\t\t\t// `thorough` is the strict checks executed on submit, but we don't want errors\n\t\t\t\t// to change/display every change event\n\t\t\t\treturn status.resolve();\n\t\t\t}\n\t\t\tif ( this.originRadio.findSelectedItem().getData() !== 'ai' ) {\n\t\t\t\treturn status.resolve();\n\t\t\t}\n\t\t\tif ( this.aiPromptTextInput.getValue().trim().length > this.config.maxAiInputLength ) {\n\t\t\t\treturn status.addError( mw.message( 'mwe-upwiz-error-too-long', this.config.maxAiInputLength ) ).reject();\n\t\t\t}\n\t\t\treturn status.resolve();\n\t\t};\n\t\tthis.aiPromptTextInput.$element.find( 'textarea' ).on( 'click', function () {\n\t\t\t// see also this.aiTextInput.$element.find( 'textarea' ).on( 'click' ) above\n\t\t\t$( this ).trigger( 'focus' );\n\t\t} );\n\t\tthis.aiPromptTextInputField = new uw.FieldLayout( this.aiPromptTextInput, {\n\t\t\tlabel: $( '<div>' )\n\t\t\t\t.addClass( 'mwe-upwiz-deed-title' )\n\t\t\t\t.addClass( 'mwe-upwiz-ai-prompt' )\n\t\t\t\t.msg( 'mwe-upwiz-source-ownwork-origin-option-ai-prompt' ),\n\t\t\t// not actually required, but we don't want to display \"optional\"\n\t\t\trequired: true\n\t\t} );\n\n\t\t// Main origin radio\n\t\tthis.originRadio = new OO.ui.RadioSelectWidget( {\n\t\t\titems: [\n\t\t\t\tnew OO.ui.RadioOptionWidget( {\n\t\t\t\t\tlabel: mw.message(\n\t\t\t\t\t\t'mwe-upwiz-source-ownwork-origin-option-own',\n\t\t\t\t\t\tthis.uploadCount,\n\t\t\t\t\t\tmw.user\n\t\t\t\t\t).parse(),\n\t\t\t\t\tdata: 'own'\n\t\t\t\t} ),\n\t\t\t\tnew OO.ui.RadioOptionWidget( {\n\t\t\t\t\tlabel: $( '<div>' )\n\t\t\t\t\t\t.msg(\n\t\t\t\t\t\t\t'mwe-upwiz-source-ownwork-origin-option-others',\n\t\t\t\t\t\t\tthis.uploadCount,\n\t\t\t\t\t\t\tmw.user\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.append(\n\t\t\t\t\t\t\t$( '<div>' )\n\t\t\t\t\t\t\t\t.addClass( 'mwe-upwiz-deed-origin-others-container' )\n\t\t\t\t\t\t\t\t.addClass( 'mwe-upwiz-deed-radio-reveal' )\n\t\t\t\t\t\t\t\t.addClass( 'mwe-upwiz-deed-subgroup' )\n\t\t\t\t\t\t\t\t// this.originOthersRadioField.$element will be appended in here\n\t\t\t\t\t\t\t\t// once it has been created (see below)\n\t\t\t\t\t\t\t\t.hide()\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.contents(),\n\t\t\t\t\tdata: 'others'\n\t\t\t\t} ),\n\t\t\t\tnew OO.ui.RadioOptionWidget( {\n\t\t\t\t\tlabel: $( '<div>' )\n\t\t\t\t\t\t.append(\n\t\t\t\t\t\t\tmw.message( 'mwe-upwiz-source-ownwork-origin-option-ai', this.uploadCount, mw.user ).parse()\n\t\t\t\t\t\t).append(\n\t\t\t\t\t\t\t$( '<div>' )\n\t\t\t\t\t\t\t\t.addClass( 'mwe-upwiz-deed-radio-reveal' )\n\t\t\t\t\t\t\t\t.addClass( 'mwe-upwiz-deed-subgroup' )\n\t\t\t\t\t\t\t\t.append(\n\t\t\t\t\t\t\t\t\tthis.aiTextInputField.$element,\n\t\t\t\t\t\t\t\t\tthis.aiPromptTextInputField.$element\n\t\t\t\t\t\t\t\t).hide()\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.contents(),\n\t\t\t\t\tdata: 'ai'\n\t\t\t\t} )\n\t\t\t],\n\t\t\tclasses: [ 'mwe-upwiz-deed-origin' ]\n\t\t} );\n\t\tthis.originRadio.on( 'select', ( selectedOption ) => {\n\t\t\trevealOptionContent( this.originRadio.$element, selectedOption.$element );\n\n\t\t\t// let's also emit a 'change' event to satisfy the listener that checks\n\t\t\t// and shows/hides an error message\n\t\t\tthis.originRadio.emit( 'change' );\n\t\t\tthis.emit( 'change' );\n\t\t} );\n\n\t\t// Origin sub-radio for \"work of others\" option\n\t\tthis.originOthersRadio = new OO.ui.RadioSelectWidget( {\n\t\t\titems: [\n\t\t\t\tnew OO.ui.RadioOptionWidget( {\n\t\t\t\t\tlabel: new OO.ui.HtmlSnippet(\n\t\t\t\t\t\tmw.message(\n\t\t\t\t\t\t\t'mwe-upwiz-source-ownwork-origin-option-others-freelicense',\n\t\t\t\t\t\t\tthis.uploadCount,\n\t\t\t\t\t\t\tmw.user\n\t\t\t\t\t\t).parse()\n\t\t\t\t\t),\n\t\t\t\t\tdata: 'freelicense'\n\t\t\t\t} ),\n\t\t\t\tnew OO.ui.RadioOptionWidget( {\n\t\t\t\t\tlabel: mw.message(\n\t\t\t\t\t\t'mwe-upwiz-source-ownwork-origin-option-others-nocopyright',\n\t\t\t\t\t\tthis.uploadCount,\n\t\t\t\t\t\tmw.user\n\t\t\t\t\t).parse(),\n\t\t\t\t\tdata: 'nocopyright'\n\t\t\t\t} ),\n\t\t\t\tnew OO.ui.RadioOptionWidget( {\n\t\t\t\t\tlabel: $( '<div>' )\n\t\t\t\t\t\t.msg(\n\t\t\t\t\t\t\t'mwe-upwiz-source-ownwork-origin-option-others-copyrighted',\n\t\t\t\t\t\t\tthis.uploadCount,\n\t\t\t\t\t\t\tmw.user\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.append(\n\t\t\t\t\t\t\tnew OO.ui.MessageWidget( {\n\t\t\t\t\t\t\t\ttype: 'warning',\n\t\t\t\t\t\t\t\tlabel: new OO.ui.HtmlSnippet(\n\t\t\t\t\t\t\t\t\tmw.message(\n\t\t\t\t\t\t\t\t\t\t'mwe-upwiz-source-ownwork-origin-option-others-copyrighted-warning-text',\n\t\t\t\t\t\t\t\t\t\tthis.uploadCount,\n\t\t\t\t\t\t\t\t\t\tmw.user\n\t\t\t\t\t\t\t\t\t).parse()\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tclasses: [ 'mwe-upwiz-deed-warning', 'mwe-upwiz-deed-radio-reveal' ]\n\t\t\t\t\t\t\t} ).$element.hide()\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.contents(),\n\t\t\t\t\tdata: 'copyrighted'\n\t\t\t\t} ),\n\t\t\t\tnew OO.ui.RadioOptionWidget( {\n\t\t\t\t\tlabel: $( '<div>' )\n\t\t\t\t\t\t.msg(\n\t\t\t\t\t\t\t'mwe-upwiz-source-ownwork-origin-option-others-unknown',\n\t\t\t\t\t\t\tthis.uploadCount,\n\t\t\t\t\t\t\tmw.user\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.append(\n\t\t\t\t\t\t\tnew OO.ui.MessageWidget( {\n\t\t\t\t\t\t\t\ttype: 'warning',\n\t\t\t\t\t\t\t\tlabel: new OO.ui.HtmlSnippet(\n\t\t\t\t\t\t\t\t\tmw.message(\n\t\t\t\t\t\t\t\t\t\t'mwe-upwiz-source-ownwork-origin-option-others-unknown-warning-text',\n\t\t\t\t\t\t\t\t\t\tthis.uploadCount,\n\t\t\t\t\t\t\t\t\t\tmw.user\n\t\t\t\t\t\t\t\t\t).parse()\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tclasses: [ 'mwe-upwiz-deed-warning', 'mwe-upwiz-deed-radio-reveal' ]\n\t\t\t\t\t\t\t} ).$element.hide()\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.contents(),\n\t\t\t\t\tdata: 'unknown'\n\t\t\t\t} )\n\t\t\t]\n\t\t} );\n\t\tuw.ValidatableElement.decorate( this.originOthersRadio );\n\t\tthis.originOthersRadio.validate = ( thorough ) => {\n\t\t\tconst status = new uw.ValidationStatus();\n\t\t\tif ( thorough !== true ) {\n\t\t\t\t// `thorough` is the strict checks executed on submit, but we don't want errors\n\t\t\t\t// to change/display every change event\n\t\t\t\treturn status.resolve();\n\t\t\t}\n\t\t\tif ( this.originOthersRadio.findSelectedItem() === null ) {\n\t\t\t\treturn status\n\t\t\t\t\t.addError( mw.message( 'mwe-upwiz-deeds-require-selection' ) )\n\t\t\t\t\t.reject();\n\t\t\t}\n\t\t\treturn status.resolve();\n\t\t};\n\t\tthis.originOthersRadio.on( 'select', ( selectedOption ) => {\n\t\t\trevealOptionContent( this.originOthersRadio.$element, selectedOption.$element );\n\n\t\t\t// let's also emit a 'change' event on the parent radio to satisfy the listener\n\t\t\t// that checks and shows/hides an error message\n\t\t\tthis.originRadio.emit( 'change' );\n\t\t\tthis.emit( 'change' );\n\t\t} );\n\t\tthis.originOthersRadioField = new uw.FieldLayout( this.originOthersRadio, {\n\t\t\tlabel: mw.message( 'mwe-upwiz-source-ownwork-origin-option-others-subquestion', this.uploadCount, mw.user ).text(),\n\t\t\trequired: true\n\t\t} );\n\t\tthis.originRadio.$element.find( '.mwe-upwiz-deed-origin-others-container' ).append( this.originOthersRadioField.$element );\n\t\tuw.ValidatableElement.decorate( this.originRadio );\n\t\tthis.originRadio.validate = ( thorough ) => {\n\t\t\tconst status = new uw.ValidationStatus(),\n\t\t\t\t// validate sub option (even if tht choice is not selected),\n\t\t\t\t// allowing any previous errors to be cleared\n\t\t\t\tothersValidationPromise = this.originOthersRadioField.validate( thorough );\n\n\t\t\tif ( thorough === true && this.originRadio.findSelectedItem() === null ) {\n\t\t\t\treturn status\n\t\t\t\t\t.addError( mw.message( 'mwe-upwiz-deeds-require-selection' ) )\n\t\t\t\t\t.reject();\n\t\t\t}\n\t\t\tif ( this.originRadio.findSelectedItem().getData() === 'others' ) {\n\t\t\t\t// this method should reflect success/failures within the child widget,\n\t\t\t\t// but not relay that one's errors; i.e. resolve/reject accordingly,\n\t\t\t\t// but without errors, since those will already be shown within the child\n\t\t\t\treturn othersValidationPromise.then(\n\t\t\t\t\t() => status.resolve(),\n\t\t\t\t\t() => status.reject()\n\t\t\t\t);\n\t\t\t}\n\t\t\treturn status.resolve();\n\t\t};\n\n\t\tthis.originRadioField = new uw.FieldLayout( this.originRadio, {\n\t\t\tlabel: $( '<div>' ).append(\n\t\t\t\t$( '<li>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-label-title' )\n\t\t\t\t\t.append(\n\t\t\t\t\t\tmw.message(\n\t\t\t\t\t\t\t'mwe-upwiz-source-ownwork-origin-label',\n\t\t\t\t\t\t\tthis.uploadCount,\n\t\t\t\t\t\t\tmw.user\n\t\t\t\t\t\t).parseDom()\n\t\t\t\t\t)\n\t\t\t),\n\t\t\trequired: true\n\t\t} );\n\n\t\tthis.licenseInput = new mw.UploadWizardLicenseInput(\n\t\t\tthis.config.licensing.ownWork,\n\t\t\tthis.uploadCount,\n\t\t\tapi\n\t\t);\n\t\tthis.licenseInput.on( 'change', () => this.emit( 'change' ) );\n\t\tthis.licenseInput.$element.addClass( 'mwe-upwiz-deed-forms' );\n\t\tthis.licenseInputField = new uw.FieldLayout( this.licenseInput, {\n\t\t\tlabel: $( '<div>' ).append(\n\t\t\t\t$( '<li>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-label-title' )\n\t\t\t\t\t.append( mw.message( 'mwe-upwiz-source-ownwork-question', this.uploadCount, mw.user ).parseDom() )\n\t\t\t),\n\t\t\trequired: true\n\t\t} );\n\n\t\tthis.purposeRadio = new OO.ui.RadioSelectWidget( {\n\t\t\titems: [\n\t\t\t\tnew OO.ui.RadioOptionWidget( {\n\t\t\t\t\tlabel: mw.message( 'mwe-upwiz-source-ownwork-purpose-option-knowledge', this.uploadCount, mw.user ).text(),\n\t\t\t\t\tdata: 'knowledge'\n\t\t\t\t} ),\n\t\t\t\tnew OO.ui.RadioOptionWidget( {\n\t\t\t\t\tlabel: $( '<div>' ).msg( 'mwe-upwiz-source-ownwork-purpose-option-personal-use', this.uploadCount, mw.user )\n\t\t\t\t\t\t.append(\n\t\t\t\t\t\t\tnew OO.ui.MessageWidget( {\n\t\t\t\t\t\t\t\ttype: 'warning',\n\t\t\t\t\t\t\t\tlabel: new OO.ui.HtmlSnippet(\n\t\t\t\t\t\t\t\t\tmw.message(\n\t\t\t\t\t\t\t\t\t\t'mwe-upwiz-source-ownwork-purpose-option-personal-warning',\n\t\t\t\t\t\t\t\t\t\tthis.uploadCount\n\t\t\t\t\t\t\t\t\t).parseDom()\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\tclasses: [ 'mwe-upwiz-deed-warning', 'mwe-upwiz-deed-radio-reveal' ]\n\t\t\t\t\t\t\t} ).$element.hide()\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.contents(),\n\t\t\t\t\tdata: 'personal'\n\t\t\t\t} )\n\t\t\t],\n\t\t\tclasses: [ 'mwe-upwiz-deed-purpose' ]\n\t\t} );\n\t\tthis.purposeRadio.on( 'select', ( selectedOption ) => {\n\t\t\trevealOptionContent( this.purposeRadio.$element, selectedOption.$element );\n\n\t\t\t// let's also emit a 'change' event to satisfy the listener that checks\n\t\t\t// and shows/hides an error message\n\t\t\tthis.purposeRadio.emit( 'change' );\n\t\t\tthis.emit( 'change' );\n\t\t} );\n\t\tuw.ValidatableElement.decorate( this.purposeRadio );\n\t\tthis.purposeRadio.validate = ( thorough ) => {\n\t\t\tconst status = new uw.ValidationStatus();\n\t\t\tif ( thorough !== true ) {\n\t\t\t\t// `thorough` is the strict checks executed on submit, but we don't want errors\n\t\t\t\t// to change/display every change event\n\t\t\t\treturn status.resolve();\n\t\t\t}\n\t\t\tif (\n\t\t\t\tthis.purposeRadio.isElementAttached() &&\n\t\t\t\t!this.purposeRadio.findSelectedItems()\n\t\t\t) {\n\t\t\t\treturn status\n\t\t\t\t\t.addError( mw.message( 'mwe-upwiz-deeds-require-selection' ) )\n\t\t\t\t\t.reject();\n\t\t\t}\n\t\t\treturn status.resolve();\n\t\t};\n\t\tthis.purposeField = new uw.FieldLayout( this.purposeRadio, {\n\t\t\tlabel: $( '<div>' ).append(\n\t\t\t\t$( '<li>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-label-title' )\n\t\t\t\t\t.append( mw.message( 'mwe-upwiz-source-ownwork-purpose-label', this.uploadCount, mw.user ).parseDom() )\n\t\t\t),\n\t\t\trequired: true\n\t\t} );\n\n\t\t// grant patent license\n\t\tthis.threeDCount = uploads.filter( this.needsPatentAgreement.bind( this ) ).length;\n\t\tif ( this.threeDCount > 0 ) {\n\t\t\tthis.patentAgreementField = this.getPatentAgreementField( uploads );\n\t\t}\n\t};\n\n\tOO.inheritClass( uw.deed.OwnWork, uw.deed.Abstract );\n\n\tuw.deed.OwnWork.prototype.unload = function () {\n\t\tthis.licenseInput.unload();\n\t};\n\n\t/**\n\t * @return {uw.FieldLayout[]} Fields that need validation\n\t */\n\tuw.deed.OwnWork.prototype.getFields = function () {\n\t\tconst fields = [ this.originRadioField, this.licenseInputField ];\n\n\t\t// don't validate purpose field in campaigns (it is not shown)\n\t\tif ( !mw.util.getParamValue( 'campaign' ) ) {\n\t\t\tfields.push( this.purposeField );\n\t\t}\n\n\t\tif ( this.threeDCount > 0 ) {\n\t\t\tfields.push( this.patentAgreementField );\n\t\t}\n\n\t\tif (\n\t\t\tthis.originRadio.findSelectedItem() &&\n\t\t\tthis.originRadio.findSelectedItem().getData() === 'ai'\n\t\t) {\n\t\t\tfields.push( this.aiTextInputField );\n\t\t\tfields.push( this.aiPromptTextInputField );\n\t\t}\n\n\t\treturn fields;\n\t};\n\n\tuw.deed.OwnWork.prototype.setFormFields = function ( $selector ) {\n\t\tconst $formFields = $( '<ol>' ).append(\n\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-ownwork-origin' )\n\t\t\t\t.append( this.originRadioField.$element ),\n\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-ownwork-license' )\n\t\t\t\t.append( this.licenseInputField.$element )\n\t\t);\n\n\t\t// show the purpose field if we aren't in a campaign and the user isn't autoconfirmed\n\t\tif (\n\t\t\t!mw.util.getParamValue( 'campaign' ) &&\n\t\t\t!mw.config.get( 'wgUserGroups' ).includes( 'autoconfirmed' )\n\t\t) {\n\t\t\t$formFields.append(\n\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-ownwork-purpose' )\n\t\t\t\t\t.append( this.purposeField.$element )\n\t\t\t);\n\t\t}\n\n\t\t// hidden inputs\n\t\t$formFields.append( this.authorInput.$element );\n\t\tif ( this.threeDCount > 0 ) {\n\t\t\t$formFields.append( this.patentAgreementField.$element );\n\t\t}\n\n\t\t$selector.append(\n\t\t\t$( '<form>' )\n\t\t\t\t.append( $( '<div>' ).addClass( 'mwe-upwiz-deed-form-internal' ).append( $formFields ) )\n\t\t);\n\n\t\tthis.setDefaultLicense();\n\t};\n\n\t/**\n\t * OwnWork's default value is different to the default LicenseInput defaults...\n\t * LicenseInput supports multiple default values, but this one does not.\n\t */\n\tuw.deed.OwnWork.prototype.setDefaultLicense = function () {\n\t\tconst defaultLicense = {};\n\t\tconst defaultLicenseKey = this.getDefaultLicense();\n\t\tif ( defaultLicenseKey ) {\n\t\t\tdefaultLicense[ defaultLicenseKey ] = true;\n\t\t\tthis.licenseInput.setValues( defaultLicense );\n\t\t}\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.deed.OwnWork.prototype.getSourceWikiText = function () {\n\t\treturn '{{own}}';\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.deed.OwnWork.prototype.getAuthorWikiText = function () {\n\t\tconst author = this.getAuthorInputValue();\n\n\t\tif ( author ) {\n\t\t\treturn author;\n\t\t}\n\n\t\treturn '[[User:' + mw.config.get( 'wgUserName' ) + ']]';\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.deed.OwnWork.prototype.getLicenseWikiText = function ( upload ) {\n\t\tlet wikitext = '';\n\n\t\twikitext += this.licenseInput.getWikiText();\n\n\t\tif ( this.needsPatentAgreement( upload ) ) {\n\t\t\twikitext += '\\n{{' + this.config.patents.template + '|ownwork}}';\n\t\t}\n\n\t\tif (\n\t\t\tthis.originRadio.findSelectedItem() &&\n\t\t\tthis.originRadio.findSelectedItem().getData() === 'ai'\n\t\t) {\n\t\t\twikitext += '\\n{{PD-algorithm}}';\n\t\t}\n\n\t\treturn wikitext;\n\t};\n\n\t/**\n\t * @return {string}\n\t */\n\tuw.deed.OwnWork.prototype.getAiPromptWikitext = function () {\n\t\tlet prompt, wikitext = '';\n\n\t\tif (\n\t\t\tthis.originRadio.findSelectedItem() &&\n\t\t\tthis.originRadio.findSelectedItem().getData() === 'ai'\n\t\t) {\n\t\t\tprompt = this.aiPromptTextInput.getValue().trim();\n\t\t\tif ( prompt ) {\n\t\t\t\twikitext = '{{Prompt|' + prompt + '}}';\n\t\t\t}\n\t\t}\n\n\t\treturn wikitext;\n\t};\n\n\t/**\n\t * There's no getValue() on a hidden input in OOUI.\n\t * Also handle an AI-generated work.\n\t *\n\t * @return {string}\n\t */\n\tuw.deed.OwnWork.prototype.getAuthorInputValue = function () {\n\t\treturn this.authorInput.$element.val().trim();\n\t};\n\n\tuw.deed.OwnWork.prototype.setAuthorInputValue = function ( value ) {\n\t\tthis.authorInput.$element.val( value );\n\t};\n\n\t/**\n\t * @return {Object}\n\t */\n\tuw.deed.OwnWork.prototype.getSerialized = function () {\n\t\tconst serialized = Object.assign(\n\t\t\tuw.deed.Abstract.prototype.getSerialized.call( this ),\n\t\t\t{ author: this.getAuthorInputValue() }\n\t\t);\n\n\t\tserialized.origin = this.originRadio.findSelectedItem() && this.originRadio.findSelectedItem().getData();\n\t\tserialized.originOthers = this.originOthersRadio.findSelectedItem() && this.originOthersRadio.findSelectedItem().getData();\n\t\tserialized.ai = this.aiTextInput.getValue();\n\t\tserialized.aiPrompt = this.aiPromptTextInput.getValue();\n\t\tserialized.license = this.licenseInput.getSerialized();\n\t\tserialized.purpose = this.purposeRadio.findSelectedItem() && this.purposeRadio.findSelectedItem().getData();\n\n\t\treturn serialized;\n\t};\n\n\t/**\n\t * @param {Object} serialized\n\t */\n\tuw.deed.OwnWork.prototype.setSerialized = function ( serialized ) {\n\t\tuw.deed.Abstract.prototype.setSerialized.call( this, serialized );\n\n\t\tif ( serialized.author ) {\n\t\t\tthis.setAuthorInputValue( serialized.author );\n\t\t}\n\t\tif ( serialized.origin ) {\n\t\t\tthis.originRadio.selectItemByData( serialized.origin );\n\t\t}\n\t\tif ( serialized.originOthers ) {\n\t\t\tthis.originOthersRadio.selectItemByData( serialized.originOthers );\n\t\t}\n\t\tif ( serialized.ai ) {\n\t\t\tthis.aiTextInput.setValue( serialized.ai );\n\t\t}\n\t\tif ( serialized.aiPrompt ) {\n\t\t\tthis.aiPromptTextInput.setValue( serialized.aiPrompt );\n\t\t}\n\t\tthis.licenseInput.setSerialized( serialized.license );\n\t\tif ( serialized.purpose ) {\n\t\t\tthis.purposeRadio.selectItemByData( serialized.purpose );\n\t\t}\n\t};\n\n\tuw.deed.OwnWork.prototype.getDefaultLicense = function () {\n\t\tlet license;\n\t\tif (\n\t\t\tthis.config.licensing.defaultType === 'ownwork' ||\n\t\t\tthis.config.licensing.defaultType === 'choice'\n\t\t) {\n\t\t\tlicense = this.config.licensing.ownWork.defaults;\n\t\t\treturn license instanceof Array ? license[ 0 ] : license;\n\t\t}\n\t};\n\n\t/**\n\t * @param {mw.UploadWizardUpload[]} uploads\n\t * @return {uw.PatentDialog}\n\t */\n\tuw.deed.OwnWork.prototype.getPatentDialog = function ( uploads ) {\n\t\tconst config = { panels: [ 'warranty', 'license-ownership', 'license-grant' ] };\n\n\t\t// Only show filename list when in \"details\" step & we're showing the dialog for individual files\n\t\tif ( uploads[ 0 ] && uploads[ 0 ].state === 'details' ) {\n\t\t\tconfig.panels.unshift( 'filelist' );\n\t\t}\n\n\t\treturn new uw.PatentDialog( config, this.config, uploads );\n\t};\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/deed/uw.deed.ThirdParty.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.FieldLayout' is undefined.","line":139,"column":1,"nodeType":"Block","endLine":139,"endColumn":1},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":266,"column":6,"nodeType":"CallExpression","endLine":268,"endColumn":7}],"suppressedMessages":[{"ruleId":"no-undef","severity":2,"message":"'dataValues' is not defined.","line":364,"column":5,"nodeType":"Identifier","messageId":"undef","endLine":364,"endColumn":15,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'dataValues' is not defined.","line":376,"column":6,"nodeType":"Identifier","messageId":"undef","endLine":376,"endColumn":16,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'dataValues' is not defined.","line":387,"column":6,"nodeType":"Identifier","messageId":"undef","endLine":387,"endColumn":16,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\t/**\n\t * Set up the form and deed object for the deed option that says these uploads are the work of a third party.\n\t *\n\t * @class\n\t * @param {Object} config The UW config\n\t * @param {mw.UploadWizardUpload[]} uploads Array of uploads that this deed refers to\n\t * @param {mw.Api} api API object - useful for doing previews\n\t */\n\tuw.deed.ThirdParty = function UWDeedThirdParty( config, uploads, api ) {\n\t\tuw.deed.Abstract.call( this, 'thirdparty', config, uploads );\n\n\t\tthis.uploadCount = uploads.length;\n\t\tthis.threeDCount = uploads.filter( this.needsPatentAgreement.bind( this ) ).length;\n\n\t\tthis.sourceInput = new OO.ui.MultilineTextInputWidget( {\n\t\t\tautosize: true,\n\t\t\tclasses: [ 'mwe-source' ],\n\t\t\tname: 'source'\n\t\t} );\n\t\tthis.sourceInput.on( 'change', () => this.emit( 'change' ) );\n\t\tthis.sourceInput.$input.attr( 'id', 'mwe-source-' + this.getInstanceCount() );\n\t\t// See uw.DetailsWidget\n\t\tuw.ValidatableElement.decorate( this.sourceInput );\n\t\tthis.sourceInput.validate = ( thorough ) => {\n\t\t\tconst status = new uw.ValidationStatus(),\n\t\t\t\tminLength = this.config.minSourceLength,\n\t\t\t\tmaxLength = this.config.maxSourceLength,\n\t\t\t\ttext = this.sourceInput.getValue().trim();\n\n\t\t\tif ( thorough !== true ) {\n\t\t\t\t// `thorough` is the strict checks executed on submit, but we don't want errors\n\t\t\t\t// to change/display every change event\n\t\t\t\treturn status.resolve();\n\t\t\t}\n\n\t\t\tif ( text === '' ) {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-question-blank' ) );\n\t\t\t} else if ( text.length < minLength ) {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-too-short', minLength ) );\n\t\t\t} else if ( text.length > maxLength ) {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-too-long', maxLength ) );\n\t\t\t}\n\n\t\t\treturn status.getErrors().length === 0 ? status.resolve() : status.reject();\n\t\t};\n\t\tthis.sourceInputField = new uw.FieldLayout( this.sourceInput, {\n\t\t\tlabel: $( '<li>' )\n\t\t\t\t.addClass( 'mwe-upwiz-label-title' )\n\t\t\t\t.msg( 'mwe-upwiz-source-text', this.uploadCount, mw.user ),\n\t\t\trequired: true\n\t\t} );\n\n\t\tthis.authorInput = new OO.ui.MultilineTextInputWidget( {\n\t\t\tautosize: true,\n\t\t\tclasses: [ 'mwe-author' ],\n\t\t\tname: 'author'\n\t\t} );\n\t\tthis.authorInput.on( 'change', () => this.emit( 'change' ) );\n\t\tthis.authorInput.$input.attr( 'id', 'mwe-author-' + this.getInstanceCount() );\n\t\tuw.ValidatableElement.decorate( this.authorInput );\n\t\tthis.authorInput.validate = ( thorough ) => {\n\t\t\tconst status = new uw.ValidationStatus(),\n\t\t\t\tminLength = this.config.minAuthorLength,\n\t\t\t\tmaxLength = this.config.maxAuthorLength,\n\t\t\t\ttext = this.authorInput.getValue().trim();\n\n\t\t\tif ( thorough !== true ) {\n\t\t\t\t// `thorough` is the strict checks executed on submit, but we don't want errors\n\t\t\t\t// to change/display every change event\n\t\t\t\treturn status.resolve();\n\t\t\t}\n\n\t\t\tif ( this.authorInput.isDisabled() ) {\n\t\t\t\treturn status.resolve();\n\t\t\t}\n\n\t\t\tif ( text === '' ) {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-question-blank' ) );\n\t\t\t} else if ( text.length < minLength ) {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-too-short', minLength ) );\n\t\t\t} else if ( text.length > maxLength ) {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-too-long', maxLength ) );\n\t\t\t}\n\n\t\t\treturn status.getErrors().length === 0 ? status.resolve() : status.reject();\n\t\t};\n\t\tthis.authorInputField = new uw.FieldLayout( this.authorInput, {\n\t\t\tlabel: $( '<li>' )\n\t\t\t\t.addClass( 'mwe-upwiz-label-title' )\n\t\t\t\t.msg( 'mwe-upwiz-author-text', this.uploadCount, mw.user ),\n\t\t\trequired: true\n\t\t} );\n\n\t\tthis.licenseInput = new mw.UploadWizardLicenseInput(\n\t\t\tthis.config.licensing.thirdParty,\n\t\t\tthis.uploadCount,\n\t\t\tapi\n\t\t);\n\t\tthis.licenseInput.on( 'change', () => this.emit( 'change' ) );\n\t\tthis.licenseInput.$element.addClass( 'mwe-upwiz-deed-license-groups' );\n\t\tthis.licenseInput.setDefaultValues();\n\t\tthis.licenseInputField = new uw.FieldLayout( this.licenseInput, {\n\t\t\tlabel: $( '<li>' )\n\t\t\t\t.addClass( 'mwe-upwiz-label-title' )\n\t\t\t\t.append( mw.message( 'mwe-upwiz-source-thirdparty-cases-title', this.uploadCount, mw.user ).parseDom() ),\n\t\t\trequired: true\n\t\t} );\n\n\t\tif ( this.threeDCount > 0 ) {\n\t\t\tthis.patentAgreementField = this.getPatentAgreementField( uploads );\n\t\t}\n\t};\n\n\tOO.inheritClass( uw.deed.ThirdParty, uw.deed.Abstract );\n\n\tuw.deed.ThirdParty.prototype.unload = function () {\n\t\tthis.licenseInput.unload();\n\t};\n\n\t/**\n\t * @return {uw.FieldLayout[]} Fields that need validation\n\t */\n\tuw.deed.ThirdParty.prototype.getFields = function () {\n\t\tconst fields = [\n\t\t\tthis.authorInputField,\n\t\t\tthis.sourceInputField,\n\t\t\tthis.licenseInputField\n\t\t];\n\t\tif ( this.threeDCount > 0 ) {\n\t\t\tfields.push( this.patentAgreementField );\n\t\t}\n\t\treturn fields;\n\t};\n\n\tuw.deed.ThirdParty.prototype.setFormFields = function ( $selector ) {\n\t\tconst $formFields = $( '<div>' ).addClass( 'mwe-upwiz-deed-form-internal' );\n\n\t\tthis.$form = $( '<form>' );\n\n\t\t$formFields.append(\n\t\t\t$( '<div>' )\n\t\t\t\t.addClass( 'mwe-upwiz-source-thirdparty-explain' )\n\t\t\t\t.msg( 'mwe-upwiz-source-thirdparty-explain' )\n\t\t);\n\n\t\tif ( this.uploadCount > 1 ) {\n\t\t\t$formFields.append(\n\t\t\t\t$( '<div>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-source-thirdparty-custom-multiple-intro' )\n\t\t\t\t\t.msg( 'mwe-upwiz-source-thirdparty-custom-multiple-intro' )\n\t\t\t);\n\t\t}\n\n\t\t$formFields.append(\n\t\t\t$( '<ol>' ).append(\n\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-thirdparty-license' )\n\t\t\t\t\t.append( this.licenseInputField.$element ),\n\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-thirdparty-fields' )\n\t\t\t\t\t.append( this.sourceInputField.$element ),\n\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-thirdparty-fields' )\n\t\t\t\t\t.append( this.authorInputField.$element )\n\t\t\t)\n\t\t);\n\n\t\tif ( this.templateOptions.aiGenerated ) {\n\t\t\t// add the element inside sourceInputField so any error msgs will be displayed for\n\t\t\t// the field (containing the text input and checkbox) rather than just the text input\n\t\t\tthis.sourceInput.$element.after(\n\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-thirdparty-checkbox' )\n\t\t\t\t\t.append( this.templateOptions.aiGenerated.field.$element )\n\t\t\t);\n\t\t\tthis.templateOptions.aiGenerated.input.$element.on( 'change', () => {\n\t\t\t\tthis.updateAuthorFieldForAI();\n\t\t\t} );\n\n\t\t\t// Set up ai-relevant help text for the author input field that can be shown\n\t\t\t// whenever the checkbox is selected\n\t\t\tthis.authorInputField.help = new OO.ui.LabelWidget( {\n\t\t\t\tlabel: $( '<div>' ).msg( 'mwe-upwiz-author-text-ai-help' )\n\t\t\t} );\n\t\t\tthis.authorInput.$element.after(\n\t\t\t\tthis.authorInputField.help.$element.addClass( 'mwe-upwiz-details-help' )\n\t\t\t);\n\t\t\tthis.authorInputField.help.$element.hide();\n\t\t}\n\t\tif ( this.templateOptions.authorUnknown ) {\n\t\t\t// add the element inside authorInputField so any error msgs will be displayed for\n\t\t\t// the field (containing the text input and checkbox) rather than just the text input\n\t\t\tif ( this.templateOptions.aiGenerated ) {\n\t\t\t\tthis.authorInputField.help.$element.after(\n\t\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-thirdparty-checkbox' )\n\t\t\t\t\t\t.append( this.templateOptions.authorUnknown.field.$element )\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tthis.authorInput.$element.after(\n\t\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-thirdparty-checkbox' )\n\t\t\t\t\t\t.append( this.templateOptions.authorUnknown.field.$element )\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.templateOptions.authorUnknown.input.$element.on( 'change', () => {\n\t\t\t\tif ( this.templateOptions.authorUnknown.input.isSelected() ) {\n\t\t\t\t\tthis.authorInput.setDisabled( true );\n\t\t\t\t\tthis.authorInput.setValue( '' );\n\t\t\t\t\tthis.authorInputField.validate( false );\n\t\t\t\t} else {\n\t\t\t\t\tthis.authorInput.setDisabled( false );\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\n\t\tif ( this.threeDCount > 0 ) {\n\t\t\t$formFields.append( this.patentAgreementField.$element );\n\t\t}\n\n\t\tthis.$form.append( $formFields );\n\n\t\t$selector.append( this.$form );\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.deed.ThirdParty.prototype.updateAuthorFieldForAI = function () {\n\t\tif ( this.templateOptions.aiGenerated.input.isSelected() ) {\n\t\t\tthis.authorInputField.setLabel(\n\t\t\t\t$( '<li>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-label-title' )\n\t\t\t\t\t.msg( 'mwe-upwiz-author-text-ai' )\n\t\t\t);\n\t\t\tthis.authorInputField.help.$element.show();\n\t\t\tif ( this.templateOptions.authorUnknown ) {\n\t\t\t\tthis.templateOptions.authorUnknown.field.setLabel(\n\t\t\t\t\tmw.message(\n\t\t\t\t\t\t'mwe-upwiz-author-not-known'\n\t\t\t\t\t).text()\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tthis.authorInputField.setLabel(\n\t\t\t\t$( '<li>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-label-title' )\n\t\t\t\t\t.msg( 'mwe-upwiz-author-text' )\n\t\t\t);\n\t\t\tthis.authorInputField.help.$element.hide();\n\t\t\tif ( this.templateOptions.authorUnknown ) {\n\t\t\t\tthis.templateOptions.authorUnknown.field.setLabel(\n\t\t\t\t\tmw.message(\n\t\t\t\t\t\tthis.config.templateOptions.thirdparty.authorUnknown.label\n\t\t\t\t\t).text()\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.deed.Abstract.prototype.getSourceWikiText = function () {\n\t\treturn this.sourceInput.getValue();\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.deed.Abstract.prototype.getAuthorWikiText = function () {\n\t\treturn this.authorInput.getValue() || '{{Unknown|author}}';\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.deed.Abstract.prototype.getLicenseWikiText = function ( upload ) {\n\t\tlet wikitext = this.licenseInput.getWikiText();\n\n\t\tif ( this.needsPatentAgreement( upload ) ) {\n\t\t\twikitext += '\\n{{' + this.config.patents.template + '}}';\n\t\t}\n\n\t\treturn wikitext;\n\t};\n\n\t/**\n\t * @return {Object}\n\t */\n\tuw.deed.ThirdParty.prototype.getSerialized = function () {\n\t\treturn Object.assign( uw.deed.Abstract.prototype.getSerialized.call( this ), {\n\t\t\tsource: this.sourceInput.getValue(),\n\t\t\tauthor: this.authorInput.getValue(),\n\t\t\tlicense: this.licenseInput.getSerialized()\n\t\t} );\n\t};\n\n\t/**\n\t * @param {Object} serialized\n\t */\n\tuw.deed.ThirdParty.prototype.setSerialized = function ( serialized ) {\n\t\tuw.deed.Abstract.prototype.setSerialized.call( this, serialized );\n\n\t\tif ( serialized.source ) {\n\t\t\tthis.sourceInput.setValue( serialized.source );\n\t\t}\n\t\tif ( serialized.author ) {\n\t\t\tthis.authorInput.setValue( serialized.author );\n\t\t}\n\t\tif ( serialized.license ) {\n\t\t\tthis.licenseInput.setSerialized( serialized.license );\n\t\t}\n\n\t\tif ( this.templateOptions.authorUnknown ) {\n\t\t\tthis.authorInput.setDisabled(\n\t\t\t\t!!this.templateOptions.authorUnknown.input.isSelected()\n\t\t\t);\n\n\t\t\tif ( this.templateOptions.aiGenerated ) {\n\t\t\t\tthis.updateAuthorFieldForAI();\n\t\t\t}\n\t\t}\n\t};\n\n\tuw.deed.ThirdParty.prototype.getStructuredDataFromSource = function () {\n\t\tconst source = this.getSourceWikiText(),\n\t\t\tconfig = mw.UploadWizard.config,\n\t\t\turlRegex = /^https?:\\/\\/\\S*\\.\\S*$/,\n\t\t\tsourceRegex = new RegExp(\n\t\t\t\t'(' +\n\t\t\t\tObject.keys( config.sourceStringToWikidataIdMapping ).join( '|' ) +\n\t\t\t\t')'\n\t\t\t),\n\t\t\tsourceStringMatch = sourceRegex.exec( source ) ? sourceRegex.exec( source )[ 0 ] : false;\n\n\t\tif ( !config.wikibase.enabled ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif ( !sourceStringMatch && !urlRegex.test( source ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst wbDataModel = mw.loader.require( 'wikibase.datamodel' );\n\n\t\tconst sourceClaim = new wbDataModel.Claim(\n\t\t\tnew wbDataModel.PropertyValueSnak(\n\t\t\t\tconfig.wikibase.properties.source,\n\t\t\t\t// eslint-disable-next-line no-undef\n\t\t\t\tdataValues.newDataValue( 'wikibase-entityid', {\n\t\t\t\t\tid: config.wikibase.items.file_available_on_the_internet\n\t\t\t\t} )\n\t\t\t)\n\t\t);\n\n\t\tconst sourceQualifiers = new wbDataModel.SnakList();\n\t\tif ( ( config.wikibase.properties.operator !== undefined ) && sourceStringMatch ) {\n\t\t\tsourceQualifiers.addItem(\n\t\t\t\tnew wbDataModel.PropertyValueSnak(\n\t\t\t\t\tconfig.wikibase.properties.operator,\n\t\t\t\t\t// eslint-disable-next-line no-undef\n\t\t\t\t\tdataValues.newDataValue( 'wikibase-entityid', {\n\t\t\t\t\t\tid: config.sourceStringToWikidataIdMapping[ sourceStringMatch ]\n\t\t\t\t\t} )\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\t\tif ( urlRegex.test( source ) ) {\n\t\t\tsourceQualifiers.addItem(\n\t\t\t\tnew wbDataModel.PropertyValueSnak(\n\t\t\t\t\tconfig.wikibase.properties.described_at_url,\n\t\t\t\t\t// eslint-disable-next-line no-undef\n\t\t\t\t\tdataValues.newDataValue( 'string', source )\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\t\tsourceClaim.setQualifiers( sourceQualifiers );\n\n\t\tconst wbSerialization = mw.loader.require( 'wikibase.serialization' );\n\t\tconst wbSerializer = new wbSerialization.StatementSerializer();\n\t\treturn wbSerializer.serialize(\n\t\t\tnew wbDataModel.Statement( sourceClaim )\n\t\t);\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/deed/uw.deed.base.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/details/uw.CampaignDetailsWidget.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.DetailsWidget' is undefined.","line":6,"column":1,"nodeType":"Block","endLine":6,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\t/**\n\t * A custom campaign-defined field in UploadWizard's \"Details\" step form.\n\t *\n\t * @extends uw.DetailsWidget\n\t * @class\n\t * @param {Object} [config] Configuration options\n\t * @param {string} config.wikitext Wikitext template to use for this field\n\t * @param {boolean} [config.required=false] Whether to mark this field as required\n\t * @param {string} [config.type='text'] Field type, 'text' or 'select'\n\t * @param {number} [config.maxLength] Maximum allowed length of input\n\t * @param {Object} [config.options] Map of select dropdown options to use when `type` is 'text'\n\t */\n\tuw.CampaignDetailsWidget = function UWCampaignDetailsWidget( config ) {\n\t\tconfig = Object.assign( { type: 'text' }, config );\n\t\tuw.CampaignDetailsWidget.super.call( this );\n\n\t\tthis.required = !!config.required;\n\t\tthis.wikitext = config.wikitext;\n\n\t\tif ( config.type === 'text' ) {\n\t\t\tthis.input = new OO.ui.TextInputWidget( {\n\t\t\t\tclasses: [ 'mwe-idfield', 'mwe-upwiz-campaignDetailsWidget-input' ],\n\t\t\t\tmaxLength: config.maxLength\n\t\t\t} );\n\t\t\tif ( config.pattern !== undefined ) {\n\t\t\t\tthis.input.$input.attr( 'pattern', config.pattern );\n\t\t\t}\n\t\t} else if ( config.type === 'select' ) {\n\t\t\tthis.input = new OO.ui.DropdownInputWidget( {\n\t\t\t\tclasses: [ 'mwe-idfield', 'mwe-upwiz-campaignDetailsWidget-input' ],\n\t\t\t\toptions: Object.keys( config.options ).map( ( key ) => ( { data: key, label: config.options[ key ] } ) )\n\t\t\t} );\n\t\t} else {\n\t\t\tthrow new Error( 'Unknown campaign field type: ' + config.type );\n\t\t}\n\n\t\t// Aggregate 'change' event\n\t\t// (but do not flash warnings in the user's face while they're typing)\n\t\tthis.input.on( 'change', OO.ui.debounce( this.emit.bind( this, 'change' ), 500 ) );\n\n\t\tthis.$element.addClass( 'mwe-id-field mwe-upwiz-campaignDetailsWidget' );\n\t\tthis.$element.append(\n\t\t\tthis.input.$element\n\t\t);\n\t};\n\tOO.inheritClass( uw.CampaignDetailsWidget, uw.DetailsWidget );\n\tOO.mixinClass( uw.CampaignDetailsWidget, uw.ValidatableElement );\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.CampaignDetailsWidget.prototype.validate = function () {\n\t\tconst status = new uw.ValidationStatus();\n\t\tif ( this.required && this.input.getValue().trim() === '' ) {\n\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-blank' ) );\n\t\t}\n\t\treturn status.getErrors().length === 0 ? status.resolve() : status.reject();\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.CampaignDetailsWidget.prototype.getWikiText = function () {\n\t\tlet value = this.input.getValue().trim();\n\t\tif ( value ) {\n\t\t\tvalue = this.wikitext.replace( '$1', value );\n\t\t}\n\t\treturn value;\n\t};\n\n\t/**\n\t * @inheritdoc\n\t * @return {Object} See #setSerialized\n\t */\n\tuw.CampaignDetailsWidget.prototype.getSerialized = function () {\n\t\treturn {\n\t\t\tvalue: this.input.getValue()\n\t\t};\n\t};\n\n\t/**\n\t * @inheritdoc\n\t * @param {Object} serialized\n\t * @param {string} serialized.value Campaign informations text\n\t */\n\tuw.CampaignDetailsWidget.prototype.setSerialized = function ( serialized ) {\n\t\tthis.input.setValue( serialized.value );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/details/uw.CategoriesDetailsWidget.js","messages":[{"ruleId":"es-x/no-iterator-prototype-some","severity":1,"message":"ES2025 'Iterator.prototype.some' method is forbidden.","line":130,"column":40,"nodeType":"MemberExpression","messageId":"forbidden","endLine":130,"endColumn":60},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":213,"column":34,"nodeType":"CallExpression","endLine":226,"endColumn":54},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":242,"column":36,"nodeType":"CallExpression","endLine":252,"endColumn":56}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\tconst NS_CATEGORY = mw.config.get( 'wgNamespaceIds' ).category;\n\n\t/**\n\t * @constructor\n\t * @param {Object} [config] Configuration options\n\t * @param {mw.Api} [config.api] Instance of mw.Api (or subclass thereof) to use for queries\n\t * @param {number} [config.searchLimit=50] Maximum number of category search results to load\n\t * @param {number} [config.subLimit=500] Maximum number of sub-categories to load\n\t */\n\tuw.CategoriesDetailsWidget = function MWCategoryMultiselectWidget( config ) {\n\t\t// Config initialization\n\t\tconfig = Object.assign( {\n\t\t\tsearchLimit: 50,\n\t\t\tsubLimit: 500,\n\t\t\tclasses: [ 'mwe-upwiz-categoriesDetailsWidget' ]\n\t\t}, config );\n\t\tthis.searchLimit = config.searchLimit;\n\t\tthis.subLimit = config.subLimit;\n\n\t\t// Parent constructor\n\t\tuw.CategoriesDetailsWidget.super.call( this, $.extend( true, {}, config, {\n\t\t\tmenu: {\n\t\t\t\tfilterFromInput: false,\n\t\t\t\twidth: '100%'\n\t\t\t},\n\t\t\t// This allows the user to both select non-existent categories, and prevents the selector from\n\t\t\t// being wiped from #onMenuItemsChange when we change the available options in the dropdown\n\t\t\tallowArbitrary: true\n\t\t} ) );\n\n\t\t// Mixin constructors\n\t\tOO.ui.mixin.PendingElement.call( this, Object.assign( {}, config, { $pending: this.$handle } ) );\n\n\t\t// Initialize\n\t\tthis.api = config.api || new mw.Api();\n\n\t\tthis.cacheSearch = {};\n\t\tthis.cacheChildren = {};\n\n\t\t// Event handler to call the autocomplete methods\n\t\tthis.input.$input.on(\n\t\t\t'change input cut paste',\n\t\t\tOO.ui.debounce( () => this.updateMenuItems(\n\t\t\t\tthis.searchCategories( this.input.$input.val().trim() ),\n\t\t\t\tthis.input.$input.val().trim()\n\t\t\t), 100 )\n\t\t);\n\n\t\t// Keep only valid titles\n\t\tconst categories = ( mw.UploadWizard.config.defaults.categories || [] ).filter( ( cat ) => !!mw.Title.makeTitle( NS_CATEGORY, cat ) );\n\t\tthis.setValue( categories );\n\t};\n\tOO.inheritClass( uw.CategoriesDetailsWidget, OO.ui.MenuTagMultiselectWidget );\n\tOO.mixinClass( uw.CategoriesDetailsWidget, OO.ui.mixin.PendingElement );\n\tOO.mixinClass( uw.CategoriesDetailsWidget, uw.ValidatableElement );\n\n\tuw.CategoriesDetailsWidget.prototype.updateMenuItems = function ( results, input ) {\n\t\tconst arrowParent = document.documentElement.dir === 'ltr' ? '←' : '→';\n\t\tconst arrowChildren = document.documentElement.dir === 'ltr' ? '→' : '←';\n\n\t\tthis.getMenu().clearItems();\n\n\t\tthis.pushPending();\n\n\t\tresults\n\t\t\t.then( ( items ) => {\n\t\t\t\tconst menu = this.getMenu();\n\n\t\t\t\t// Never show the menu if the input lost focus in the meantime\n\t\t\t\tif ( !this.input.$input.is( ':focus' ) ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tmenu\n\t\t\t\t\t// menu should already have been cleared, but since this is an async callback,\n\t\t\t\t\t// let's clear it once more to ensure no race conditions\n\t\t\t\t\t.clearItems()\n\t\t\t\t\t.addItems(\n\t\t\t\t\t\titems\n\t\t\t\t\t\t\t.filter( ( data ) => {\n\t\t\t\t\t\t\t\t// Sense-check\n\t\t\t\t\t\t\t\tconst title = mw.Title.newFromText( data.title, NS_CATEGORY );\n\t\t\t\t\t\t\t\treturn title !== null;\n\t\t\t\t\t\t\t} )\n\t\t\t\t\t\t\t.map( ( data ) => {\n\t\t\t\t\t\t\t\t// clone data to avoid modifying the original object, as that will\n\t\t\t\t\t\t\t\t// be passed forward to the subcategory handler\n\t\t\t\t\t\t\t\tconst menuData = Object.assign( {}, data );\n\t\t\t\t\t\t\t\tconst title = mw.Title.newFromText( menuData.title, NS_CATEGORY );\n\n\t\t\t\t\t\t\t\t// ensure title is properly escaped; we'll be inserting it unescaped\n\t\t\t\t\t\t\t\t// (some will get additional HTML) later on\n\t\t\t\t\t\t\t\tlet text = title.getMainText();\n\t\t\t\t\t\t\t\ttext = $( '<span>' ).text( text ).html();\n\n\t\t\t\t\t\t\t\tif ( menuData.parent ) {\n\t\t\t\t\t\t\t\t\t// indicate that this navigates to the parent category\n\t\t\t\t\t\t\t\t\tmenuData.handler = () => this.updateMenuItems(\n\t\t\t\t\t\t\t\t\t\tmenuData.parent.results,\n\t\t\t\t\t\t\t\t\t\tmenuData.parent.input\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\ttext = arrowParent;\n\t\t\t\t\t\t\t\t} else if ( menuData.current ) {\n\t\t\t\t\t\t\t\t\t// indicate that this is the current category (and clicking it\n\t\t\t\t\t\t\t\t\t// will not navigate to its subcategories, but select it instead)\n\t\t\t\t\t\t\t\t\ttext = $( '<span>' ).addClass( 'mwe-upwiz-categories-category-title' ).text( text )[ 0 ].outerHTML;\n\t\t\t\t\t\t\t\t\ttext = mw.message( 'mwe-upwiz-categories-current', text ).text();\n\t\t\t\t\t\t\t\t} else if ( menuData.categoryinfo.subcats > 0 ) {\n\t\t\t\t\t\t\t\t\t// indicate that the category has subcategories\n\t\t\t\t\t\t\t\t\tmenuData.handler = () => this.updateMenuItems(\n\t\t\t\t\t\t\t\t\t\tthis.getSubCategories( title.getMainText() ).then( ( subcategories ) => {\n\t\t\t\t\t\t\t\t\t\t\tconst navigation = [\n\t\t\t\t\t\t\t\t\t\t\t\t// upwards navigation, back to parent\n\t\t\t\t\t\t\t\t\t\t\t\tObject.assign( {}, data, { parent: { results: results, input: input } } ),\n\t\t\t\t\t\t\t\t\t\t\t\t// current category selector (i.e. allow selection despite it having subcats)\n\t\t\t\t\t\t\t\t\t\t\t\tObject.assign( {}, data, { current: true } )\n\t\t\t\t\t\t\t\t\t\t\t];\n\t\t\t\t\t\t\t\t\t\t\treturn navigation.concat( subcategories );\n\t\t\t\t\t\t\t\t\t\t} ),\n\t\t\t\t\t\t\t\t\t\ttitle.getMainText()\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\ttext = $( '<span>' ).addClass( 'mwe-upwiz-categories-category-title' ).text( text )[ 0 ].outerHTML + ' ' + arrowChildren;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\treturn new OO.ui.MenuOptionWidget( {\n\t\t\t\t\t\t\t\t\tdata: menuData,\n\t\t\t\t\t\t\t\t\tlabel: new OO.ui.HtmlSnippet( text ),\n\t\t\t\t\t\t\t\t\tselected: !menuData.parent && this.getItems().some( ( item ) => item.data === title.getMainText() )\n\t\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t\t} )\n\t\t\t\t\t)\n\t\t\t\t\t.toggle( true );\n\t\t\t} )\n\t\t\t.always( this.popPending.bind( this ) );\n\t};\n\n\tuw.CategoriesDetailsWidget.prototype.clearInput = function () {\n\t\tuw.CategoriesDetailsWidget.super.prototype.clearInput.call( this );\n\t\t// Abort all pending requests, we won't need their results\n\t\tthis.api.abort();\n\t};\n\n\tuw.CategoriesDetailsWidget.prototype.onMenuChoose = function ( menuItem, selected ) {\n\t\t// some menu items are not meant for selection, but for navigation; those should not\n\t\t// result in tags being added!\n\t\tconst data = menuItem.getData();\n\t\tif ( data.handler ) {\n\t\t\tdata.handler();\n\t\t\treturn;\n\t\t}\n\n\t\tuw.CategoriesDetailsWidget.super.prototype.onMenuChoose.call( this, menuItem, selected );\n\t};\n\n\tuw.CategoriesDetailsWidget.prototype.titleFromData = function ( data ) {\n\t\treturn mw.Title.newFromText(\n\t\t\t// manual input is string (just category name; selection from menu is object)\n\t\t\ttypeof data === 'string' ? data : data.title,\n\t\t\tNS_CATEGORY\n\t\t);\n\t};\n\n\tuw.CategoriesDetailsWidget.prototype.isAllowedData = function ( data ) {\n\t\tconst title = this.titleFromData( data );\n\t\tif ( !title ) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn uw.CategoriesDetailsWidget.super.prototype.isAllowedData.call( this, data );\n\t};\n\n\tuw.CategoriesDetailsWidget.prototype.findItemFromData = function ( data ) {\n\t\tconst title = this.titleFromData( data );\n\t\tif ( !title ) {\n\t\t\treturn null;\n\t\t}\n\t\treturn OO.ui.mixin.GroupElement.prototype.findItemFromData.call( this, title.getMainText() );\n\t};\n\n\tuw.CategoriesDetailsWidget.prototype.createTagItemWidget = function ( data ) {\n\t\tconst title = this.titleFromData( data );\n\n\t\tconst widget = new mw.widgets.CategoryTagItemWidget( {\n\t\t\tapiUrl: this.api.apiUrl || undefined,\n\t\t\ttitle: title\n\t\t} );\n\t\twidget.setMissing = ( missing ) => {\n\t\t\twidget.constructor.prototype.setMissing.call( widget, missing );\n\t\t\t// Aggregate 'change' event\n\t\t\tthis.emit( 'change' );\n\t\t};\n\n\t\treturn widget;\n\t};\n\n\tuw.CategoriesDetailsWidget.prototype.searchCategories = function ( input ) {\n\t\tconst cacheKey = input;\n\n\t\t// Abort all pending requests, we won't need their results\n\t\tthis.api.abort();\n\n\t\tif ( input.trim() === '' ) {\n\t\t\treturn $.Deferred().resolve( [] ).promise();\n\t\t}\n\n\t\t// Check cache\n\t\tif ( Object.prototype.hasOwnProperty.call( this.cacheSearch, cacheKey ) ) {\n\t\t\treturn this.cacheSearch[ cacheKey ];\n\t\t}\n\n\t\tthis.cacheSearch[ cacheKey ] = this.api.get( {\n\t\t\tformatversion: 2,\n\t\t\taction: 'query',\n\t\t\tgenerator: 'prefixsearch',\n\t\t\tgpsnamespace: NS_CATEGORY,\n\t\t\tgpslimit: this.searchLimit,\n\t\t\tgpssearch: input,\n\t\t\tprop: 'categoryinfo'\n\t\t} )\n\t\t\t.then( ( res ) => Object.keys( res && res.query && res.query.pages || [] )\n\t\t\t\t.map( ( key ) => res.query.pages[ key ] )\n\t\t\t\t.sort( ( a, b ) => a.index > b.index )\n\t\t\t)\n\t\t\t.fail( () => delete this.cacheSearch[ cacheKey ] );\n\n\t\treturn this.cacheSearch[ cacheKey ];\n\t};\n\n\tuw.CategoriesDetailsWidget.prototype.getSubCategories = function ( input ) {\n\t\tconst cacheKey = input;\n\n\t\t// Abort all pending requests, we won't need their results\n\t\tthis.api.abort();\n\n\t\t// Check cache\n\t\tif ( Object.prototype.hasOwnProperty.call( this.cacheChildren, cacheKey ) ) {\n\t\t\treturn this.cacheChildren[ cacheKey ];\n\t\t}\n\n\t\tthis.cacheChildren[ cacheKey ] = this.api.get( {\n\t\t\tformatversion: 2,\n\t\t\taction: 'query',\n\t\t\tgenerator: 'categorymembers',\n\t\t\tgcmnamespace: NS_CATEGORY,\n\t\t\tgcmlimit: this.subLimit,\n\t\t\tgcmtitle: mw.Title.newFromText( input, NS_CATEGORY ).getPrefixedDb(),\n\t\t\tprop: 'categoryinfo'\n\t\t} )\n\t\t\t.then( ( res ) => Object.keys( res && res.query && res.query.pages || [] ).map( ( key ) => res.query.pages[ key ] ) )\n\t\t\t.fail( () => delete this.cacheChildren[ cacheKey ] );\n\n\t\treturn this.cacheChildren[ cacheKey ];\n\t};\n\n\tuw.CategoriesDetailsWidget.prototype.validate = function () {\n\t\tconst status = new uw.ValidationStatus(),\n\t\t\tmissing = this.getItems().filter( ( item ) => item.missing );\n\n\t\tif ( missing.length > 0 ) {\n\t\t\tstatus.addWarning( mw.message( 'mwe-upwiz-categories-missing', missing.length ) );\n\t\t}\n\n\t\treturn status.resolve();\n\t};\n\n\tuw.CategoriesDetailsWidget.prototype.getWikiText = function () {\n\t\tlet hiddenCats = [];\n\t\tif ( mw.UploadWizard.config.autoAdd.categories ) {\n\t\t\thiddenCats = hiddenCats.concat( mw.UploadWizard.config.autoAdd.categories );\n\t\t}\n\t\tif ( mw.UploadWizard.config.trackingCategory ) {\n\t\t\tif ( mw.UploadWizard.config.trackingCategory.all ) {\n\t\t\t\thiddenCats.push( mw.UploadWizard.config.trackingCategory.all );\n\t\t\t}\n\t\t\tif ( mw.UploadWizard.config.trackingCategory.campaign ) {\n\t\t\t\thiddenCats.push( mw.UploadWizard.config.trackingCategory.campaign );\n\t\t\t}\n\t\t}\n\t\t// Keep only valid titles\n\t\thiddenCats = hiddenCats.filter( ( cat ) => !!mw.Title.makeTitle( NS_CATEGORY, cat ) );\n\n\t\tlet missingCatsWikiText = null;\n\t\tif (\n\t\t\ttypeof mw.UploadWizard.config.missingCategoriesWikiText === 'string' &&\n\t\t\tmw.UploadWizard.config.missingCategoriesWikiText.length > 0\n\t\t) {\n\t\t\tmissingCatsWikiText = mw.UploadWizard.config.missingCategoriesWikiText;\n\t\t}\n\n\t\tconst categories = this.getItems().map( ( item ) => item.data );\n\n\t\t// add all categories\n\t\tlet wikiText = categories.concat( hiddenCats )\n\t\t\t.map( ( cat ) => '[[' + mw.Title.makeTitle( NS_CATEGORY, cat ).getPrefixedText() + ']]' )\n\t\t\t.join( '\\n' );\n\n\t\t// if so configured, and there are no user-visible categories, add warning\n\t\tif ( missingCatsWikiText !== null && categories.length === 0 ) {\n\t\t\twikiText += '\\n\\n' + missingCatsWikiText;\n\t\t}\n\n\t\treturn wikiText;\n\t};\n\n\tuw.CategoriesDetailsWidget.prototype.getSerialized = function () {\n\t\treturn {\n\t\t\tvalue: this.getItems().map( ( item ) => item.data )\n\t\t};\n\t};\n\n\tuw.CategoriesDetailsWidget.prototype.setSerialized = function ( serialized ) {\n\t\tthis.setValue( serialized.value );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/details/uw.DateDetailsWidget.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.DetailsWidget' is undefined.","line":6,"column":1,"nodeType":"Block","endLine":6,"endColumn":1},{"ruleId":"mediawiki/no-unlabeled-buttonwidget","severity":1,"message":"OO.ui.ButtonWidget has no label. Even icon-only buttons should set a label with invisibleLabel set to true.","line":16,"column":31,"nodeType":"NewExpression","messageId":"noLabel","endLine":20,"endColumn":6},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":164,"column":6,"nodeType":"CallExpression","endLine":164,"endColumn":43},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":171,"column":6,"nodeType":"CallExpression","endLine":171,"endColumn":51},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":179,"column":6,"nodeType":"CallExpression","endLine":179,"endColumn":47},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":187,"column":6,"nodeType":"CallExpression","endLine":187,"endColumn":48},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":204,"column":25,"nodeType":"CallExpression","endLine":208,"endColumn":7}],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":2,"message":"'thorough' is defined but never used.","line":129,"column":55,"nodeType":"Identifier","messageId":"unusedVar","endLine":129,"endColumn":63,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\t/**\n\t * A date field in UploadWizard's \"Details\" step form.\n\t *\n\t * @extends uw.DetailsWidget\n\t * @class\n\t * @param {Object} config Configuration options\n\t * @param {mw.UploadWizardUpload} config.upload\n\t */\n\tuw.DateDetailsWidget = function UWDateDetailsWidget( config ) {\n\t\tuw.DateDetailsWidget.super.call( this );\n\n\t\tthis.upload = config.upload;\n\t\tthis.prefilled = false;\n\t\tthis.calendarButtonWidget = new OO.ui.ButtonWidget( {\n\t\t\ticon: 'calendar',\n\t\t\tflags: [ 'progressive' ],\n\t\t\ttitle: mw.msg( 'mwe-upwiz-calendar-date' )\n\t\t} );\n\n\t\tthis.dateInputWidget = new OO.ui.TextInputWidget( {\n\t\t\tclasses: [ 'mwe-date' ],\n\t\t\tplaceholder: mw.msg( 'mwe-upwiz-select-date' )\n\t\t} );\n\n\t\tthis.calendar = new mw.widgets.CalendarWidget( {\n\t\t\tlazyInitOnToggle: true\n\t\t} );\n\t\tthis.calendar.toggle( false );\n\t\t// clicking the calendar icon toggles the calendar on/off,\n\t\t// clicking the input field always closes it\n\t\tthis.calendarButtonWidget.on( 'click', () => this.calendar.toggle( !this.calendar.visible ) );\n\t\tthis.dateInputWidget.on( 'click', () => this.calendar.toggle( false ) );\n\t\t// selecting a date from the calendar writes that date into the input field;\n\t\t// when anything changes in the input (either manual input or calendar selection),\n\t\t// the calendar closes & change event is emitted (but only once every 500ms)\n\t\tthis.calendar.on( 'change', ( value ) => {\n\t\t\tthis.dateInputWidget.setValue( value );\n\t\t} );\n\t\tthis.dateInputWidget.on( 'change', () => {\n\t\t\tthis.calendar.toggle( false );\n\t\t} );\n\t\tthis.dateInputWidget.on( 'change', OO.ui.debounce( ( value ) => {\n\t\t\tthis.emit( 'change', value );\n\t\t}, 500 ) );\n\n\t\tthis.$element\n\t\t\t.addClass( 'mwe-upwiz-dateDetailsWidget' )\n\t\t\t.append(\n\t\t\t\tthis.calendarButtonWidget.$element,\n\t\t\t\t$( '<div>' )\n\t\t\t\t\t.addClass( 'mw-widget-dateInputWidget' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-dateDetailsWidget-date' )\n\t\t\t\t\t.append(\n\t\t\t\t\t\tthis.dateInputWidget.$element,\n\t\t\t\t\t\tthis.calendar.$element\n\t\t\t\t\t\t\t.addClass( 'mw-widget-dateInputWidget-calendar' )\n\t\t\t\t\t)\n\t\t\t);\n\t};\n\tOO.inheritClass( uw.DateDetailsWidget, uw.DetailsWidget );\n\tOO.mixinClass( uw.StatementWidget, uw.ValidatableElement );\n\n\t/**\n\t * Tell whether the date input field was prefilled\n\t * with a value extracted from the upload's metadata.\n\t *\n\t * @param {boolean} prefilled Whether the date is prefilled\n\t */\n\tuw.DateDetailsWidget.prototype.setPrefilled = function ( prefilled ) {\n\t\tthis.prefilled = prefilled;\n\t};\n\n\t/**\n\t * Parse user input into a Wikibase date\n\t * via the `wbparsevalue` API endpoint.\n\t * See https://www.wikidata.org/w/api.php?action=help&modules=wbparsevalue\n\t * and https://www.wikidata.org/wiki/Help:Dates.\n\t *\n\t * @return {jQuery.Promise} Promise with the API response\n\t */\n\tuw.DateDetailsWidget.prototype.parseDate = function () {\n\t\tconst userInput = this.dateInputWidget.getValue(),\n\t\t\t// Handle input that includes time:\n\t\t\t// it typically comes from the upload's EXIF metadata,\n\t\t\t// but might also be inserted by the user.\n\t\t\t// The Wikibase value parser won't accept it,\n\t\t\t// since dates with precision higher than day aren't supported.\n\t\t\t// See https://phabricator.wikimedia.org/T57755.\n\t\t\t// The API would return an opaque\n\t\t\t// 'wikibase-validator-malformed-value' error code:\n\t\t\t// avoid this by stripping time (in standard format)\n\t\t\tuserInputWithoutTime = userInput.replace( /\\D\\d\\d:\\d\\d:\\d\\d/, '' ),\n\t\t\tparams = {\n\t\t\t\taction: 'wbparsevalue',\n\t\t\t\tdatatype: 'time',\n\t\t\t\tvalidate: true,\n\t\t\t\toptions: JSON.stringify(\n\t\t\t\t\t{ lang: mw.config.get( 'wgUserLanguage' ) }\n\t\t\t\t),\n\t\t\t\tvalues: userInputWithoutTime\n\t\t\t};\n\n\t\treturn this.upload.api.get( params );\n\t};\n\n\t/**\n\t * Gets the selected license(s). The returned value will be a license\n\t * key => license props map, as defined in UploadWizard.config.php.\n\t *\n\t * @return {Object}\n\t */\n\tuw.DateDetailsWidget.prototype.getLicenses = function () {\n\t\tif ( this.upload.deedChooser && this.upload.deedChooser.deed && this.upload.deedChooser.deed.licenseInput ) {\n\t\t\treturn this.upload.deedChooser.deed.licenseInput.getLicenses();\n\t\t}\n\n\t\t// no license has been selected yet\n\t\t// this could happen when uploading multiple files and selecting to\n\t\t// provide copyright information for each file individually\n\t\treturn {};\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\t// eslint-disable-next-line no-unused-vars\n\tuw.DateDetailsWidget.prototype.validate = function ( thorough ) {\n\t\tconst status = new uw.ValidationStatus(),\n\t\t\ttrimmedInput = this.dateInputWidget.getValue().trim(),\n\t\t\tinputDate = new Date( trimmedInput ),\n\t\t\tnow = new Date(),\n\t\t\tlicenses = this.getLicenses(),\n\t\t\t// Timestamps: milliseconds\n\t\t\tutc14 = 14 * 60 * 60 * 1000, // 14 hours in milliseconds\n\t\t\t// Dates: years, months, days\n\t\t\told95 = new Date( now.getFullYear() - 95 ), // Only the year is relevant\n\t\t\told70 = new Date( now.getFullYear() - 70, now.getMonth(), now.getDate() ),\n\t\t\told100 = new Date( now.getFullYear() - 100, now.getMonth(), now.getDate() );\n\n\t\tlet promise = $.Deferred().resolve().promise();\n\n\t\tif ( this.parseDateValidation ) {\n\t\t\tthis.parseDateValidation.abort();\n\t\t}\n\n\t\t// Blank\n\t\tif ( trimmedInput === '' ) {\n\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-date-blank' ) );\n\t\t\t// Date in the future.\n\t\t\t// We don't really know what timezone this datetime is in. It could be the user's timezone, or\n\t\t\t// it could be the camera's timezone for data imported from EXIF, and we don't know what\n\t\t\t// timezone that is. UTC+14 is the highest timezone that currently exists, so assume that to\n\t\t\t// avoid giving false errors.\n\t\t} else if ( inputDate.getTime() > now.getTime() + utc14 ) {\n\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-postdate' ) );\n\t\t\t// License mismatch.\n\t\t\t// Public domain work in the U.S.: it must've been created at least 95 years ago.\n\t\t} else if ( 'pd-us' in licenses && inputDate >= old95 ) {\n\t\t\tstatus.addError(\n\t\t\t\tmw.message(\n\t\t\t\t\t'mwe-upwiz-error-date-license-mismatch',\n\t\t\t\t\tmw.message( licenses[ 'pd-us' ].msg ).parseDom()\n\t\t\t\t)\n\t\t\t);\n\t\t} else if ( 'pd-us-generic' in licenses && inputDate >= old95 ) {\n\t\t\tstatus.addError(\n\t\t\t\tmw.message(\n\t\t\t\t\t'mwe-upwiz-error-date-license-mismatch',\n\t\t\t\t\tmw.message( licenses[ 'pd-us-generic' ].msg ).parseDom()\n\t\t\t\t)\n\t\t\t);\n\t\t\t// The author died 70 years ago: the date should reflect that\n\t\t} else if ( 'pd-old-70' in licenses && inputDate > old70 ) {\n\t\t\tstatus.addError(\n\t\t\t\tmw.message(\n\t\t\t\t\t'mwe-upwiz-error-date-license-mismatch',\n\t\t\t\t\tmw.message( licenses[ 'pd-old-70' ].msg ).parseDom()\n\t\t\t\t)\n\t\t\t);\n\t\t\t// The author died 100 years ago: the date should reflect that\n\t\t} else if ( 'pd-old-100' in licenses && inputDate > old100 ) {\n\t\t\tstatus.addError(\n\t\t\t\tmw.message(\n\t\t\t\t\t'mwe-upwiz-error-date-license-mismatch',\n\t\t\t\t\tmw.message( licenses[ 'pd-old-100' ].msg ).parseDom()\n\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\t// Unlikely license.\n\t\tif (\n\t\t\t// The `Date` constructor returns `NaN` if it couldn't parse the date.\n\t\t\t!isNaN( inputDate.valueOf() ) &&\n\t\t\t// It's unlikely for public-domain images to have been published today\n\t\t\tnow.toISOString().slice( 0, 10 ) === inputDate.toISOString().slice( 0, 10 )\n\t\t) {\n\t\t\t// Public-domain licenses that likely mean\n\t\t\t// the image date is some time in the past.\n\t\t\t[ 'pd-usgov', 'pd-usgov-nasa', 'pd-art' ].forEach( ( warnLicense ) => {\n\t\t\t\tif ( warnLicense in licenses ) {\n\t\t\t\t\tconst license = licenses[ warnLicense ];\n\t\t\t\t\tconst licenseMsg = mw.message(\n\t\t\t\t\t\tlicense.msg,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tlicense.url ? license.url : '#missing license URL'\n\t\t\t\t\t);\n\t\t\t\t\tstatus.addWarning(\n\t\t\t\t\t\tmw.message(\n\t\t\t\t\t\t\t'mwe-upwiz-error-date-license-unlikely',\n\t\t\t\t\t\t\tlicenseMsg.parseDom()\n\t\t\t\t\t\t)\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} );\n\t\t}\n\n\t\tif ( this.prefilled ) {\n\t\t\tstatus.addNotice( mw.message( 'mwe-upwiz-warning-date-prefilled' ) );\n\t\t}\n\n\t\tif ( this.dateInputWidget.getValue().trim() !== '' ) {\n\t\t\tthis.parseDateValidation = this.parseDate();\n\t\t\tpromise = this.parseDateValidation.then(\n\t\t\t\t( data ) => {\n\t\t\t\t\tconst dayPrecision = 11;\n\t\t\t\t\tif ( data.results && data.results[ 0 ] && data.results[ 0 ].value.precision < dayPrecision ) {\n\t\t\t\t\t\tstatus.addNotice( mw.message( 'mwe-upwiz-notice-date-imprecise' ) );\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t( code ) => {\n\t\t\t\t\t// warn on failures, except when the failure is http (request aborted\n\t\t\t\t\t// or network issues)\n\t\t\t\t\tif ( code !== 'http' ) {\n\t\t\t\t\t\tstatus.addNotice( mw.message( 'mwe-upwiz-notice-date-imprecise' ) );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\n\t\treturn promise.then( () => status.getErrors().length === 0 ? status.resolve() : status.reject() );\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.DateDetailsWidget.prototype.getWikiText = function () {\n\t\treturn this.dateInputWidget.getValue().trim();\n\t};\n\n\t/**\n\t * @inheritdoc\n\t * @return {Object} See #setSerialized\n\t */\n\tuw.DateDetailsWidget.prototype.getSerialized = function () {\n\t\treturn {\n\t\t\tprefilled: this.prefilled,\n\t\t\tvalue: this.dateInputWidget.getValue()\n\t\t};\n\t};\n\n\t/**\n\t * @inheritdoc\n\t * @param {Object} serialized\n\t * @param {boolean} serialized.prefilled Whether the date is prefilled\n\t * @param {string} serialized.value Date value for the given mode\n\t */\n\tuw.DateDetailsWidget.prototype.setSerialized = function ( serialized ) {\n\t\t// select the given date in the input widget\n\t\tthis.calendar.setDate( serialized.value );\n\t\t// update the input widget last, at the end of the call stack (i.e.\n\t\t// after the calendar's change event has been emitted/handled), to\n\t\t// ensure the date input widget has the actual value, which may have\n\t\t// more precision (hours, minutes, seconds) than the calendar value\n\t\tsetTimeout( () => {\n\t\t\tthis.prefilled = serialized.prefilled;\n\t\t\tthis.dateInputWidget.setValue( serialized.value );\n\t\t} );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/details/uw.LanguageDropdownWidget.js","messages":[{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":14,"column":53,"nodeType":"ObjectExpression","endLine":17,"endColumn":4}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\t/**\n\t * A language dropdown within a description field in UploadWizard's \"Details\" step form.\n\t *\n\t * @class\n\t * @param {Object} [config]\n\t */\n\tuw.LanguageDropdownWidget = function UWLanguageDropdownWidget( config ) {\n\t\tconfig = config || {};\n\n\t\tuw.LanguageDropdownWidget.super.call( this );\n\n\t\tthis.languageDropdown = new OO.ui.DropdownWidget( {\n\t\t\tmenu: { items: this.getLanguageMenuOptionWidgets( config.languages ) },\n\t\t\tclasses: config.classes\n\t\t} );\n\t\tthis.languageDropdown.getMenu().connect( this, { select: [ 'emit', 'select' ] } );\n\t};\n\tOO.inheritClass( uw.LanguageDropdownWidget, OO.ui.Widget );\n\tOO.mixinClass( uw.LanguageDropdownWidget, OO.EventEmitter );\n\n\t/**\n\t * @param {Object} languages\n\t */\n\tuw.LanguageDropdownWidget.prototype.updateLanguages = function ( languages ) {\n\t\tconst menu = this.languageDropdown.getMenu(),\n\t\t\tcurrentMenuItems = menu.getItems(),\n\t\t\tcurrentValue = this.getValue();\n\n\t\t// remove all items except the one currently selected (don't want\n\t\t// to trigger another select by removing it)\n\t\tmenu.removeItems( currentMenuItems.filter( ( item ) => !item.isSelected() ) );\n\n\t\t// and add the rest of the languages back in there\n\t\tdelete languages[ currentValue ];\n\t\tmenu.addItems( this.getLanguageMenuOptionWidgets( languages ) );\n\t};\n\n\t/**\n\t * @param {string} value\n\t */\n\tuw.LanguageDropdownWidget.prototype.setValue = function ( value ) {\n\t\tthis.languageDropdown.getMenu().selectItemByData( value );\n\t};\n\n\t/**\n\t * @return {string}\n\t */\n\tuw.LanguageDropdownWidget.prototype.getValue = function () {\n\t\treturn this.languageDropdown.getMenu().findSelectedItem().getData();\n\t};\n\n\t/**\n\t * @return {OO.ui.DropdownWidget}\n\t */\n\tuw.LanguageDropdownWidget.prototype.getElement = function () {\n\t\treturn this.languageDropdown.$element;\n\t};\n\n\t/**\n\t * Get options for the dropdown list of all allowed languages.\n\t *\n\t * @private\n\t * @param {Object} languages\n\t * @return {OO.ui.MenuOptionWidget[]}\n\t */\n\tuw.LanguageDropdownWidget.prototype.getLanguageMenuOptionWidgets = function ( languages ) {\n\t\treturn Object.keys( languages ).map( ( code ) => new OO.ui.MenuOptionWidget( {\n\t\t\tdata: code,\n\t\t\tlabel: languages[ code ]\n\t\t} ) );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/details/uw.LocationDetailsWidget.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.DetailsWidget' is undefined.","line":6,"column":1,"nodeType":"Block","endLine":6,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":67,"column":3,"nodeType":"CallExpression","endLine":70,"endColumn":6}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\t/**\n\t * A set of location fields in UploadWizard's \"Details\" step form.\n\t *\n\t * @extends uw.DetailsWidget\n\t * @class\n\t * @param {Object} [config] Configuration options\n\t * @param {string} [config.latitudeKey]\n\t * @param {string} [config.longitudeKey]\n\t * @param {string} [config.headingKey]\n\t * @param {string} [config.templateName]\n\t */\n\tuw.LocationDetailsWidget = function UWLocationDetailsWidget( config ) {\n\t\tthis.config = config || {};\n\n\t\tuw.LocationDetailsWidget.super.call( this );\n\n\t\tthis.$element.addClass( 'mwe-upwiz-locationDetailsWidget' );\n\n\t\tthis.latitudeInput = new OO.ui.TextInputWidget();\n\t\tthis.longitudeInput = new OO.ui.TextInputWidget();\n\t\tthis.headingInput = new OO.ui.TextInputWidget();\n\t\tthis.$map = $( '<div>' ).css( { width: 500, height: 300 } );\n\t\tthis.mapButton = new OO.ui.PopupButtonWidget( {\n\t\t\ticon: 'mapPin',\n\t\t\ttitle: mw.message( 'mwe-upwiz-location-button' ).text(),\n\t\t\tpopup: {\n\t\t\t\t$content: this.$map,\n\t\t\t\twidth: 500,\n\t\t\t\theight: 300\n\t\t\t}\n\t\t} );\n\n\t\tthis.$element.append(\n\t\t\tnew OO.ui.FieldLayout( this.latitudeInput, {\n\t\t\t\talign: 'top',\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-location-lat' ).text()\n\t\t\t} ).$element,\n\t\t\tnew OO.ui.FieldLayout( this.longitudeInput, {\n\t\t\t\talign: 'top',\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-location-lon' ).text()\n\t\t\t} ).$element\n\t\t);\n\n\t\tif ( this.config.headingKey ) {\n\t\t\tthis.$element.append(\n\t\t\t\tnew OO.ui.FieldLayout( this.headingInput, {\n\t\t\t\t\talign: 'top',\n\t\t\t\t\tlabel: mw.message( 'mwe-upwiz-location-heading' ).text()\n\t\t\t\t} ).$element\n\t\t\t);\n\t\t}\n\n\t\tthis.mapButton.setDisabled( true );\n\t\tthis.$element.append( this.mapButton.$element );\n\n\t\t// Aggregate 'change' events\n\t\tthis.latitudeInput.connect( this, { change: [ 'emit', 'change' ] } );\n\t\tthis.longitudeInput.connect( this, { change: [ 'emit', 'change' ] } );\n\t\tthis.headingInput.connect( this, { change: [ 'emit', 'change' ] } );\n\n\t\tthis.mapButton.connect( this, { click: 'onMapButtonClick' } );\n\t\tthis.connect( this, { change: 'onChange' } );\n\n\t\tthis.mapButton.toggle( false );\n\t\tmw.loader.using( [ 'ext.kartographer.box', 'ext.kartographer.editing' ] ).done( () => {\n\t\t\t// Kartographer is installed and we'll be able to show the map. Display the button.\n\t\t\tthis.mapButton.toggle( true );\n\t\t} );\n\t};\n\tOO.inheritClass( uw.LocationDetailsWidget, uw.DetailsWidget );\n\tOO.mixinClass( uw.LocationDetailsWidget, uw.ValidatableElement );\n\n\t/**\n\t * @private\n\t */\n\tuw.LocationDetailsWidget.prototype.onChange = function () {\n\t\tthis.validate().then(\n\t\t\t() => this.mapButton.setDisabled( false ),\n\t\t\t() => this.mapButton.setDisabled( true )\n\t\t);\n\t};\n\n\t/**\n\t * @private\n\t */\n\tuw.LocationDetailsWidget.prototype.onMapButtonClick = function () {\n\t\tconst latitude = this.normalizeCoordinate( this.latitudeInput.getValue() ),\n\t\t\tlongitude = this.normalizeCoordinate( this.longitudeInput.getValue() );\n\n\t\t// Disable clipping because it doesn't play nicely with the map\n\t\tthis.mapButton.getPopup().toggleClipping( false );\n\n\t\tif ( !this.map ) {\n\t\t\tthis.map = require( 'ext.kartographer.box' ).map( {\n\t\t\t\tcontainer: this.$map[ 0 ]\n\t\t\t} );\n\t\t}\n\t\trequire( 'ext.kartographer.editing' ).getKartographerLayer( this.map ).setGeoJSON( {\n\t\t\ttype: 'Feature',\n\t\t\tproperties: {},\n\t\t\tgeometry: { type: 'Point', coordinates: [ longitude, latitude ] }\n\t\t} );\n\t\tthis.map.setView( [ latitude, longitude ], 9 );\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.LocationDetailsWidget.prototype.validate = function () {\n\t\tconst status = new mw.uploadWizard.ValidationStatus(),\n\t\t\tlatInput = this.latitudeInput.getValue(),\n\t\t\tlonInput = this.longitudeInput.getValue(),\n\t\t\theadInput = this.headingInput.getValue(),\n\t\t\tlatNum = this.normalizeCoordinate( latInput ),\n\t\t\tlonNum = this.normalizeCoordinate( lonInput ),\n\t\t\theadNum = parseFloat( headInput );\n\n\t\t// input is invalid if the coordinates are out of bounds, or if the\n\t\t// coordinates that were derived from the input are 0, without a 0 even\n\t\t// being present in the input\n\t\tif ( latInput || lonInput ) {\n\t\t\tif ( latNum > 90 || latNum < -90 || ( latNum === 0 && !latInput.includes( '0' ) ) || isNaN( latNum ) ) {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-latitude' ) );\n\t\t\t}\n\n\t\t\tif ( lonNum > 180 || lonNum < -180 || ( lonNum === 0 && !lonInput.includes( '0' ) ) || isNaN( lonNum ) ) {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-longitude' ) );\n\t\t\t}\n\t\t}\n\n\t\tif ( headInput !== '' && ( headInput > 360 || headInput < 0 || isNaN( headNum ) ) ) {\n\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-heading' ) );\n\t\t}\n\n\t\treturn status.getErrors().length === 0 ? status.resolve() : status.reject();\n\t};\n\n\t/**\n\t * Set up the input fields.\n\t *\n\t * @param {string} [lat] Latitude value to set.\n\t * @param {string} [lon] Longitude value to set.\n\t * @param {string} [head] Heading value to set.\n\t * @private\n\t */\n\tuw.LocationDetailsWidget.prototype.setupInputs = function ( lat, lon, head ) {\n\t\tif ( lat !== undefined ) {\n\t\t\tthis.latitudeInput.setValue( lat );\n\t\t}\n\n\t\tif ( lon !== undefined ) {\n\t\t\tthis.longitudeInput.setValue( lon );\n\t\t}\n\n\t\tif ( head !== undefined ) {\n\t\t\tthis.headingInput.setValue( head );\n\t\t}\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.LocationDetailsWidget.prototype.getWikiText = function () {\n\t\tconst latInput = this.latitudeInput.getValue(),\n\t\t\tlonInput = this.longitudeInput.getValue(),\n\t\t\theadInput = this.headingInput.getValue(),\n\t\t\tlatNum = this.normalizeCoordinate( latInput ),\n\t\t\tlonNum = this.normalizeCoordinate( lonInput ),\n\t\t\theadNum = parseFloat( headInput );\n\n\t\t// input is invalid if the coordinates are out of bounds, or if the\n\t\t// coordinates that were derived from the input are 0, without a 0 even\n\t\t// being present in the input\n\t\tif ( latNum !== 0 || latInput.includes( '0' ) || lonNum !== 0 || lonInput.includes( '0' ) ) {\n\t\t\t// {{Location}} or {{Object location}}\n\t\t\tconst locationParts = [ '{{' + this.config.templateName, latNum, lonNum ];\n\n\t\t\tif ( !isNaN( headNum ) ) {\n\t\t\t\tlocationParts.push( 'heading:' + headNum );\n\t\t\t}\n\n\t\t\treturn locationParts.join( '|' ) + '}}';\n\t\t}\n\n\t\treturn '';\n\t};\n\n\t/**\n\t * @inheritdoc\n\t * @return {Object} See #setSerialized\n\t */\n\tuw.LocationDetailsWidget.prototype.getSerialized = function () {\n\t\treturn {\n\t\t\t[ this.config.latitudeKey ]: this.latitudeInput.getValue(),\n\t\t\t[ this.config.longitudeKey ]: this.longitudeInput.getValue(),\n\t\t\t[ this.config.headingKey ]: this.headingInput.getValue()\n\t\t};\n\t};\n\n\t/**\n\t * @inheritdoc\n\t * @param {Object} serialized\n\t * @param {string} serialized.latitude Latitude value\n\t * @param {string} serialized.longitude Longitude value\n\t * @param {string} serialized.heading Heading value\n\t */\n\tuw.LocationDetailsWidget.prototype.setSerialized = function ( serialized ) {\n\t\tthis.setupInputs(\n\t\t\tserialized[ this.config.latitudeKey ],\n\t\t\tserialized[ this.config.longitudeKey ],\n\t\t\tserialized[ this.config.headingKey ]\n\t\t);\n\t};\n\n\t/**\n\t * Interprets a wide variety of coordinate input formats, it'll return the\n\t * coordinate in decimal degrees.\n\t *\n\t * Formats understood include:\n\t * - degrees minutes seconds: 40° 26' 46\" S\n\t * - degrees decimal minutes: 40° 26.767' S\n\t * - decimal degrees: 40.446° S\n\t * - decimal degrees exact value: -40.446\n\t *\n\t * This code is shared with the Kartographer extension. Please consider updating both when you\n\t * touch this.\n\t *\n\t * @param {string} input\n\t * @return {number|NaN} NaN when normalization was not possible\n\t */\n\tuw.LocationDetailsWidget.prototype.normalizeCoordinate = function ( input ) {\n\t\tconst sign = input.match( /[sw]/i ) ? -1 : 1;\n\n\t\t// fix commonly used character alternatives\n\t\tlet value = input.trim()\n\t\t\t.replace( /−/g, '-' )\n\t\t\t.replace( /\\s*[,.]\\s*/g, '.' );\n\n\t\t// convert degrees, minutes, seconds (or degrees & decimal minutes) to\n\t\t// decimal degrees\n\t\t// there can be a lot of variation in the notation, so let's only\n\t\t// focus on \"groups of digits\" (and not whether e.g. ″ or \" is used)\n\t\tconst parts = value.match( /^\\D*(-?\\d{1,3}\\b[\\d.]*)[^\\d.]+(\\d{1,2}\\b[\\d.]*)(?:[^\\d.]+(\\d{1,2}\\b[\\d.]*))?\\D*$/ );\n\t\tif ( parts ) {\n\t\t\tvalue = parts[ 1 ] * 1 + parts[ 2 ] / 60 + ( parts[ 3 ] || 0 ) / 3600;\n\t\t} else {\n\t\t\tvalue = value.replace( /[^-\\d.]+/g, '' ) * 1;\n\t\t\tif ( Math.abs( value ) > 360 ) {\n\t\t\t\treturn NaN;\n\t\t\t}\n\t\t}\n\n\t\t// Round to 6 decimal places, this approx. corresponds to a precision of 0.1 meter or less\n\t\treturn Math.round( sign * value * 1000000 ) / 1000000;\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/details/uw.MultipleLanguageInputWidget.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.DetailsWidget' is undefined.","line":7,"column":1,"nodeType":"Block","endLine":7,"endColumn":1},{"ruleId":"es-x/no-iterator-prototype-reduce","severity":1,"message":"ES2025 'Iterator.prototype.reduce' method is forbidden.","line":134,"column":10,"nodeType":"MemberExpression","messageId":"forbidden","endLine":134,"endColumn":22},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":181,"column":10,"nodeType":"CallExpression","endLine":181,"endColumn":45}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\t/**\n\t * A multi-language input field in UploadWizard's \"Details\" step form.\n\t *\n\t * @class\n\t * @extends uw.DetailsWidget\n\t * @mixes OO.ui.mixin.GroupElement\n\t * @param {Object} [config]\n\t * @param {boolean} [config.required=true]\n\t * @param {mw.Message} [config.label] Text for label\n\t * @param {mw.Message} [config.placeholder] Placeholder text for input field\n\t * @param {mw.Message} [config.remove] Title text for remove icon\n\t * @param {mw.Message} [config.errorBlank] Error message for blank input\n\t * @param {number} [config.minLength=0] Minimum input length\n\t * @param {number} [config.maxLength=99999] Maximum input length\n\t * @param {Object} [config.languages] { langcode: text } map of languages\n\t */\n\tuw.MultipleLanguageInputWidget = function UWMultipleLanguageInputWidget( config ) {\n\t\tthis.config = Object.assign( {\n\t\t\trequired: true,\n\t\t\tlabel: mw.message( '' ),\n\t\t\terrorBlank: mw.message( 'mwe-upwiz-error-blank' ),\n\t\t\tlanguages: this.getLanguageOptions()\n\t\t}, config );\n\t\tuw.MultipleLanguageInputWidget.super.call( this );\n\t\tOO.ui.mixin.GroupElement.call( this );\n\n\t\tthis.required = !!this.config.required;\n\t\tthis.addButton = new OO.ui.ButtonWidget( {\n\t\t\tclasses: [ 'mwe-upwiz-multipleLanguageInputWidget-addItem' ],\n\t\t\tframed: true,\n\t\t\ticon: 'add',\n\t\t\tlabel: this.getLabelText()\n\t\t} );\n\n\t\t// if a language becomes available because the input gets removed,\n\t\t// or unavailable because it gets added, we'll need to update other\n\t\t// language dropdowns to reflect the change\n\t\tthis.connect( this, { add: 'onChangeLanguages' } );\n\t\tthis.connect( this, { remove: 'onChangeLanguages' } );\n\n\t\t// update the 'add language' button accordingly\n\t\tthis.connect( this, { add: 'recount' } );\n\t\tthis.connect( this, { remove: 'recount' } );\n\n\t\t// Aggregate 'change' event\n\t\tthis.aggregate( { change: 'change' } );\n\n\t\tthis.$element.addClass( 'mwe-upwiz-multipleLanguageInputsWidget' );\n\t\tthis.$element.append(\n\t\t\tthis.$group,\n\t\t\tthis.addButton.$element\n\t\t);\n\n\t\t// Add empty input (non-removable if this field is required)\n\t\tthis.addLanguageInput( Object.assign( {}, this.config, { removable: !this.required } ) );\n\t\t// Clicking the button will add new, removable, language inputs\n\t\tthis.addButton.connect( this, { click: [ 'addLanguageInput', Object.assign( {}, this.config, { removable: true } ) ] } );\n\t};\n\tOO.inheritClass( uw.MultipleLanguageInputWidget, uw.DetailsWidget );\n\tOO.mixinClass( uw.MultipleLanguageInputWidget, OO.ui.mixin.GroupElement );\n\tOO.mixinClass( uw.MultipleLanguageInputWidget, uw.ValidatableElement );\n\n\t/**\n\t * @param {Object} config\n\t * @param {string} [text]\n\t */\n\tuw.MultipleLanguageInputWidget.prototype.addLanguageInput = function ( config, text ) {\n\t\tconst allLanguages = this.config.languages,\n\t\t\tunusedLanguages = this.getUnusedLanguages();\n\n\t\tif ( unusedLanguages.length === 0 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet languages = {};\n\t\t// only add given language + unused/remaining languages - we don't want\n\t\t// languages that have already been selected to show up in the next dropdown...\n\t\tif ( config.defaultLanguage ) {\n\t\t\tlanguages[ config.defaultLanguage ] = allLanguages[ config.defaultLanguage ];\n\t\t\tlanguages = Object.assign( {}, languages, unusedLanguages );\n\t\t} else {\n\t\t\tlanguages = unusedLanguages;\n\t\t}\n\n\t\tconfig = Object.assign( {}, config, { languages: languages } );\n\t\tconst input = new uw.SingleLanguageInputWidget( config );\n\t\tinput.setText( text || '' );\n\n\t\t// if a language is changed, we'll need to update other language dropdowns\n\t\t// to reflect the change\n\t\tinput.connect( this, { select: 'onChangeLanguages' } );\n\t\t// forward change events from the input to this widget\n\t\tinput.connect( this, { change: [ 'emit', 'change' ] } );\n\n\t\tthis.addItems( [ new uw.FieldLayout( input ) ] );\n\t};\n\n\t/**\n\t * When a language changes (or an input is removed), the old language\n\t * becomes available again in other language dropdowns, and the new\n\t * language should no longer be selected.\n\t * This will iterate all inputs, destroy then, and construct new ones\n\t * with the updated language selections.\n\t */\n\tuw.MultipleLanguageInputWidget.prototype.onChangeLanguages = function () {\n\t\tconst allLanguages = this.config.languages,\n\t\t\tunusedLanguages = this.getUnusedLanguages(),\n\t\t\titems = this.getItems();\n\n\t\tfor ( let i = 0; i < items.length; i++ ) {\n\t\t\tconst input = items[ i ].fieldWidget;\n\n\t\t\t// only add existing language + unused/remaining languages - we don't want\n\t\t\t// languages that have already been selected to show up in the next dropdown...\n\t\t\tlet languages = {};\n\t\t\tlanguages[ input.getLanguage() ] = allLanguages[ input.getLanguage() ];\n\t\t\tlanguages = Object.assign( {}, languages, unusedLanguages );\n\t\t\tinput.updateLanguages( languages );\n\t\t}\n\t};\n\n\t/**\n\t * Returns an object of `langcode: text` pairs with the languages\n\t * already used in dropdowns.\n\t *\n\t * @return {Object}\n\t */\n\tuw.MultipleLanguageInputWidget.prototype.getUsedLanguages = function () {\n\t\tconst allLanguages = this.config.languages,\n\t\t\titems = this.getItems();\n\n\t\treturn items.reduce( ( obj, item ) => {\n\t\t\tconst input = item.fieldWidget;\n\t\t\tconst languageCode = input.getLanguage();\n\t\t\tobj[ languageCode ] = allLanguages[ languageCode ];\n\t\t\treturn obj;\n\t\t}, {} );\n\t};\n\n\t/**\n\t * Returns an object of `langcode: text` pairs with remaining languages\n\t * not yet used in dropdowns.\n\t *\n\t * @return {Object}\n\t */\n\tuw.MultipleLanguageInputWidget.prototype.getUnusedLanguages = function () {\n\t\tconst allLanguages = this.config.languages,\n\t\t\tusedLanguageCodes = Object.keys( this.getUsedLanguages() );\n\n\t\treturn Object.keys( allLanguages ).reduce( ( remaining, language ) => {\n\t\t\tif ( !usedLanguageCodes.includes( language ) ) {\n\t\t\t\tremaining[ language ] = allLanguages[ language ];\n\t\t\t}\n\t\t\treturn remaining;\n\t\t}, {} );\n\t};\n\n\t/**\n\t * Update the button label after adding or removing inputs.\n\t */\n\tuw.MultipleLanguageInputWidget.prototype.recount = function () {\n\t\tconst text = this.getLabelText(),\n\t\t\tunusedLanguages = this.getUnusedLanguages();\n\n\t\tthis.addButton.setLabel( text );\n\t\t// hide the button if there are no remaining languages...\n\t\tthis.addButton.toggle( Object.keys( unusedLanguages ).length > 0 );\n\t};\n\n\t/**\n\t * @return {string}\n\t */\n\tuw.MultipleLanguageInputWidget.prototype.getLabelText = function () {\n\t\tlet text = '', msg;\n\t\tif ( this.config.label.exists() ) {\n\t\t\t// clone the original object: `.params` doesn't replace existing\n\t\t\t// params so follow-up calls here would otherwise just keep adding\n\t\t\t// to the params instead of setting a new value for the first param\n\t\t\tmsg = mw.message( this.config.label.key ).params( this.config.label.parameters );\n\t\t\ttext = msg.params( [ this.items.length ] ).text();\n\t\t}\n\n\t\treturn text;\n\t};\n\n\t/**\n\t * @return {Object}\n\t */\n\tuw.MultipleLanguageInputWidget.prototype.getLanguageOptions = function () {\n\t\tconst languages = {};\n\t\tfor ( const code in mw.UploadWizard.config.uwLanguages ) {\n\t\t\tif ( Object.prototype.hasOwnProperty.call( mw.UploadWizard.config.uwLanguages, code ) ) {\n\t\t\t\tlanguages[ code ] = mw.UploadWizard.config.uwLanguages[ code ];\n\t\t\t}\n\t\t}\n\t\treturn languages;\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.MultipleLanguageInputWidget.prototype.validate = function ( thorough ) {\n\t\t// Gather errors from each item\n\t\tconst status = new mw.uploadWizard.ValidationStatus(),\n\t\t\tinputPromises = this.getItems().map( ( item ) => item.validate( thorough ) );\n\n\t\t// check whether we have any content at all (if required)\n\t\tif ( thorough && this.required && this.getWikiText() === '' ) {\n\t\t\tstatus.addError( this.config.errorBlank );\n\t\t}\n\n\t\t// TODO Check for duplicate languages\n\n\t\treturn uw.ValidationStatus.mergePromises( ...inputPromises ).then(\n\t\t\t// input (if any) in single language fields is fine\n\t\t\t() => status.getErrors().length === 0 ? status.resolve() : status.reject(),\n\t\t\t// there was an error in one of the single language inputs; we'll still want\n\t\t\t// to reject, but those child messages need not be added into this status\n\t\t\t// object, since they'll already be displayed within those child widgets\n\t\t\t() => status.reject()\n\t\t);\n\t};\n\n\t/**\n\t * @return {Object} an object of `{ language code: text }` pairs\n\t */\n\tuw.MultipleLanguageInputWidget.prototype.getValues = function () {\n\t\tconst values = {},\n\t\t\titems = this.getItems();\n\n\t\tfor ( let i = 0; i < items.length; i++ ) {\n\t\t\tconst input = items[ i ].fieldWidget;\n\t\t\tconst language = input.getLanguage();\n\t\t\tconst text = input.getText();\n\n\t\t\tif ( text !== '' ) {\n\t\t\t\tvalues[ language ] = text;\n\t\t\t}\n\t\t}\n\n\t\treturn values;\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.MultipleLanguageInputWidget.prototype.getWikiText = function () {\n\t\t// Some code here and in mw.UploadWizardDetails relies on this function returning an empty\n\t\t// string when there are some inputs, but all are empty.\n\t\treturn this.getItems().map( ( item ) => item.fieldWidget.getWikiText() ).filter( ( wikiText ) => !!wikiText ).join( '\\n' );\n\t};\n\n\t/**\n\t * @inheritdoc\n\t * @return {Object} See #setSerialized\n\t */\n\tuw.MultipleLanguageInputWidget.prototype.getSerialized = function () {\n\t\tconst inputs = this.getItems().map( ( item ) => item.fieldWidget.getSerialized() );\n\t\treturn {\n\t\t\tinputs: inputs\n\t\t};\n\t};\n\n\t/**\n\t * @inheritdoc\n\t * @param {Object} serialized\n\t * @param {Object[]} serialized.inputs Array of serialized inputs,\n\t * see uw.SingleLanguageInputWidget#setSerialized\n\t */\n\tuw.MultipleLanguageInputWidget.prototype.setSerialized = function ( serialized ) {\n\t\tlet config = this.config,\n\t\t\ti;\n\n\t\t// remove all existing\n\t\tthis.removeItems( this.getItems() );\n\n\t\tfor ( i = 0; i < serialized.inputs.length; i++ ) {\n\t\t\tconfig = Object.assign( {}, config, {\n\t\t\t\tdefaultLanguage: serialized.inputs[ i ].language,\n\t\t\t\tremovable: serialized.inputs[ i ].removable\n\t\t\t} );\n\n\t\t\tthis.addLanguageInput( config, serialized.inputs[ i ].text );\n\t\t}\n\t};\n\n\t/**\n\t * @param {boolean} required\n\t */\n\tuw.MultipleLanguageInputWidget.prototype.setRequired = function ( required ) {\n\t\tthis.required = !!required;\n\t\tthis.getItems()[ 0 ].fieldWidget.setRemovable( !this.required );\n\n\t\t// emit change event - while no content has changed, the state has, and\n\t\t// whatever (lack of) content there was may now have become (in)valid\n\t\tthis.emit( 'change' );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/details/uw.OtherDetailsWidget.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.DetailsWidget' is undefined.","line":7,"column":1,"nodeType":"Block","endLine":7,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\t/**\n\t * An other informations field in UploadWizard's \"Details\" step form.\n\t *\n\t * @class\n\t * @extends uw.DetailsWidget\n\t */\n\tuw.OtherDetailsWidget = function UWOtherDetailsWidget() {\n\t\tuw.OtherDetailsWidget.super.call( this );\n\n\t\tthis.textInput = new OO.ui.MultilineTextInputWidget( {\n\t\t\tclasses: [ 'mwe-upwiz-other-textarea', 'mwe-upwiz-otherDetailsWidget-other' ],\n\t\t\tautosize: true\n\t\t} );\n\n\t\t// Aggregate 'change' event\n\t\t// (but do not flash warnings in the user's face while they're typing)\n\t\tthis.textInput.on( 'change', OO.ui.debounce( this.emit.bind( this, 'change' ), 500 ) );\n\n\t\tthis.$element.addClass( 'mwe-upwiz-otherDetailsWidget' );\n\t\tthis.$element.append(\n\t\t\tthis.textInput.$element\n\t\t);\n\t};\n\tOO.inheritClass( uw.OtherDetailsWidget, uw.DetailsWidget );\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.OtherDetailsWidget.prototype.getWikiText = function () {\n\t\treturn this.textInput.getValue().trim();\n\t};\n\n\t/**\n\t * @inheritdoc\n\t * @return {Object} See #setSerialized\n\t */\n\tuw.OtherDetailsWidget.prototype.getSerialized = function () {\n\t\treturn {\n\t\t\tother: this.textInput.getValue()\n\t\t};\n\t};\n\n\t/**\n\t * @inheritdoc\n\t * @param {Object} serialized\n\t * @param {string} serialized.other Other informations text\n\t */\n\tuw.OtherDetailsWidget.prototype.setSerialized = function ( serialized ) {\n\t\tthis.textInput.setValue( serialized.other );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/details/uw.SingleLanguageInputWidget.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.DetailsWidget' is undefined.","line":6,"column":1,"nodeType":"Block","endLine":6,"endColumn":1},{"ruleId":"mediawiki/no-unlabeled-buttonwidget","severity":1,"message":"OO.ui.ButtonWidget has no label. Even icon-only buttons should set a label with invisibleLabel set to true.","line":45,"column":23,"nodeType":"NewExpression","messageId":"noLabel","endLine":50,"endColumn":6}],"suppressedMessages":[{"ruleId":"new-cap","severity":2,"message":"A constructor name should not start with a lowercase letter.","line":44,"column":36,"nodeType":"NewExpression","messageId":"lower","endLine":44,"endColumn":58,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-vars","severity":2,"message":"'thorough' is defined but never used.","line":150,"column":63,"nodeType":"Identifier","messageId":"unusedVar","endLine":150,"endColumn":71,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\t/**\n\t * A single language input field in UploadWizard's \"Details\" step form.\n\t *\n\t * @extends uw.DetailsWidget\n\t * @class\n\t * @param {Object} config\n\t * @param {Object} config.languages { langcode: text } map of languages\n\t * @param {Object} [config.defaultLanguage]\n\t * @param {boolean} [config.removable=true]\n\t * @param {mw.Message} [config.remove] Title text for remove icon\n\t * @param {number} [config.minLength=0] Minimum input length\n\t * @param {number} [config.maxLength=99999] Maximum input length\n\t */\n\tuw.SingleLanguageInputWidget = function UWSingleLanguageInputWidget( config ) {\n\t\tthis.config = Object.assign( {\n\t\t\tinputWidgetConstructor: OO.ui.MultilineTextInputWidget.bind( null, {\n\t\t\t\tclasses: [ 'mwe-upwiz-singleLanguageInputWidget-text' ],\n\t\t\t\tautosize: true\n\t\t\t} ),\n\t\t\tremovable: true,\n\t\t\tremove: mw.message( '' ),\n\t\t\tminLength: 0,\n\t\t\tmaxLength: 99999\n\t\t}, config );\n\n\t\tuw.SingleLanguageInputWidget.super.call( this );\n\t\tuw.ValidationMessageElement.call( this );\n\n\t\tif ( mw.loader.getState( 'ext.uls.mediawiki' ) === 'ready' ) {\n\t\t\tthis.languageSelector = new uw.UlsWidget( {\n\t\t\t\tlanguages: config.languages,\n\t\t\t\tclasses: [ 'mwe-upwiz-singleLanguageInputWidget-language' ]\n\t\t\t} );\n\t\t} else {\n\t\t\tthis.languageSelector = new uw.LanguageDropdownWidget( {\n\t\t\t\tlanguages: config.languages,\n\t\t\t\tclasses: [ 'mwe-upwiz-singleLanguageInputWidget-language' ]\n\t\t\t} );\n\t\t}\n\n\t\t// eslint-disable-next-line new-cap\n\t\tthis.textInput = new this.config.inputWidgetConstructor();\n\t\tthis.removeButton = new OO.ui.ButtonWidget( {\n\t\t\tclasses: [ 'mwe-upwiz-singleLanguageInputWidget-removeItem' ],\n\t\t\ticon: 'trash',\n\t\t\tframed: false,\n\t\t\ttitle: this.config.remove.exists() ? this.config.remove.text() : ''\n\t\t} );\n\n\t\tthis.removeButton.connect( this, {\n\t\t\tclick: 'onRemoveClick'\n\t\t} );\n\n\t\tthis.setLanguage( config.defaultLanguage || this.getDefaultLanguage() );\n\t\tthis.languageSelector.on( 'select', () => {\n\t\t\tthis.textInput.$input.attr( 'lang', this.languageSelector.getValue() );\n\t\t} );\n\t\tthis.languageSelector.connect( this, { select: [ 'emit', 'select' ] } );\n\t\t// Aggregate 'change' event\n\t\t// (but do not flash warnings in the user's face while they're typing)\n\t\tthis.textInput.on( 'change', OO.ui.debounce( this.emit.bind( this, 'change' ), 500 ) );\n\n\t\t// Note: ValidationMessageElement will append messages after this.$body\n\t\tthis.$body = $( '<div>' ).addClass( 'mwe-upwiz-singleLanguageInputWidget-body' ).append(\n\t\t\tthis.languageSelector.getElement(),\n\t\t\t// remove button will be hidden with CSS if it's not meant to be removable\n\t\t\tthis.removeButton.$element,\n\t\t\tthis.textInput.$element\n\t\t);\n\t\tthis.$element.addClass( 'mwe-upwiz-singleLanguageInputWidget' ).append( this.$body );\n\t\tthis.setRemovable( this.config.removable );\n\t};\n\tOO.inheritClass( uw.SingleLanguageInputWidget, uw.DetailsWidget );\n\tOO.mixinClass( uw.SingleLanguageInputWidget, uw.ValidatableElement );\n\n\t/**\n\t * Handle remove button click events.\n\t *\n\t * @private\n\t */\n\tuw.SingleLanguageInputWidget.prototype.onRemoveClick = function () {\n\t\tconst element = this.getElementGroup();\n\n\t\tif ( element && typeof element.removeItems === 'function' ) {\n\t\t\telement.removeItems( [ this ] );\n\t\t}\n\t};\n\n\t/**\n\t * Check if the given language code can be used for inputs.\n\t * If not, try finding a similar language code that can be.\n\t *\n\t * @public\n\t * @param {string} code Language code\n\t * @param {string} [fallback] Language code to use when there's nothing close,\n\t * defaults to result of #getDefaultLanguage\n\t * @return {string|null}\n\t */\n\tuw.SingleLanguageInputWidget.prototype.getClosestAllowedLanguage = function ( code, fallback ) {\n\t\t// Is this still needed?\n\t\tif ( code === 'nan' || code === 'minnan' ) {\n\t\t\tcode = 'zh-min-nan';\n\t\t}\n\t\tif ( this.config.languages[ code ] ) {\n\t\t\treturn code;\n\t\t}\n\t\tif ( code.includes( '-' ) ) {\n\t\t\treturn this.getClosestAllowedLanguage( code.slice( 0, code.lastIndexOf( '-' ) ), fallback );\n\t\t}\n\t\treturn arguments.length > 1 ? fallback : this.getDefaultLanguage();\n\t};\n\n\t/**\n\t * Get the default language to use for inputs.\n\t *\n\t * Choose a reasonable default based on user preferences and wiki config.\n\t *\n\t * @public\n\t * @return {string}\n\t */\n\tuw.SingleLanguageInputWidget.prototype.getDefaultLanguage = function () {\n\t\tif ( this.defaultLanguage !== undefined ) {\n\t\t\treturn this.defaultLanguage;\n\t\t}\n\n\t\tlet defaultLanguage = this.getClosestAllowedLanguage( mw.config.get( 'wgUserLanguage' ), null ) ||\n\t\t\tthis.getClosestAllowedLanguage( mw.config.get( 'wgContentLanguage' ), null ) ||\n\t\t\tthis.getClosestAllowedLanguage( 'en', null ) ||\n\t\t\tObject.keys( this.config.languages )[ 0 ];\n\n\t\t// Logic copied from MediaWiki:UploadForm.js\n\t\t// Per request from Portuguese and Brazilian users, treat Brazilian Portuguese as Portuguese.\n\t\tif ( defaultLanguage === 'pt-br' ) {\n\t\t\tdefaultLanguage = 'pt';\n\t\t// this was also in UploadForm.js, but without the heartwarming justification\n\t\t} else if ( defaultLanguage === 'en-gb' ) {\n\t\t\tdefaultLanguage = 'en';\n\t\t}\n\n\t\tthis.defaultLanguage = defaultLanguage;\n\t\treturn defaultLanguage;\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\t// eslint-disable-next-line no-unused-vars\n\tuw.SingleLanguageInputWidget.prototype.validate = function ( thorough ) {\n\t\tconst status = new mw.uploadWizard.ValidationStatus(),\n\t\t\ttext = this.textInput.getValue().trim();\n\n\t\tif ( text.length !== 0 && text.length < this.config.minLength ) {\n\t\t\t// Empty input is allowed\n\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-too-short', this.config.minLength ) );\n\t\t}\n\t\tif ( text.length > this.config.maxLength ) {\n\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-too-long', this.config.maxLength ) );\n\t\t}\n\n\t\treturn status.getErrors().length === 0 ? status.resolve() : status.reject();\n\t};\n\n\t/**\n\t * @param {Object} languages\n\t */\n\tuw.SingleLanguageInputWidget.prototype.updateLanguages = function ( languages ) {\n\t\tthis.languageSelector.updateLanguages( languages );\n\t};\n\n\t/**\n\t * @return {string} language code\n\t */\n\tuw.SingleLanguageInputWidget.prototype.getLanguage = function () {\n\t\treturn this.languageSelector.getValue();\n\t};\n\n\t/**\n\t * @param {string} value language code\n\t */\n\tuw.SingleLanguageInputWidget.prototype.setLanguage = function ( value ) {\n\t\tthis.languageSelector.setValue( value );\n\t\tthis.textInput.$input.attr( 'lang', value );\n\t\tthis.textInput.$input.attr( 'spellcheck', '' );\n\t};\n\n\t/**\n\t * @return {string} text input\n\t */\n\tuw.SingleLanguageInputWidget.prototype.getText = function () {\n\t\treturn this.textInput.getValue().trim();\n\t};\n\n\t/**\n\t * @param {string} value text input\n\t */\n\tuw.SingleLanguageInputWidget.prototype.setText = function ( value ) {\n\t\tthis.textInput.setValue( value );\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.SingleLanguageInputWidget.prototype.getWikiText = function () {\n\t\tlet language = this.getLanguage();\n\t\tconst text = this.getText();\n\n\t\tif ( !text ) {\n\t\t\treturn '';\n\t\t}\n\n\t\tif ( mw.UploadWizard.config.languageTemplateFixups[ language ] ) {\n\t\t\tlanguage = mw.UploadWizard.config.languageTemplateFixups[ language ];\n\t\t}\n\n\t\treturn '{{' + language + '|1=' + mw.Escaper.escapeForTemplate( text ) + '}}';\n\t};\n\n\t/**\n\t * @inheritdoc\n\t * @return {Object} See #setSerialized\n\t */\n\tuw.SingleLanguageInputWidget.prototype.getSerialized = function () {\n\t\treturn {\n\t\t\tlanguage: this.languageSelector.getValue(),\n\t\t\ttext: this.textInput.getValue(),\n\t\t\tremovable: this.config.removable\n\t\t};\n\t};\n\n\t/**\n\t * @inheritdoc\n\t * @param {Object} serialized\n\t * @param {string} serialized.language Language code\n\t * @param {string} serialized.text Text\n\t * @param {boolean} serialized.removable\n\t */\n\tuw.SingleLanguageInputWidget.prototype.setSerialized = function ( serialized ) {\n\t\tthis.setLanguage( serialized.language );\n\t\tthis.setText( serialized.text );\n\t\tthis.setRemovable( serialized.removable );\n\t};\n\n\t/**\n\t * @param {boolean} removable\n\t */\n\tuw.SingleLanguageInputWidget.prototype.setRemovable = function ( removable ) {\n\t\tthis.config.removable = !!removable;\n\t\tthis.$element.toggleClass( 'mwe-upwiz-singleLanguageInputWidget-removable', this.config.removable );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/details/uw.StatementWidget.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'dataValues.DataValue' is undefined.","line":70,"column":1,"nodeType":"Block","endLine":70,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'datamodel.StatementList' is undefined.","line":105,"column":1,"nodeType":"Block","endLine":105,"endColumn":1},{"ruleId":"es-x/no-iterator-prototype-toarray","severity":1,"message":"ES2025 'Iterator.prototype.toArray' method is forbidden.","line":108,"column":22,"nodeType":"MemberExpression","messageId":"forbidden","endLine":108,"endColumn":34},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'datamodel.StatementList' is undefined.","line":117,"column":1,"nodeType":"Block","endLine":117,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'dataValues.DataValue' is undefined.","line":131,"column":1,"nodeType":"Block","endLine":131,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'datamodel.Statement' is undefined.","line":132,"column":1,"nodeType":"Block","endLine":132,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'datamodel.StatementList' is undefined.","line":145,"column":1,"nodeType":"Block","endLine":145,"endColumn":1}],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":2,"message":"'thorough' is defined but never used.","line":93,"column":53,"nodeType":"Identifier","messageId":"unusedVar","endLine":93,"endColumn":61,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\t/**\n\t * @constructor\n\t * @param {Object} config Configuration options\n\t * @param {string} config.propertyId Property ID (e.g. P180 id of `depicts` property)\n\t */\n\tuw.StatementWidget = function UWStatementWidget( config ) {\n\t\tconst EntityInputWidget =\n\t\t\tmw.loader.require( 'wikibase.mediainfo.statements' ).inputs.EntityInputWidget,\n\t\t\tFormatValueElement =\n\t\t\t\tmw.loader.require( 'wikibase.mediainfo.base' ).FormatValueElement;\n\n\t\tthis.propertyId = config.propertyId;\n\t\tthis.datamodel = mw.loader.require( 'wikibase.datamodel' );\n\t\tthis.formatValueElement = new FormatValueElement();\n\t\tthis.placeholder = config.placeholder;\n\n\t\tuw.StatementWidget.super.call( this, config );\n\t\tthis.$element.addClass( 'mwe-upwiz-statementWidget' );\n\n\t\tthis.input = new EntityInputWidget( {\n\t\t\tisQualifier: false,\n\t\t\ttype: 'wikibase-entityid',\n\t\t\tdisabled: false,\n\t\t\thideSnakTypeWidget: true,\n\t\t\t$overlay: true,\n\t\t\ticon: '',\n\t\t\tlabel: '',\n\t\t\tplaceholder: this.placeholder\n\t\t} );\n\t\tthis.input.$element.addClass( 'mwe-upwiz-statement-input' );\n\t\tthis.$element.append( this.input.$element );\n\t\tthis.input.connect( this, { add: 'addTagFromInput' } );\n\t\tthis.input.connect( this, { focus: 'onFocus' } );\n\t\tthis.input.connect( this, { blur: 'onBlur' } );\n\n\t\tOO.ui.mixin.GroupWidget.call( this, config );\n\t\tthis.setGroupElement( this.$element );\n\t\tthis.aggregate( {\n\t\t\tremove: 'itemRemove',\n\t\t\tchange: [ 'emit', 'change' ]\n\t\t} );\n\t\tthis.connect( this, {\n\t\t\titemRemove: 'onTagRemove'\n\t\t} );\n\t};\n\n\tOO.inheritClass( uw.StatementWidget, OO.ui.Widget );\n\tOO.mixinClass( uw.StatementWidget, OO.ui.mixin.GroupWidget );\n\tOO.mixinClass( uw.StatementWidget, uw.ValidatableElement );\n\n\tuw.StatementWidget.prototype.onFocus = function () {\n\t\tthis.$element.addClass( 'mwe-upwiz-statementWidget-active' );\n\t};\n\n\tuw.StatementWidget.prototype.onBlur = function () {\n\t\tthis.input.clear();\n\t\tthis.$element.removeClass( 'mwe-upwiz-statementWidget-active' );\n\t};\n\n\t/**\n\t * Receives a DataValue from the input widget and uses it to create a new Tag\n\t */\n\tuw.StatementWidget.prototype.addTagFromInput = function () {\n\t\tthis.addTag( this.input.getData() );\n\t};\n\n\t/**\n\t * @param {dataValues.DataValue} dataValue\n\t */\n\tuw.StatementWidget.prototype.addTag = function ( dataValue ) {\n\t\tconst data = this.createStatement( dataValue );\n\n\t\tthis.formatValueElement.formatValue(\n\t\t\tdataValue, 'text/plain', null, this.propertyId\n\t\t).then( ( label ) => {\n\t\t\tif ( this.findItemFromData( data ) === null ) {\n\t\t\t\tconst tag = new OO.ui.TagItemWidget( {\n\t\t\t\t\tlabel: label, data: data\n\t\t\t\t} );\n\t\t\t\tthis.addItems( [ tag ] );\n\t\t\t\tthis.updateInputSize();\n\t\t\t}\n\t\t\tthis.input.clear();\n\t\t} );\n\t};\n\n\t/**\n\t * @inheritDoc\n\t */\n\t// eslint-disable-next-line no-unused-vars\n\tuw.StatementWidget.prototype.validate = function ( thorough ) {\n\t\tconst status = new uw.ValidationStatus(),\n\t\t\tmaxDepicts = 3;\n\n\t\tif ( this.getItems().length > maxDepicts ) {\n\t\t\tstatus.addNotice( mw.message( 'mwe-upwiz-statements-too-many-items', maxDepicts ) );\n\t\t}\n\n\t\treturn status.resolve();\n\t};\n\n\t/**\n\t * @param {datamodel.StatementList} data\n\t */\n\tuw.StatementWidget.prototype.setData = function ( data ) {\n\t\tconst statements = data.toArray();\n\n\t\tstatements.forEach( ( statement ) => {\n\t\t\tconst dataValue = statement.getClaim().getMainSnak().getValue();\n\t\t\tthis.addTag( dataValue );\n\t\t} );\n\t};\n\n\t/**\n\t * @param {datamodel.StatementList} data\n\t */\n\tuw.StatementWidget.prototype.resetData = function ( data ) {\n\t\tthis.clearItems();\n\t\tthis.setData( data );\n\t};\n\n\tuw.StatementWidget.prototype.onTagRemove = function ( tag ) {\n\t\tconst item = this.findItemFromData( tag.getData() );\n\t\tthis.removeItems( [ item ] );\n\t\tthis.updateInputSize();\n\t};\n\n\t/**\n\t * @param {dataValues.DataValue} dataValue\n\t * @return {datamodel.Statement}\n\t */\n\tuw.StatementWidget.prototype.createStatement = function ( dataValue ) {\n\t\tconst snak = new this.datamodel.PropertyValueSnak( this.propertyId, dataValue, null );\n\n\t\treturn new this.datamodel.Statement(\n\t\t\tnew this.datamodel.Claim(\n\t\t\t\tsnak\n\t\t\t)\n\t\t);\n\t};\n\n\t/**\n\t * @return {datamodel.StatementList}\n\t */\n\tuw.StatementWidget.prototype.getStatementList = function () {\n\t\treturn new this.datamodel.StatementList(\n\t\t\tthis.getItems().map( ( item ) => item.getData() )\n\t\t);\n\t};\n\n\t/**\n\t * Copied (more or less) from OO.ui.TagMultiselectWidget\n\t *\n\t * Update the dimensions of the text input field to encompass all available area.\n\t * This is especially relevant for when the input is at the edge of a line\n\t * and should get smaller. The usual operation (as an inline-block with min-width)\n\t * does not work in that case, pushing the input downwards to the next line.\n\t *\n\t * @private\n\t */\n\tuw.StatementWidget.prototype.updateInputSize = function () {\n\t\tconst containerWidth = this.$element.width();\n\n\t\tlet tagsWidth = 0;\n\t\tthis.input.$element.detach();\n\t\tthis.getItems().forEach( ( item ) => {\n\t\t\ttagsWidth += item.$element.outerWidth();\n\t\t} );\n\t\tlet newWidth = containerWidth - tagsWidth - 20;\n\n\t\t// if the new width is too narrow, expand to the container size instead (forces input onto\n\t\t// a new line)\n\t\tif ( this.placeholderWidth === undefined ) {\n\t\t\t// FIXME - calculate this rather than setting an arbitrary value\n\t\t\tthis.placeholderWidth = 200;\n\t\t}\n\t\tif ( newWidth < this.placeholderWidth ) {\n\t\t\tnewWidth = containerWidth;\n\t\t}\n\n\t\tthis.input.$element.width( newWidth );\n\t\tthis.$group.append( this.input.$element );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/details/uw.TitleDetailsWidget.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.DetailsWidget' is undefined.","line":10,"column":1,"nodeType":"Block","endLine":10,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"thorough\" type.","line":112,"column":1,"nodeType":"Block","endLine":112,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":113,"column":1,"nodeType":"Block","endLine":113,"endColumn":1},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":161,"column":30,"nodeType":"CallExpression","endLine":161,"endColumn":80},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":207,"column":10,"nodeType":"CallExpression","endLine":207,"endColumn":34},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":215,"column":27,"nodeType":"CallExpression","endLine":215,"endColumn":62},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":217,"column":21,"nodeType":"CallExpression","endLine":217,"endColumn":55}],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":2,"message":"'thorough' is defined but never used.","line":116,"column":56,"nodeType":"Identifier","messageId":"unusedVar","endLine":116,"endColumn":64,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":7,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\tconst NS_FILE = mw.config.get( 'wgNamespaceIds' ).file,\n\t\tbyteLength = require( 'mediawiki.String' ).byteLength;\n\n\t/**\n\t * A title field in UploadWizard's \"Details\" step form.\n\t *\n\t * @class\n\t * @extends uw.DetailsWidget\n\t * @param {Object} [config]\n\t */\n\tuw.TitleDetailsWidget = function UWTitleDetailsWidget( config ) {\n\t\tconfig = config || {};\n\t\tuw.TitleDetailsWidget.super.call( this );\n\n\t\tthis.config = config;\n\t\tthis.extension = config.extension;\n\t\t// We wouldn't want or use any of mw.widgets.TitleInputWidget functionality.\n\t\tthis.titleInput = new OO.ui.TextInputWidget( {\n\t\t\tclasses: [ 'mwe-title', 'mwe-upwiz-titleDetailsWidget-title' ],\n\t\t\t// Add 1 character to the text input limit,\n\t\t\t// or the user will never see the error message if they reach the max title length.\n\t\t\tmaxLength: config.maxLength + 1\n\t\t} );\n\n\t\t// Aggregate 'change' event (with delay)\n\t\tthis.titleInput.on( 'change', OO.ui.debounce( this.emit.bind( this, 'change' ), 500 ) );\n\n\t\tthis.$element.addClass( 'mwe-upwiz-titleDetailsWidget' );\n\t\tthis.$element.append(\n\t\t\tthis.titleInput.$element\n\t\t);\n\n\t\tthis.isDuplicate = false;\n\t\tthis.on( 'change', () => {\n\t\t\tthis.isDuplicate = false;\n\t\t} );\n\t};\n\tOO.inheritClass( uw.TitleDetailsWidget, uw.DetailsWidget );\n\tOO.mixinClass( uw.TitleDetailsWidget, uw.ValidatableElement );\n\n\t/**\n\t * Reliably turn input into a MediaWiki title that is located in the 'File:' namespace.\n\t * Also applies file-specific checks ($wgIllegalFileChars).\n\t *\n\t * var title = uw.TitleDetailsWidget.static.makeTitleInFileNS( 'filename.ext' );\n\t *\n\t * @static\n\t * @param {string} filename Desired file name; optionally with 'File:' namespace prefixed\n\t * @return {mw.Title|null}\n\t */\n\tuw.TitleDetailsWidget.static.makeTitleInFileNS = function ( filename ) {\n\t\tlet mwTitle = mw.Title.newFromText( filename, NS_FILE );\n\t\tconst illegalFileChars = new RegExp( '[' + mw.config.get( 'wgIllegalFileChars', '' ) + ']' );\n\t\tif ( mwTitle && mwTitle.getNamespaceId() !== NS_FILE ) {\n\t\t\t// Force file namespace\n\t\t\tmwTitle = mw.Title.makeTitle( NS_FILE, filename );\n\t\t}\n\t\tif ( mwTitle && ( illegalFileChars.test( mwTitle.getMainText() ) || mwTitle.fragment !== null ) ) {\n\t\t\t// Consider the title invalid if it contains characters disallowed in file names\n\t\t\tmwTitle = null;\n\t\t}\n\t\treturn mwTitle;\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.TitleDetailsWidget.prototype.pushPending = function () {\n\t\tthis.titleInput.pushPending();\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.TitleDetailsWidget.prototype.popPending = function () {\n\t\tthis.titleInput.popPending();\n\t};\n\n\t/**\n\t * Get a mw.Title object for current input.\n\t *\n\t * @return {mw.Title|null}\n\t */\n\tuw.TitleDetailsWidget.prototype.getTitle = function () {\n\t\treturn this.buildTitleFromInput( this.titleInput.getValue() );\n\t};\n\n\t/**\n\t * Get a mw.Title object for a given value.\n\t *\n\t * @param {string} value\n\t * @return {mw.Title}\n\t */\n\tuw.TitleDetailsWidget.prototype.buildTitleFromInput = function ( value ) {\n\t\tvalue = value.trim();\n\t\tif ( !value ) {\n\t\t\treturn null;\n\t\t}\n\t\tconst extRegex = new RegExp( '\\\\.' + this.extension + '$', 'i' );\n\t\tconst cleaned = value.replace( extRegex, '' ).replace( /\\.+$/g, '' ).trim();\n\t\tconst title = uw.TitleDetailsWidget.static.makeTitleInFileNS( cleaned + '.' + this.extension );\n\t\treturn title;\n\t};\n\n\tuw.TitleDetailsWidget.prototype.setIsDuplicate = function ( isDuplicate ) {\n\t\tthis.isDuplicate = !!isDuplicate;\n\t};\n\n\t/**\n\t * @param thorough\n\t * @return {jQuery.Promise<uw.ValidationStatus>}\n\t */\n\t// eslint-disable-next-line no-unused-vars\n\tuw.TitleDetailsWidget.prototype.validate = function ( thorough ) {\n\t\tconst status = new uw.ValidationStatus(),\n\t\t\tvalue = this.titleInput.getValue().trim(),\n\t\t\ttitle = this.buildTitleFromInput( value ),\n\t\t\t// max title length is dependent on DB column size and is bytes rather than characters\n\t\t\tlength = byteLength( value ),\n\t\t\t// ... however MIN title length is easier for users to understand expressed in\n\t\t\t// characters rather than bytes\n\t\t\tcharLength = value.length;\n\n\t\tif ( value === '' ) {\n\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-title-blank' ) );\n\t\t\treturn status.reject();\n\t\t}\n\n\t\tif ( this.config.minLength && charLength < this.config.minLength ) {\n\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-title-too-few-characters', this.config.minLength ) );\n\t\t\treturn status.reject();\n\t\t}\n\n\t\tif ( this.config.maxLength && length > this.config.maxLength ) {\n\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-title-too-long', this.config.maxLength ) );\n\t\t\treturn status.reject();\n\t\t}\n\n\t\tif ( !title ) {\n\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-title-invalid' ) );\n\t\t\treturn status.reject();\n\t\t}\n\n\t\tif ( this.isDuplicate ) {\n\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-title-duplicate' ) );\n\t\t\treturn status.reject();\n\t\t}\n\n\t\treturn mw.DestinationChecker.checkTitle( title.getPrefixedText() )\n\t\t\t.then( ( result ) => {\n\t\t\t\tthis.processDestinationCheck( result ).forEach( ( error ) => status.addError( error ) );\n\n\t\t\t\tif ( result.blacklist.unavailable ) {\n\t\t\t\t\t// We don't have a title blacklist, so just check for some likely undesirable patterns.\n\t\t\t\t\t// Messages:\n\t\t\t\t\t// mwe-upwiz-error-title-invalid, mwe-upwiz-error-title-senselessimagename,\n\t\t\t\t\t// mwe-upwiz-error-title-thumbnail, mwe-upwiz-error-title-extension,\n\t\t\t\t\tmw.QuickTitleChecker.checkTitle( title.getNameText() )\n\t\t\t\t\t\t.map( ( errorCode ) => mw.message( 'mwe-upwiz-error-title-' + errorCode ) )\n\t\t\t\t\t\t.forEach( ( error ) => status.addError( error ) );\n\t\t\t\t}\n\n\t\t\t\treturn status.getErrors().length === 0 ? status.resolve() : status.reject();\n\t\t\t} );\n\t};\n\n\t/**\n\t * Process the result of a destination filename check, return array of mw.Messages objects\n\t * representing errors.\n\t *\n\t * @private\n\t * @param {Object} result Result to process, output from mw.DestinationChecker\n\t * @return {mw.Message[]} Error messages\n\t */\n\tuw.TitleDetailsWidget.prototype.processDestinationCheck = function ( result ) {\n\t\tif ( result.unique.isUnique && result.blacklist.notBlacklisted && !result.unique.isProtected ) {\n\t\t\treturn [];\n\t\t}\n\n\t\t// Something is wrong with this title.\n\t\tconst errors = [];\n\n\t\tlet titleString;\n\t\ttry {\n\t\t\ttitleString = result.unique.title || result.title;\n\t\t\ttitleString = uw.TitleDetailsWidget.static.makeTitleInFileNS( titleString ).getPrefixedText();\n\t\t} catch ( e ) {\n\t\t\t// Unparseable result? This shouldn't happen, we checked for that earlier...\n\t\t\terrors.push( mw.message( 'mwe-upwiz-error-title-invalid' ) );\n\t\t\treturn errors;\n\t\t}\n\n\t\tif ( !result.unique.isUnique ) {\n\t\t\t// result is NOT unique\n\t\t\tif ( result.unique.href ) {\n\t\t\t\terrors.push( mw.message( 'mwe-upwiz-fileexists-replace-on-page-v2' ) );\n\t\t\t} else {\n\t\t\t\terrors.push( mw.message( 'mwe-upwiz-fileexists-replace-no-link', titleString ) );\n\t\t\t}\n\t\t} else if ( result.unique.isProtected ) {\n\t\t\terrors.push( mw.message( 'mwe-upwiz-error-title-protected' ) );\n\t\t} else {\n\t\t\t// check whether we have a custom error message for this blacklist reason\n\t\t\tlet messageKey = 'mwe-upwiz-blacklisted-details-' + result.blacklist.blacklistMessage;\n\t\t\tif ( !mw.message( messageKey ).exists() ) {\n\t\t\t\tmessageKey = 'mwe-upwiz-blacklisted-details';\n\t\t\t}\n\n\t\t\tconst messageParams = [\n\t\t\t\tmessageKey,\n\t\t\t\ttitleString,\n\t\t\t\tfunction () {\n\t\t\t\t\tconst titleMessage = mw.message( messageKey + '-title' ),\n\t\t\t\t\t\ttitle = titleMessage.exists() ? titleMessage.text() : '',\n\t\t\t\t\t\ttextMessage = mw.message( messageKey + '-text' ),\n\t\t\t\t\t\ttext = textMessage.exists() ? textMessage.text() : result.blacklist.blacklistReason;\n\n\t\t\t\t\tmw.errorDialog( text, title );\n\t\t\t\t}\n\t\t\t];\n\n\t\t\t// feedback request for titleblacklist\n\t\t\tif ( mw.UploadWizard.config.blacklistIssuesPage !== undefined && mw.UploadWizard.config.blacklistIssuesPage !== '' ) {\n\t\t\t\tmessageParams[ 0 ] = 'mwe-upwiz-blacklisted-details-feedback';\n\t\t\t\tmessageParams.push( () => {\n\t\t\t\t\tconst feedback = new mw.Feedback( {\n\t\t\t\t\t\ttitle: new mw.Title( mw.UploadWizard.config.blacklistIssuesPage ),\n\t\t\t\t\t\tdialogTitleMessageKey: 'mwe-upwiz-feedback-title'\n\t\t\t\t\t} );\n\t\t\t\t\tfeedback.launch( {\n\t\t\t\t\t\tmessage: mw.message( 'mwe-upwiz-feedback-blacklist-line-intro', result.blacklist.blacklistLine ).text(),\n\t\t\t\t\t\tsubject: mw.message( 'mwe-upwiz-feedback-blacklist-subject', titleString ).text()\n\t\t\t\t\t} );\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\terrors.push( mw.message.apply( mw, messageParams ) );\n\t\t}\n\n\t\treturn errors;\n\t};\n\n\t/**\n\t * @inheritdoc\n\t */\n\tuw.TitleDetailsWidget.prototype.getWikiText = function () {\n\t\treturn this.titleInput.getValue().trim();\n\t};\n\n\t/**\n\t * @inheritdoc\n\t * @return {Object} See #setSerialized\n\t */\n\tuw.TitleDetailsWidget.prototype.getSerialized = function () {\n\t\treturn {\n\t\t\ttitle: this.titleInput.getValue()\n\t\t};\n\t};\n\n\t/**\n\t * @inheritdoc\n\t * @param {Object} serialized\n\t * @param {string} serialized.language Title language code\n\t * @param {string} serialized.title Title text\n\t */\n\tuw.TitleDetailsWidget.prototype.setSerialized = function ( serialized ) {\n\t\tconst titleInput = this.titleInput,\n\t\t\ttitle = serialized.title;\n\n\t\ttitleInput.setValue( title );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/details/uw.UlsWidget.js","messages":[{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":30,"column":3,"nodeType":"CallExpression","endLine":30,"endColumn":43},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.UlsWidget' is undefined.","line":99,"column":1,"nodeType":"Block","endLine":99,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\t/**\n\t * A ULS within a description field in UploadWizard's \"Details\" step form.\n\t *\n\t * @class\n\t * @param {Object} [config]\n\t * @param {Object} [config.languages] Keys are 2-letter language codes, values are language autonyms\n\t * @param {Array} [config.classes] Classes to apply to the ULS container div\n\t */\n\tuw.UlsWidget = function UWUlsWidget( config ) {\n\t\tuw.UlsWidget.super.call( this );\n\n\t\tthis.$element = $( '<div>' )\n\t\t\t.append(\n\t\t\t\t$( '<span>' )\n\t\t\t\t\t.attr( 'tabindex', 0 )\n\t\t\t\t\t.addClass( 'oo-ui-dropdownWidget-handle' )\n\t\t\t\t\t.addClass( 'oo-ui-widget' )\n\t\t\t\t\t.addClass( 'oo-ui-indicatorElement' )\n\t\t\t\t\t.append(\n\t\t\t\t\t\t$( '<span>' ).addClass( 'oo-ui-labelElement-label' ),\n\t\t\t\t\t\t$( '<span>' )\n\t\t\t\t\t\t\t.addClass( 'oo-ui-indicatorElement-indicator' )\n\t\t\t\t\t\t\t.addClass( 'oo-ui-indicator-down' )\n\t\t\t\t\t)\n\t\t\t)\n\t\t\t.addClass( 'oo-ui-dropdownWidget' )\n\t\t\t.addClass( 'oo-ui-widget-enabled' );\n\t\tthis.$element.addClass( config.classes );\n\n\t\t// Show the ULS when a user tabs into the language selection field\n\t\tthis.$element.find( '.oo-ui-dropdownWidget-handle' ).on( 'keyup', function ( e ) {\n\t\t\tif ( e.key === 'Tab' ) {\n\t\t\t\t$( this ).trigger( 'click' );\n\t\t\t}\n\t\t} );\n\n\t\tif ( mw.loader.getState( 'ext.uls.mediawiki' ) === 'ready' ) {\n\t\t\tthis.initialiseUls( config.languages );\n\t\t}\n\t};\n\tOO.inheritClass( uw.UlsWidget, OO.ui.Widget );\n\tOO.mixinClass( uw.UlsWidget, OO.EventEmitter );\n\n\tuw.UlsWidget.prototype.initialiseUls = function ( languages ) {\n\t\tconst ulsWidget = this;\n\n\t\tthis.languages = languages;\n\n\t\tthis.uls = this.$element.uls( {\n\t\t\tonSelect: function ( language ) {\n\t\t\t\tulsWidget.setValue( language );\n\t\t\t\tulsWidget.$element.parent().find( '.oo-ui-inputWidget-input' ).trigger( 'focus' );\n\t\t\t},\n\t\t\tlanguages: languages,\n\t\t\tulsPurpose: 'upload-wizard-description',\n\t\t\tonVisible: function () {\n\t\t\t\t// Re-position the ULS *after* the widget has been rendered, so that we can be\n\t\t\t\t// sure it's in the right place\n\t\t\t\tconst offset = ulsWidget.$element.offset();\n\t\t\t\tif ( this.$menu.css( 'direction' ) === 'rtl' ) {\n\t\t\t\t\toffset.left =\n\t\t\t\t\t\toffset.left - parseInt( this.$menu.css( 'width' ) ) + ulsWidget.$element.width();\n\t\t\t\t}\n\t\t\t\tthis.$menu.css( offset );\n\t\t\t}\n\t\t} );\n\t};\n\n\t/**\n\t * @param {Object} languages\n\t */\n\tuw.UlsWidget.prototype.updateLanguages = function ( languages ) {\n\t\tthis.uls.off().removeData( 'uls' );\n\t\tthis.initialiseUls( languages );\n\t};\n\n\t/**\n\t * @param {string} value\n\t */\n\tuw.UlsWidget.prototype.setValue = function ( value ) {\n\t\tconst current = this.languageValue;\n\t\tthis.languageValue = value;\n\t\tthis.$element.find( '.oo-ui-labelElement-label' ).text( this.languages[ value ] );\n\t\tif ( current !== value ) {\n\t\t\tthis.emit( 'select' );\n\t\t}\n\t};\n\n\t/**\n\t * @return {string}\n\t */\n\tuw.UlsWidget.prototype.getValue = function () {\n\t\treturn this.languageValue;\n\t};\n\n\t/**\n\t * @return {uw.UlsWidget}\n\t */\n\tuw.UlsWidget.prototype.getElement = function () {\n\t\treturn this.$element;\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/handlers/mw.ApiUploadFormDataHandler.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/handlers/mw.ApiUploadHandler.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/handlers/mw.ApiUploadPostHandler.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/jquery.arrowSteps/jquery.arrowSteps.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/jquery/jquery.morphCrossfade.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-animate","severity":1,"message":"Prefer CSS transitions to .animate","line":108,"column":6,"nodeType":"CallExpression","endLine":110,"endColumn":9,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-animate","severity":1,"message":"Prefer CSS transitions to .animate","line":116,"column":5,"nodeType":"CallExpression","endLine":121,"endColumn":8,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-animate","severity":1,"message":"Prefer CSS transitions to .animate","line":123,"column":5,"nodeType":"CallExpression","endLine":123,"endColumn":54,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.DestinationChecker.js","messages":[{"ruleId":"jsdoc/require-returns","severity":1,"message":"Found more than one @return declaration.","line":14,"column":3,"nodeType":"Block","endLine":23,"endColumn":6},{"ruleId":"jsdoc/require-returns-check","severity":1,"message":"Found more than one @return declaration.","line":14,"column":3,"nodeType":"Block","endLine":23,"endColumn":6},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Found more than one @return declaration.","line":35,"column":3,"nodeType":"Block","endLine":45,"endColumn":6},{"ruleId":"jsdoc/require-returns-check","severity":1,"message":"Found more than one @return declaration.","line":35,"column":3,"nodeType":"Block","endLine":45,"endColumn":6},{"ruleId":"jsdoc/require-returns","severity":1,"message":"Found more than one @return declaration.","line":80,"column":3,"nodeType":"Block","endLine":91,"endColumn":6},{"ruleId":"jsdoc/require-returns-check","severity":1,"message":"Found more than one @return declaration.","line":80,"column":3,"nodeType":"Block","endLine":91,"endColumn":6}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function () {\n\n\t/**\n\t * @class\n\t */\n\tmw.DestinationChecker = {\n\n\t\tapi: new mw.Api(),\n\n\t\t// cached results from uniqueness api calls\n\t\tcachedResult: {},\n\t\tcachedBlacklist: {},\n\n\t\t/**\n\t\t * Check title for validity.\n\t\t *\n\t\t * @param {string} title Title to check\n\t\t * @return {jQuery.Promise}\n\t\t * @return {Function} return.done\n\t\t * @return {string} return.done.title The title that was passed in\n\t\t * @return {Object|boolean} return.done.blacklist See #checkBlacklist\n\t\t * @return {Object|boolean} return.done.unique See #checkUnique\n\t\t */\n\t\tcheckTitle: function ( title ) {\n\t\t\treturn $.when(\n\t\t\t\tthis.checkUnique( title ),\n\t\t\t\tthis.checkBlacklist( title )\n\t\t\t).then( ( unique, blacklist ) => ( {\n\t\t\t\tunique: unique,\n\t\t\t\tblacklist: blacklist,\n\t\t\t\ttitle: title\n\t\t\t} ) );\n\t\t},\n\n\t\t/**\n\t\t * Async check if a title is in the titleblacklist.\n\t\t *\n\t\t * @param {string} title Title to check against the blacklist\n\t\t * @return {jQuery.Promise}\n\t\t * @return {Function} return.done\n\t\t * @return {boolean} return.done.notBlacklisted\n\t\t * @return {string} [return.done.blacklistReason] See mw.Api#isBlacklisted\n\t\t * @return {string} [return.done.blacklistMessage] See mw.Api#isBlacklisted\n\t\t * @return {string} [return.done.blacklistLine] See mw.Api#isBlacklisted\n\t\t */\n\t\tcheckBlacklist: function ( title ) {\n\t\t\t/**\n\t\t\t * Process result of a TitleBlacklist API call.\n\t\t\t *\n\t\t\t * @private\n\t\t\t * @param {Object|boolean} blacklistResult `false` if not blacklisted, object if blacklisted\n\t\t\t * @return {Object}\n\t\t\t */\n\t\t\tconst blacklistResultProcessor = ( blacklistResult ) => {\n\t\t\t\tlet result;\n\n\t\t\t\tif ( blacklistResult === false ) {\n\t\t\t\t\tresult = { notBlacklisted: true };\n\t\t\t\t} else {\n\t\t\t\t\tresult = {\n\t\t\t\t\t\tnotBlacklisted: false,\n\t\t\t\t\t\tblacklistReason: blacklistResult.reason,\n\t\t\t\t\t\tblacklistMessage: blacklistResult.message,\n\t\t\t\t\t\tblacklistLine: blacklistResult.line\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tthis.cachedBlacklist[ title ] = result;\n\t\t\t\treturn result;\n\t\t\t};\n\n\t\t\tif ( this.cachedBlacklist[ title ] !== undefined ) {\n\t\t\t\treturn $.Deferred().resolve( this.cachedBlacklist[ title ] );\n\t\t\t}\n\n\t\t\t// it's not blacklisted, because the API isn't even available\n\t\t\treturn mw.loader.using( 'mediawiki.api.titleblacklist' ).then( () => this.api.isBlacklisted( title ).then( blacklistResultProcessor ), () => $.Deferred().resolve( { notBlacklisted: true, unavailable: true } ) );\n\t\t},\n\n\t\t/**\n\t\t * Async check if a filename is unique. Can be attached to a field's change() event\n\t\t * This is a more abstract version of AddMedia/UploadHandler.js::doDestCheck\n\t\t *\n\t\t * @param {string} title Title to check for uniqueness\n\t\t * @return {jQuery.Promise}\n\t\t * @return {Function} return.done\n\t\t * @return {boolean} return.done.isUnique\n\t\t * @return {boolean} [return.done.isProtected]\n\t\t * @return {Object} [return.done.img] Image info\n\t\t * @return {string} [return.done.href] URL to file description page\n\t\t */\n\t\tcheckUnique: function ( title ) {\n\t\t\tconst NS_FILE = mw.config.get( 'wgNamespaceIds' ).file;\n\n\t\t\tconst titleObj = mw.Title.newFromText( title );\n\t\t\tconst ext = mw.Title.normalizeExtension( titleObj.getExtension() || '' );\n\t\t\t// Strip namespace and file extension\n\t\t\tconst prefix = titleObj.getNameText();\n\n\t\t\t/**\n\t\t\t * Process result of a an imageinfo API call.\n\t\t\t *\n\t\t\t * @private\n\t\t\t * @param {Object} data API result\n\t\t\t * @return {Object}\n\t\t\t */\n\t\t\tconst checkUniqueProcessor = ( data ) => {\n\t\t\t\tlet result, protection, pageId, ntitle, ntitleObj, img;\n\n\t\t\t\tresult = { isUnique: true };\n\n\t\t\t\tif ( data.query && data.query.pages ) {\n\t\t\t\t\t// The API will check for files with that filename.\n\t\t\t\t\t// If no file found: a page with a key of -1 and no imageinfo\n\t\t\t\t\t// If file found on another repository, such as when the wiki is using InstantCommons: page with a key of -1, plus imageinfo\n\t\t\t\t\t// If file found on this repository: page with some positive numeric key\n\t\t\t\t\tif ( data.query.pages[ -1 ] && !data.query.pages[ -1 ].imageinfo ) {\n\t\t\t\t\t\tprotection = data.query.pages[ -1 ].protection;\n\t\t\t\t\t\tif ( protection && protection.length > 0 ) {\n\t\t\t\t\t\t\tprotection.forEach( ( val ) => {\n\t\t\t\t\t\t\t\tif ( !mw.config.get( 'wgUserGroups' ).includes( val.level ) ) {\n\t\t\t\t\t\t\t\t\tresult = {\n\t\t\t\t\t\t\t\t\t\tisUnique: true,\n\t\t\t\t\t\t\t\t\t\tisProtected: true\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// No conflict found on any repository this wiki uses\n\t\t\t\t\t\t\tresult = { isUnique: true };\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfor ( pageId in data.query.pages ) {\n\t\t\t\t\t\t\tif ( !Object.prototype.hasOwnProperty.call( data.query.pages, pageId ) ) {\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tntitle = data.query.pages[ pageId ].title;\n\t\t\t\t\t\t\tntitleObj = mw.Title.newFromText( ntitle );\n\t\t\t\t\t\t\tif ( ntitleObj.getNameText() !== prefix ) {\n\t\t\t\t\t\t\t\t// It's a different file name entirely\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif ( ext !== mw.Title.normalizeExtension( ntitleObj.getExtension() || '' ) ) {\n\t\t\t\t\t\t\t\t// It's a different extension, that's fine (e.g. to upload a SVG version of a PNG file)\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Conflict found, this filename is NOT unique\n\n\t\t\t\t\t\t\tif ( !data.query.pages[ pageId ].imageinfo ) {\n\t\t\t\t\t\t\t\t// This means that there's a page, but it's not a file. Well,\n\t\t\t\t\t\t\t\t// we should really report that anyway, but we shouldn't process\n\t\t\t\t\t\t\t\t// it like a file, and we should defer to other entries that may be files.\n\t\t\t\t\t\t\t\tresult = {\n\t\t\t\t\t\t\t\t\tisUnique: false,\n\t\t\t\t\t\t\t\t\ttitle: ntitle,\n\t\t\t\t\t\t\t\t\timg: null,\n\t\t\t\t\t\t\t\t\thref: null\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\timg = data.query.pages[ pageId ].imageinfo[ 0 ];\n\n\t\t\t\t\t\t\tresult = {\n\t\t\t\t\t\t\t\tisUnique: false,\n\t\t\t\t\t\t\t\timg: img,\n\t\t\t\t\t\t\t\ttitle: ntitle,\n\t\t\t\t\t\t\t\thref: img.descriptionurl\n\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn result;\n\t\t\t};\n\n\t\t\tif ( this.cachedResult[ title ] !== undefined ) {\n\t\t\t\treturn $.Deferred().resolve( this.cachedResult[ title ] );\n\t\t\t}\n\n\t\t\t// Setup the request -- will return thumbnail data if it finds one\n\t\t\t// XXX do not use iiurlwidth as it will create a thumbnail\n\t\t\treturn $.when(\n\t\t\t\t// Checks for exact matches on this wiki and foreign file repos\n\t\t\t\tthis.api.get( {\n\t\t\t\t\taction: 'query',\n\t\t\t\t\ttitles: title,\n\t\t\t\t\tprop: 'info|imageinfo',\n\t\t\t\t\tinprop: 'protection',\n\t\t\t\t\tiiprop: 'url|mime|size',\n\t\t\t\t\tiiurlwidth: 150\n\t\t\t\t} ).then( checkUniqueProcessor ),\n\t\t\t\t// Checks for matches with different versions of the file extension on this wiki only\n\t\t\t\tthis.api.get( {\n\t\t\t\t\taction: 'query',\n\t\t\t\t\tgenerator: 'allpages',\n\t\t\t\t\tgapnamespace: NS_FILE,\n\t\t\t\t\tgapprefix: prefix,\n\t\t\t\t\tprop: 'info|imageinfo',\n\t\t\t\t\tinprop: 'protection',\n\t\t\t\t\tiiprop: 'url|mime|size',\n\t\t\t\t\tiiurlwidth: 150\n\t\t\t\t} ).then( checkUniqueProcessor )\n\t\t\t).then( ( exact, fuzzy ) => {\n\t\t\t\tlet result;\n\t\t\t\tif ( !exact.isUnique || exact.isProtected ) {\n\t\t\t\t\tresult = exact;\n\t\t\t\t} else if ( !fuzzy.isUnique || fuzzy.isProtected ) {\n\t\t\t\t\tresult = fuzzy;\n\t\t\t\t} else {\n\t\t\t\t\tresult = { isUnique: true };\n\t\t\t\t}\n\n\t\t\t\tthis.cachedResult[ title ] = result;\n\t\t\t\treturn result;\n\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * Clears the result cache\n\t\t */\n\t\tclearCache: function () {\n\t\t\tthis.cachedResult = {};\n\t\t}\n\n\t};\n\n}() );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.Escaper.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.FlickrChecker.js","messages":[{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"ui\" type.","line":4,"column":1,"nodeType":"Block","endLine":4,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"selectButton\" type.","line":5,"column":1,"nodeType":"Block","endLine":5,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'getPhotos' is undefined.","line":212,"column":1,"nodeType":"Block","endLine":212,"endColumn":1},{"ruleId":"no-shadow","severity":1,"message":"'data' is already declared in the upper scope on line 219 column 15.","line":226,"column":17,"nodeType":"Identifier","messageId":"noShadow","endLine":226,"endColumn":21},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'getCollection' is undefined.","line":246,"column":1,"nodeType":"Block","endLine":246,"endColumn":1},{"ruleId":"no-shadow","severity":1,"message":"'data' is already declared in the upper scope on line 293 column 15.","line":304,"column":45,"nodeType":"Identifier","messageId":"noShadow","endLine":304,"endColumn":49},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'getPhotos' is undefined.","line":314,"column":1,"nodeType":"Block","endLine":314,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'getPhotos' is undefined.","line":331,"column":1,"nodeType":"Block","endLine":331,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":375,"column":10,"nodeType":"CallExpression","endLine":506,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":468,"column":5,"nodeType":"CallExpression","endLine":477,"endColumn":8},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":519,"column":10,"nodeType":"CallExpression","endLine":594,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":581,"column":4,"nodeType":"CallExpression","endLine":586,"endColumn":7},{"ruleId":"es-x/no-symbol-prototype-description","severity":1,"message":"ES2019 'Symbol.prototype.description' property is forbidden.","line":582,"column":46,"nodeType":"MemberExpression","messageId":"forbidden","endLine":582,"endColumn":63},{"ruleId":"es-x/no-symbol-prototype-description","severity":1,"message":"ES2019 'Symbol.prototype.description' property is forbidden.","line":678,"column":4,"nodeType":"MemberExpression","messageId":"forbidden","endLine":678,"endColumn":22},{"ruleId":"es-x/no-symbol-prototype-description","severity":1,"message":"ES2019 'Symbol.prototype.description' property is forbidden.","line":696,"column":39,"nodeType":"MemberExpression","messageId":"forbidden","endLine":696,"endColumn":61}],"suppressedMessages":[{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":99,"column":3,"nodeType":"CallExpression","endLine":99,"endColumn":49,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":108,"column":4,"nodeType":"CallExpression","endLine":108,"endColumn":49,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'api_key' is not in camel case.","line":174,"column":4,"nodeType":"Identifier","messageId":"notCamelCase","endLine":174,"endColumn":11,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'user_id' is not in camel case.","line":202,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":202,"endColumn":12,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'group_id' is not in camel case.","line":228,"column":6,"nodeType":"Identifier","messageId":"notCamelCase","endLine":228,"endColumn":14,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'user_id' is not in camel case.","line":229,"column":6,"nodeType":"Identifier","messageId":"notCamelCase","endLine":229,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'group_id' is not in camel case.","line":235,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":235,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":267,"column":7,"nodeType":"CallExpression","endLine":267,"endColumn":49,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'photoset_id' is not in camel case.","line":270,"column":8,"nodeType":"Identifier","messageId":"notCamelCase","endLine":270,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'user_id' is not in camel case.","line":297,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":297,"endColumn":12,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'collection_id' is not in camel case.","line":301,"column":9,"nodeType":"Identifier","messageId":"notCamelCase","endLine":301,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":306,"column":5,"nodeType":"CallExpression","endLine":306,"endColumn":28,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'gallery_id' is not in camel case.","line":324,"column":4,"nodeType":"Identifier","messageId":"notCamelCase","endLine":324,"endColumn":14,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'photoset_id' is not in camel case.","line":338,"column":4,"nodeType":"Identifier","messageId":"notCamelCase","endLine":338,"endColumn":15,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'per_page' is not in camel case.","line":358,"column":4,"nodeType":"Identifier","messageId":"notCamelCase","endLine":358,"endColumn":12,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":445,"column":4,"nodeType":"CallExpression","endLine":445,"endColumn":40,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":463,"column":5,"nodeType":"CallExpression","endLine":463,"endColumn":51,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":465,"column":5,"nodeType":"CallExpression","endLine":465,"endColumn":35,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":467,"column":5,"nodeType":"CallExpression","endLine":467,"endColumn":51,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":490,"column":5,"nodeType":"CallExpression","endLine":490,"endColumn":51,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":493,"column":5,"nodeType":"CallExpression","endLine":493,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'skip_invisible' is not in camel case.","line":495,"column":6,"nodeType":"Identifier","messageId":"notCamelCase","endLine":495,"endColumn":20,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'photo_id' is not in camel case.","line":521,"column":4,"nodeType":"Identifier","messageId":"notCamelCase","endLine":521,"endColumn":12,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_content'.","line":542,"column":47,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":542,"endColumn":67,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_content'.","line":556,"column":18,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":556,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_content'.","line":582,"column":46,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":582,"endColumn":72,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'path_alias' is not in camel case.","line":608,"column":34,"nodeType":"Identifier","messageId":"notCamelCase","endLine":608,"endColumn":44,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'path_alias' is not in camel case.","line":609,"column":3,"nodeType":"Identifier","messageId":"notCamelCase","endLine":609,"endColumn":13,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'path_alias' is not in camel case.","line":611,"column":76,"nodeType":"Identifier","messageId":"notCamelCase","endLine":611,"endColumn":86,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'path_alias' is not in camel case.","line":611,"column":90,"nodeType":"Identifier","messageId":"notCamelCase","endLine":611,"endColumn":100,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-support","severity":1,"message":"$.support is not allowed","line":655,"column":3,"nodeType":"MemberExpression","endLine":655,"endColumn":12,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'photo_id' is not in camel case.","line":694,"column":4,"nodeType":"Identifier","messageId":"notCamelCase","endLine":694,"endColumn":12,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_content'.","line":696,"column":39,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":696,"endColumn":70,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'photo_id' is not in camel case.","line":713,"column":4,"nodeType":"Identifier","messageId":"notCamelCase","endLine":713,"endColumn":12,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":15,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/* eslint-disable camelcase, no-underscore-dangle */\n\n/**\n * @param ui\n * @param selectButton\n * @class\n */\nmw.FlickrChecker = function ( ui, selectButton ) {\n\tthis.ui = ui;\n\tthis.imageUploads = [];\n\tthis.apiUrl = mw.UploadWizard.config.flickrApiUrl;\n\tthis.apiKey = mw.UploadWizard.config.flickrApiKey;\n\tthis.selectButton = selectButton;\n};\n\n/**\n * Static list of all Flickr upload filenames.\n * Used to avoid name conflicts. Filenames are not removed when an upload is cancelled, so this can\n * contain fakes. Since we only use the list to choose an ugly but more unique file format on conflict,\n * and never refuse an upload based on it, that is not really a problem.\n *\n * @type {Object}\n */\nmw.FlickrChecker.fileNames = {};\n\n/**\n * Cache for Flickr blacklist lookups.\n * Resolves to a hash whose keys are the blacklisted Flickr NSIDs.\n * Use `FlickrChecker.getBlacklist()` instead of accessing this directly.\n *\n * @type {jQuery.Promise}\n */\nmw.FlickrChecker.blacklist = null;\n\n/**\n * Cache for Flickr license lookups.\n *\n * @type {jQuery.Promise}\n */\nmw.FlickrChecker.licensePromise = null;\n\n/**\n * Flickr licenses.\n */\nmw.FlickrChecker.licenseList = [];\n\n// Map each Flickr license name to the equivalent templates.\n// These are the current Flickr license names as of July 2, 2025.\n// Live list at http://api.flickr.com/services/rest/?&method=flickr.photos.licenses.getInfo&api_key=...\nmw.FlickrChecker.licenseMaps = {\n\t'All Rights Reserved': 'invalid',\n\t'CC BY 2.0': '{{cc-by-2.0}}{{flickrreview}}',\n\t'CC BY-ND 2.0': 'invalid',\n\t'CC BY-NC-ND 2.0': 'invalid',\n\t'CC BY-NC 2.0': 'invalid',\n\t'CC BY-NC-SA 2.0': 'invalid',\n\t'CC BY-SA 2.0': '{{cc-by-sa-2.0}}{{flickrreview}}',\n\t'No known copyright restrictions': '{{Flickr-no known copyright restrictions}}{{flickrreview}}',\n\t'United States Government Work': '{{PD-USGov}}{{flickrreview}}',\n\t'Public Domain Dedication (CC0)': '{{cc-zero}}{{flickrreview}}',\n\t'Public Domain Mark': '{{PD-US}}{{flickrreview}}',\n\t'CC BY 4.0': '{{cc-by-4.0}}{{flickrreview}}',\n\t'CC BY-ND 4.0': 'invalid',\n\t'CC BY-NC-ND 4.0': 'invalid',\n\t'CC BY-NC 4.0': 'invalid',\n\t'CC BY-NC-SA 4.0': 'invalid',\n\t'CC BY-SA 4.0': '{{cc-by-sa-4.0}}{{flickrreview}}',\n\t// the following are old flickr license names from 2011, preserved here just in case\n\t'Attribution License': '{{cc-by-2.0}}{{flickrreview}}',\n\t'Attribution-NoDerivs License': 'invalid',\n\t'Attribution-NonCommercial-NoDerivs License': 'invalid',\n\t'Attribution-NonCommercial License': 'invalid',\n\t'Attribution-NonCommercial-ShareAlike License': 'invalid',\n\t'Attribution-ShareAlike License': '{{cc-by-sa-2.0}}{{flickrreview}}'\n};\n\nmw.FlickrChecker.prototype = {\n\t/**\n\t * If a photo is from Flickr, retrieve its license. If the license is valid, display the license\n\t * to the user, hide the normal license selection interface, and set it as the deed for the upload.\n\t * If the license is not valid, alert the user with an error message. If no recognized license is\n\t * retrieved, do nothing. Note that the license look-up system is fragile on purpose. If Flickr\n\t * changes the name associated with a license ID, it's better for the lookup to fail than to use\n\t * an incorrect license.\n\t *\n\t * @param {string} flickrInputUrl The source URL to check\n\t */\n\tcheckFlickr: function ( flickrInputUrl ) {\n\t\tlet photoIdMatches = flickrInputUrl.match( /flickr\\.com\\/(?:x\\/t\\/[^/]+\\/)?photos\\/[^/]+\\/([0-9]+)/ );\n\t\tconst albumIdMatches = flickrInputUrl.match( /flickr\\.com\\/photos\\/[^/]+\\/(sets|albums)\\/([0-9]+)/ );\n\t\tconst userCollectionMatches = flickrInputUrl.match( /flickr\\.com\\/(?:x\\/t\\/[^/]+\\/)?photos\\/[^/]+\\/collections\\/?([0-9]+)?/ );\n\t\tconst userPhotostreamMatches = flickrInputUrl.match( /flickr\\.com\\/(?:x\\/t\\/[^/]+\\/)?photos\\/([^/]+)/ );\n\t\tconst groupPoolMatches = flickrInputUrl.match( /flickr\\.com\\/groups\\/[^/]+(?:\\/pool\\/([^/]+))?/ );\n\t\tconst userGalleryMatches = flickrInputUrl.match( /flickr\\.com\\/(?:x\\/t\\/[^/]+\\/)?photos\\/[^/]+\\/galleries\\/([0-9]+)/ );\n\t\tconst userFavoritesMatches = flickrInputUrl.match( /flickr\\.com\\/(?:x\\/t\\/[^/]+\\/)?photos\\/([^/]+)\\/favorites/ );\n\n\t\tthis.$spinner = $.createSpinner( { size: 'large', type: 'block' } );\n\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t$( '#mwe-upwiz-flickr-select-list-container' ).after( this.$spinner );\n\n\t\tif ( photoIdMatches === null ) {\n\t\t\t// try static urls\n\t\t\tphotoIdMatches = flickrInputUrl.match( /static\\.?flickr\\.com\\/[^/]+\\/([0-9]+)_/ );\n\t\t}\n\t\tif ( albumIdMatches || photoIdMatches || userCollectionMatches || userPhotostreamMatches ||\n\t\t\tgroupPoolMatches || userGalleryMatches || userFavoritesMatches ) {\n\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t$( '#mwe-upwiz-upload-add-flickr-container' ).hide();\n\t\t\tthis.imageUploads = [];\n\t\t\tif ( albumIdMatches && albumIdMatches[ 2 ] > 0 ) {\n\t\t\t\tthis.getPhotoset( albumIdMatches, flickrInputUrl );\n\t\t\t} else if ( photoIdMatches && photoIdMatches[ 1 ] > 0 ) {\n\t\t\t\tthis.getPhoto( photoIdMatches, flickrInputUrl );\n\t\t\t} else if ( userCollectionMatches ) {\n\t\t\t\tthis.getCollection( userCollectionMatches, flickrInputUrl );\n\t\t\t} else if ( userFavoritesMatches && userFavoritesMatches[ 1 ] ) {\n\t\t\t\tthis.getPhotostream( 'favorites', userPhotostreamMatches, flickrInputUrl );\n\t\t\t} else if ( userGalleryMatches && userGalleryMatches[ 1 ] ) {\n\t\t\t\tthis.getGallery( flickrInputUrl );\n\t\t\t} else if ( userPhotostreamMatches && userPhotostreamMatches[ 1 ] ) {\n\t\t\t\tthis.getPhotostream( 'stream', flickrInputUrl );\n\t\t\t} else if ( groupPoolMatches ) {\n\t\t\t\tthis.getGroupPool( groupPoolMatches, flickrInputUrl );\n\t\t\t}\n\t\t} else {\n\t\t\t// XXX show user the message that the URL entered was not valid\n\t\t\tmw.errorDialog( mw.message( 'mwe-upwiz-url-invalid', 'Flickr' ).escaped() );\n\t\t\tthis.$spinner.remove();\n\t\t\tthis.ui.flickrInterfaceReset();\n\t\t}\n\t},\n\n\t/**\n\t * Returns a suggested filename for the image.\n\t * Usually the filename is just the Flickr title plus an extension, but in case of name conflicts\n\t * or empty title a unique filename is generated.\n\t *\n\t * @param {string} title image title on Flickr\n\t * @param {number} id image id on Flickr\n\t * @param {string} ownername owner name on Flickr\n\t * @return {string}\n\t */\n\tgetFilenameFromItem: function ( title, id, ownername ) {\n\t\tlet fileName;\n\n\t\tif ( title === '' ) {\n\t\t\tfileName = ownername + ' - ' + id + '.jpg';\n\t\t} else if ( mw.FlickrChecker.fileNames[ title + '.jpg' ] ) {\n\t\t\tfileName = title + ' - ' + id + '.jpg';\n\t\t} else {\n\t\t\tfileName = title + '.jpg';\n\t\t}\n\n\t\treturn fileName;\n\t},\n\n\t/**\n\t * Reserves a filename; used by `mw.FlickrChecker.getFileNameFromItem()` which tries to\n\t * avoid returning a filename which is already reserved.\n\t * This works even when the filename was reserved in a different FlickrChecker instance.\n\t *\n\t * @param {string} fileName\n\t */\n\treserveFileName: function ( fileName ) {\n\t\tmw.FlickrChecker.fileNames[ fileName ] = true;\n\t},\n\n\t/**\n\t * @param {Object} params\n\t * @return {jQuery.Promise} a promise with the response data\n\t */\n\tflickrRequest: function ( params ) {\n\t\tparams = Object.assign( {\n\t\t\tapi_key: this.apiKey,\n\t\t\tformat: 'json',\n\t\t\tnojsoncallback: 1\n\t\t}, params );\n\t\treturn $.getJSON( this.apiUrl, params );\n\t},\n\n\t/*\n\t * Retrieves a list of photos in photostream and displays it.\n\t * @see {@link getPhotos}\n\t * @param {string} mode may be: 'favorites' - user's favorites are retrieved,\n\t * or 'stream' - user's photostream is retrieved\n\t * @param {string} url URL to get the user from.\n\t * @return {jQuery.Promise}\n\t */\n\tgetPhotostream: function ( mode, url ) {\n\t\treturn this.flickrRequest( {\n\t\t\tmethod: 'flickr.urls.lookupUser',\n\t\t\turl: url\n\t\t} ).then( ( data ) => {\n\t\t\tlet method;\n\t\t\tif ( mode === 'stream' ) {\n\t\t\t\tmethod = 'flickr.people.getPublicPhotos';\n\t\t\t} else if ( mode === 'favorites' ) {\n\t\t\t\tmethod = 'flickr.favorites.getPublicList';\n\t\t\t}\n\t\t\treturn this.getPhotos( 'photos', {\n\t\t\t\tmethod: method,\n\t\t\t\tuser_id: data.user.id\n\t\t\t} );\n\t\t} );\n\t},\n\n\t/**\n\t * Retrieves a list of photos in group pool and displays it.\n\t *\n\t * @param {Object} groupPoolMatches Groups in the input URL\n\t * @param {string} url The URL from which to get the group.\n\t * @see {@link getPhotos}\n\t * @return {jQuery.Promise}\n\t */\n\tgetGroupPool: function ( groupPoolMatches, url ) {\n\t\treturn this.flickrRequest( {\n\t\t\tmethod: 'flickr.urls.lookupGroup',\n\t\t\turl: url\n\t\t} ).then( ( data ) => {\n\t\t\tconst gid = data.group.id;\n\n\t\t\tif ( groupPoolMatches[ 1 ] ) { // URL contains a user ID\n\t\t\t\treturn this.flickrRequest( {\n\t\t\t\t\tmethod: 'flickr.urls.lookupUser',\n\t\t\t\t\turl: 'http://www.flickr.com/photos/' + groupPoolMatches[ 1 ]\n\t\t\t\t} ).then( ( data ) => this.getPhotos( 'photos', {\n\t\t\t\t\tmethod: 'flickr.groups.pools.getPhotos',\n\t\t\t\t\tgroup_id: gid,\n\t\t\t\t\tuser_id: data.user.id\n\t\t\t\t} ) );\n\t\t\t}\n\n\t\t\treturn this.getPhotos( 'photos', {\n\t\t\t\tmethod: 'flickr.groups.pools.getPhotos',\n\t\t\t\tgroup_id: gid\n\t\t\t} );\n\t\t} );\n\t},\n\n\t/**\n\t * Constructs an unordered list of sets in the collection.\n\t *\n\t * @param {boolean} appendId True if you want to append\n\t * id=\"mwe-upwiz-files-collection-chooser\"; false otherwise\n\t * @param {Object} data The retrieved data\n\t * @see {@link getCollection}\n\t * @return {jQuery}\n\t */\n\tbuildCollectionLinks: function ( appendId, data ) {\n\t\tconst $elem = $( '<ul>' );\n\t\tif ( appendId ) {\n\t\t\t$elem.attr( 'id', 'mwe-upwiz-files-collection-chooser' );\n\t\t}\n\t\tdata.collection.forEach( ( value ) => {\n\t\t\tconst $li = $( '<li>' );\n\t\t\t$li.append( value.title );\n\t\t\tif ( value.collection !== undefined ) {\n\t\t\t\t$li.append( this.buildCollectionLinks( false, value ) );\n\t\t\t}\n\t\t\tif ( value.set !== undefined ) {\n\t\t\t\tconst $ul = $( '<ul>' );\n\t\t\t\tvalue.set.forEach( ( value2 ) => {\n\t\t\t\t\tconst $link = $( '<a>' ).attr( { href: '#', role: 'button', 'data-id': value2.id } );\n\t\t\t\t\t$link.append( value2.title );\n\t\t\t\t\t$link.on( 'click', () => {\n\t\t\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t\t\t$( '#mwe-upwiz-files-collection-chooser' ).remove();\n\t\t\t\t\t\tthis.getPhotos( 'photoset', {\n\t\t\t\t\t\t\tmethod: 'flickr.photosets.getPhotos',\n\t\t\t\t\t\t\tphotoset_id: $link.data( 'id' )\n\t\t\t\t\t\t} );\n\t\t\t\t\t} );\n\t\t\t\t\t$ul.append( $( '<li>' ).append( $link ) );\n\t\t\t\t} );\n\t\t\t\t$li.append( $ul );\n\t\t\t}\n\t\t\t$elem.append( $li );\n\t\t} );\n\t\treturn $elem;\n\t},\n\n\t/**\n\t * Retrieves a list of sets in a collection and displays it.\n\t *\n\t * @param {Object} userCollectionMatches Result of this.url.match\n\t * @param {string} url URL with which to look up the user.\n\t * @return {jQuery.Promise}\n\t */\n\tgetCollection: function ( userCollectionMatches, url ) {\n\t\treturn this.flickrRequest( {\n\t\t\tmethod: 'flickr.urls.lookupUser',\n\t\t\turl: url\n\t\t} ).then( ( data ) => {\n\t\t\tconst req = {\n\t\t\t\tmethod: 'flickr.collections.getTree',\n\t\t\t\textras: 'license, url_sq, owner_name, original_format, date_taken, geo',\n\t\t\t\tuser_id: data.user.id\n\t\t\t};\n\n\t\t\tif ( userCollectionMatches[ 1 ] ) {\n\t\t\t\treq.collection_id = userCollectionMatches[ 1 ];\n\t\t\t}\n\n\t\t\treturn this.flickrRequest( req ).then( ( data ) => {\n\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t$( '#mwe-upwiz-files' ).append( this.buildCollectionLinks( true, data.collections ) );\n\t\t\t} );\n\t\t} );\n\t},\n\n\t/**\n\t * Retrieves a list of photos in gallery and displays it.\n\t *\n\t * @see {@link getPhotos}\n\t * @param {string} url URL with which to look up the gallery information.\n\t * @return {jQuery.Promise}\n\t */\n\tgetGallery: function ( url ) {\n\t\treturn this.flickrRequest( {\n\t\t\tmethod: 'flickr.urls.lookupGallery',\n\t\t\turl: url\n\t\t} ).then( ( data ) => this.getPhotos( 'photos', {\n\t\t\tmethod: 'flickr.galleries.getPhotos',\n\t\t\tgallery_id: data.gallery.id\n\t\t} ) );\n\t},\n\n\t/**\n\t * Retrieves a list of photos in photoset and displays it.\n\t *\n\t * @see {@link getPhotos}\n\t * @param {Object} albumIdMatches Result of this.url.match\n\t * @return {jQuery.Promise}\n\t */\n\tgetPhotoset: function ( albumIdMatches ) {\n\t\treturn this.getPhotos( 'photoset', {\n\t\t\tmethod: 'flickr.photosets.getPhotos',\n\t\t\tphotoset_id: albumIdMatches[ 2 ]\n\t\t} );\n\t},\n\n\t/**\n\t * Retrieves a list of photos and displays it.\n\t *\n\t * @param {string} mode may be: 'photoset' - for use with photosets,\n\t * or 'photos' - for use with everything else (the parameter is used\n\t * to determine how the properties in retrieved JSON are named)\n\t * @param {Object} options options to pass to the API call; especially API method\n\t * and some \"***_id\"s (photoset_id, etc.)\n\t * @return {jQuery.Promise}\n\t */\n\tgetPhotos: function ( mode, options ) {\n\t\tthis.selectButton.setLabel( mw.message( 'mwe-upwiz-select-flickr' ).text() );\n\t\tthis.selectButton.setDisabled( true );\n\n\t\tconst req = Object.assign( {}, options, {\n\t\t\textras: 'license, url_sq, owner_name, original_format, date_taken, geo, path_alias',\n\t\t\tper_page: '500'\n\t\t} );\n\n\t\tconst flickrPromise = this.flickrRequest( req ).then( ( data ) => {\n\t\t\tlet photoset;\n\t\t\tif ( mode === 'photoset' ) {\n\t\t\t\tphotoset = data.photoset;\n\t\t\t} else if ( mode === 'photos' ) {\n\t\t\t\tphotoset = data.photos;\n\t\t\t}\n\t\t\tif ( !photoset ) {\n\t\t\t\t$.Deferred().reject( mw.message( 'mwe-upwiz-url-invalid', 'Flickr' ).escaped() );\n\t\t\t}\n\t\t\treturn photoset;\n\t\t} );\n\n\t\t// would be better to use isBlacklisted(), but didn't find a nice way of combining it with $.each\n\t\treturn $.when( flickrPromise, this.getBlacklist() ).then( ( photoset, blacklist ) => {\n\t\t\tlet sourceURL;\n\t\t\tconst checkboxes = [];\n\t\t\tconst checkboxesWidget = new OO.ui.CheckboxMultiselectWidget();\n\t\t\tlet x = 0;\n\n\t\t\tthis.$spinner.remove();\n\n\t\t\tphotoset.photo.forEach( ( item, i ) => {\n\t\t\t\tconst license = this.checkLicense( item.license );\n\t\t\t\tconst licenseValue = license.licenseValue;\n\t\t\t\tif ( licenseValue === 'invalid' ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tlet ownerId;\n\t\t\t\tif ( mode === 'photoset' ) {\n\t\t\t\t\townerId = photoset.owner;\n\t\t\t\t\tsourceURL = 'https://www.flickr.com/photos/' + photoset.owner + '/' + item.id + '/';\n\t\t\t\t} else if ( mode === 'photos' ) {\n\t\t\t\t\townerId = item.owner;\n\t\t\t\t\tsourceURL = 'https://www.flickr.com/photos/' + item.owner + '/' + item.id + '/';\n\t\t\t\t}\n\n\t\t\t\tif ( ownerId in blacklist || item.pathalias in blacklist ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Limit to maximum of 500 valid images\n\t\t\t\t// (Flickr's API returns a maximum of 500 images anyway.)\n\t\t\t\tif ( x++ >= 500 ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\n\t\t\t\tconst fileName = this.getFilenameFromItem( item.title, item.id, item.ownername );\n\n\t\t\t\tconst flickrUpload = {\n\t\t\t\t\tname: fileName,\n\t\t\t\t\turl: '',\n\t\t\t\t\ttype: 'JPEG',\n\t\t\t\t\tfromURL: true,\n\t\t\t\t\tsource: 'flickr',\n\t\t\t\t\tlicenseValue: licenseValue,\n\t\t\t\t\tlicenseMessage: license.licenseMessage,\n\t\t\t\t\tlicenseName: license.licenseName,\n\t\t\t\t\tphotoId: item.id,\n\t\t\t\t\tlocation: {\n\t\t\t\t\t\tlatitude: item.latitude,\n\t\t\t\t\t\tlongitude: item.longitude\n\t\t\t\t\t},\n\t\t\t\t\tauthor: item.ownername,\n\t\t\t\t\tdate: item.datetaken,\n\t\t\t\t\toriginalFormat: item.originalformat,\n\t\t\t\t\tsourceURL: sourceURL,\n\t\t\t\t\tindex: i\n\t\t\t\t};\n\t\t\t\t// Adding all the Photoset files which have a valid license with the required info to an array so that they can be referenced later\n\t\t\t\tthis.imageUploads[ i ] = flickrUpload;\n\t\t\t\tthis.reserveFileName( fileName );\n\n\t\t\t\t// setting up the thumbnail previews in the Selection list\n\t\t\t\tif ( item.url_sq ) {\n\t\t\t\t\tcheckboxes.push( new OO.ui.CheckboxMultioptionWidget( {\n\t\t\t\t\t\tdata: i,\n\t\t\t\t\t\tlabel: $( '<img class=\"lazy-thumbnail\" data-original=\"' + item.url_sq + '\">' )\n\t\t\t\t\t} ) );\n\t\t\t\t}\n\t\t\t} );\n\t\t\tcheckboxesWidget.addItems( checkboxes );\n\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t$( '#mwe-upwiz-flickr-select-list' ).append( checkboxesWidget.$element );\n\t\t\t// Set up checkboxes\n\t\t\tcheckboxesWidget.on( 'select', () => {\n\t\t\t\tconst selectedCount = checkboxesWidget.findSelectedItems().length;\n\t\t\t\t// If at least one item is selected, activate the upload button\n\t\t\t\tthis.selectButton.setDisabled( selectedCount === 0 );\n\t\t\t\t// Limit the number of selectable images\n\t\t\t\tcheckboxesWidget.getItems().forEach( ( checkbox ) => {\n\t\t\t\t\tif ( !checkbox.isSelected() ) {\n\t\t\t\t\t\tcheckbox.setDisabled( selectedCount >= mw.UploadWizard.config.maxFlickrUploads );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t} );\n\t\t\t// Set up action for 'Upload selected images' button\n\t\t\tthis.selectButton.on( 'click', () => {\n\t\t\t\tconst uploads = [];\n\t\t\t\tthis.$spinner = $.createSpinner( { size: 'large', type: 'block' } );\n\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t$( '#mwe-upwiz-flickr-select-list-container' ).hide();\n\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t$( '#mwe-upwiz-upload-ctrls' ).show();\n\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t$( '#mwe-upwiz-flickr-select-list-container' ).after( this.$spinner );\n\t\t\t\t$.when.apply( $, checkboxesWidget.findSelectedItemsData().map( ( image ) => {\n\t\t\t\t\tuploads.push( this.imageUploads[ image ] );\n\t\t\t\t\t// For each image, load the description and URL to upload from\n\t\t\t\t\treturn $.when(\n\t\t\t\t\t\tthis.setUploadDescription( this.imageUploads[ image ] ),\n\t\t\t\t\t\tthis.setImageURL( image )\n\t\t\t\t\t);\n\t\t\t\t} ) ).done( () => {\n\t\t\t\t\tthis.ui.emit( 'files-added', uploads );\n\t\t\t\t} ).always( () => {\n\t\t\t\t\t// We'll only bind this once, since that selectButton could be\n\t\t\t\t\t// reused later, with a different flickr set (it is not destroyed)\n\t\t\t\t\tthis.selectButton.off( 'click' );\n\t\t\t\t\tthis.$spinner.remove();\n\t\t\t\t\tthis.ui.flickrInterfaceDestroy();\n\t\t\t\t} );\n\t\t\t} );\n\n\t\t\tif ( this.imageUploads.length === 0 ) {\n\t\t\t\treturn $.Deferred().reject( mw.message( 'mwe-upwiz-license-photoset-invalid' ).escaped() );\n\t\t\t} else {\n\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t$( '#mwe-upwiz-flickr-select-list-container' ).show();\n\t\t\t\t// Lazy-load images\n\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t$( 'img.lazy-thumbnail' ).lazyload( {\n\t\t\t\t\t// jQuery considers all images without 'src' to not be ':visible'\n\t\t\t\t\tskip_invisible: false\n\t\t\t\t} );\n\t\t\t\t// Trigger initial update (HACK)\n\t\t\t\tsetTimeout( () => {\n\t\t\t\t\t$( window ).triggerHandler( 'resize' );\n\t\t\t\t} );\n\t\t\t}\n\t\t} ).fail( ( message ) => {\n\t\t\tmw.errorDialog( message, mw.msg( 'mwe-upwiz-license-photoset-invalid-title' ) );\n\t\t\tthis.$spinner.remove();\n\t\t\tthis.ui.flickrInterfaceReset();\n\t\t} );\n\t},\n\n\t/**\n\t * Get a single photo from Flickr.\n\t *\n\t * @param {Object} photoIdMatches Result of matching input URL against a regex\n\t * for photo IDs.\n\t * @return {jQuery.Promise}\n\t */\n\tgetPhoto: function ( photoIdMatches ) {\n\t\tconst photoId = photoIdMatches[ 1 ];\n\n\t\treturn this.flickrRequest( {\n\t\t\tmethod: 'flickr.photos.getInfo',\n\t\t\tphoto_id: photoId\n\t\t} ).then( ( data ) => {\n\t\t\tif ( !data.photo ) {\n\t\t\t\treturn $.Deferred().reject( mw.message( 'mwe-upwiz-url-invalid', 'Flickr' ).escaped() );\n\t\t\t}\n\t\t\treturn data.photo;\n\t\t} ).then( ( photo ) => {\n\t\t\tconst isBlacklistedPromise = this.isBlacklisted( photo.owner.nsid, photo.owner.path_alias );\n\t\t\treturn isBlacklistedPromise.then( ( isBlacklisted ) => {\n\t\t\t\tif ( isBlacklisted ) {\n\t\t\t\t\treturn $.Deferred().reject( mw.message( 'mwe-upwiz-user-blacklisted', 'Flickr' ).escaped() );\n\t\t\t\t} else {\n\t\t\t\t\treturn photo;\n\t\t\t\t}\n\t\t\t} );\n\t\t} ).then( ( photo ) => {\n\t\t\tconst license = this.checkLicense( photo.license );\n\t\t\tif ( license.licenseValue === 'invalid' ) {\n\t\t\t\treturn $.Deferred().reject( license.licenseMessage );\n\t\t\t}\n\n\t\t\tconst fileName = this.getFilenameFromItem( photo.title._content, photo.id,\n\t\t\t\tphoto.owner.username );\n\n\t\t\t// if owner doesn't have a real name, use username\n\t\t\tlet photoAuthor;\n\t\t\tif ( photo.owner.realname !== '' ) {\n\t\t\t\tphotoAuthor = photo.owner.realname;\n\t\t\t} else {\n\t\t\t\tphotoAuthor = photo.owner.username;\n\t\t\t}\n\t\t\t// get the URL of the photo page\n\t\t\tlet sourceURL;\n\t\t\tphoto.urls.url.forEach( ( url ) => {\n\t\t\t\tif ( url.type === 'photopage' ) {\n\t\t\t\t\tsourceURL = url._content;\n\t\t\t\t\t// break each loop\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t} );\n\t\t\tconst flickrUpload = {\n\t\t\t\tname: fileName,\n\t\t\t\turl: '',\n\t\t\t\ttype: 'JPEG',\n\t\t\t\tfromURL: true,\n\t\t\t\tsource: 'flickr',\n\t\t\t\tlicenseValue: license.licenseValue,\n\t\t\t\tlicenseMessage: license.licenseMessage,\n\t\t\t\tlicenseName: license.licenseName,\n\t\t\t\tauthor: photoAuthor,\n\t\t\t\toriginalFormat: photo.originalformat,\n\t\t\t\tdate: photo.dates.taken,\n\t\t\t\tlocation: photo.location,\n\t\t\t\tphotoId: photo.id,\n\t\t\t\tsourceURL: sourceURL\n\t\t\t};\n\n\t\t\tthis.imageUploads.push( flickrUpload );\n\t\t\tthis.reserveFileName( fileName );\n\n\t\t\t$.when(\n\t\t\t\tthis.setUploadDescription( flickrUpload, photo.description._content ),\n\t\t\t\tthis.setImageURL( 0 )\n\t\t\t).done( () => {\n\t\t\t\tthis.ui.emit( 'files-added', [ flickrUpload ] );\n\t\t\t} ).always( () => {\n\t\t\t\tthis.$spinner.remove();\n\t\t\t\tthis.ui.flickrInterfaceDestroy();\n\t\t\t} );\n\t\t} ).fail( ( message ) => {\n\t\t\tmw.errorDialog( message, mw.msg( 'mwe-upwiz-license-external-invalid-title' ) );\n\t\t\tthis.$spinner.remove();\n\t\t\tthis.ui.flickrInterfaceReset();\n\t\t} );\n\t},\n\n\t/**\n\t * Checks a user against the blacklist. Both the NSID and the path_alias (if it exists) MUST be\n\t * supplied, as the blacklist will probably only contain one of them. (Users don't have a\n\t * path_alias in the beginning, and must set it manually; if it does not exist, it can be left\n\t * undefined, or an empty string can be supplied (which is what the Flickr API usually returns\n\t * as the path_alias for such users).\n\t *\n\t * @param {string} nsid Flickr NSID of the author\n\t * @param {string} [path_alias] Flickr username of the author (the unchangeable one, in the URL)\n\t * @return {jQuery.Promise} a promise which resolves to a boolean - true if the user is blacklisted\n\t */\n\tisBlacklisted: function ( nsid, path_alias ) {\n\t\tpath_alias = String( path_alias );\n\t\t// the blacklist should never contain the empty string, but better safe then sorry\n\t\treturn this.getBlacklist().then( ( blacklist ) => ( nsid in blacklist || path_alias && path_alias in blacklist ) );\n\t},\n\n\t/**\n\t * Returns a promise for the Flickr user blacklist.\n\t * The promise resolves to a hash with the blacklisted NSIDs/path_alias-es as its keys.\n\t * (path_alias is the username that appears in the URL.)\n\t * The blacklist will usually contain the path_alias or the NSID of the user, but not both;\n\t * it is the caller's responsibility to check against both of them.\n\t *\n\t * @return {jQuery.Promise}\n\t */\n\tgetBlacklist: function () {\n\t\tconst api = new mw.Api();\n\t\tif ( !mw.FlickrChecker.blacklist ) {\n\t\t\tmw.FlickrChecker.blacklist = api.get( {\n\t\t\t\taction: 'flickrblacklist',\n\t\t\t\tlist: 1,\n\t\t\t\tformat: 'json'\n\t\t\t} ).then( ( data ) => {\n\t\t\t\tconst blacklist = {};\n\t\t\t\tif ( data.flickrblacklist && data.flickrblacklist.list ) {\n\t\t\t\t\tdata.flickrblacklist.list.forEach( ( username ) => {\n\t\t\t\t\t\tblacklist[ username ] = true;\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t\treturn blacklist;\n\t\t\t} );\n\t\t}\n\t\treturn mw.FlickrChecker.blacklist;\n\t},\n\n\t/**\n\t * Retrieve the list of all current Flickr licenses and store it in an array (`mw.FlickrChecker.licenseList`)\n\t *\n\t * @return {jQuery.Promise}\n\t */\n\tgetLicenses: function () {\n\t\tif ( mw.FlickrChecker.licensePromise ) {\n\t\t\treturn mw.FlickrChecker.licensePromise;\n\t\t}\n\n\t\t// Workaround for http://bugs.jquery.com/ticket/8283\n\t\t// eslint-disable-next-line no-jquery/no-support\n\t\t$.support.cors = true;\n\t\tmw.FlickrChecker.licensePromise = this.flickrRequest( {\n\t\t\tmethod: 'flickr.photos.licenses.getInfo'\n\t\t} ).then( ( data ) => {\n\t\t\tif ( typeof data.licenses !== 'undefined' ) {\n\t\t\t\tdata.licenses.license.forEach( ( value ) => {\n\t\t\t\t\tmw.FlickrChecker.licenseList[ value.id ] = value.name;\n\t\t\t\t} );\n\t\t\t}\n\t\t} );\n\n\t\treturn mw.FlickrChecker.licensePromise;\n\t},\n\n\t/**\n\t * @param {Object} upload\n\t * @param {string} description\n\t * @return {jQuery.Promise}\n\t */\n\tsetUploadDescription: function ( upload, description ) {\n\t\tif ( description !== undefined ) {\n\t\t\t// If a Flickr description has a | character in it, it will\n\t\t\t// mess up the MediaWiki description. Escape them.\n\t\t\tupload.description = description.replace( /\\|/g, '|' );\n\t\t\treturn $.Deferred().resolve();\n\t\t} else {\n\t\t\treturn this.setImageDescription( upload );\n\t\t}\n\t},\n\n\t/**\n\t * @param {Object} upload\n\t * @return {jQuery.Promise}\n\t */\n\tsetImageDescription: function ( upload ) {\n\t\tconst photoId = upload.photoId;\n\n\t\treturn this.flickrRequest( {\n\t\t\tmethod: 'flickr.photos.getInfo',\n\t\t\tphoto_id: photoId\n\t\t} ).then( ( data ) => {\n\t\t\tthis.setUploadDescription( upload, data.photo.description._content );\n\t\t} );\n\t},\n\n\t/**\n\t * Retrieve the URL of the largest version available on Flickr and set that\n\t * as the upload URL.\n\t *\n\t * @param {number} index Index of the image for which we need to set the URL\n\t * @return {jQuery.Promise}\n\t */\n\tsetImageURL: function ( index ) {\n\t\tconst upload = this.imageUploads[ index ];\n\t\tconst photoId = upload.photoId;\n\n\t\treturn this.flickrRequest( {\n\t\t\tmethod: 'flickr.photos.getSizes',\n\t\t\tphoto_id: photoId\n\t\t} ).then( ( data ) => {\n\t\t\tlet nameParts;\n\n\t\t\tif (\n\t\t\t\ttypeof data.sizes !== 'undefined' &&\n\t\t\t\ttypeof data.sizes.size !== 'undefined' &&\n\t\t\t\tdata.sizes.size.length > 0\n\t\t\t) {\n\t\t\t\t// Flickr always returns the largest version as the final size.\n\t\t\t\t// TODO: Make this less fragile by actually comparing sizes.\n\t\t\t\tconst largestSize = data.sizes.size.pop();\n\t\t\t\t// Flickr provides the original format for images coming from pro users, hence we need to change the default JPEG to this format\n\t\t\t\tif ( largestSize.label === 'Original' ) {\n\t\t\t\t\tupload.type = upload.originalFormat;\n\n\t\t\t\t\tnameParts = upload.name.split( '.' );\n\t\t\t\t\tif ( nameParts.length > 1 ) {\n\t\t\t\t\t\tnameParts.pop();\n\t\t\t\t\t}\n\t\t\t\t\tupload.name = nameParts.join( '.' ) + '.' + upload.originalFormat;\n\t\t\t\t}\n\t\t\t\tupload.url = largestSize.source;\n\t\t\t} else {\n\t\t\t\tmw.errorDialog( mw.message( 'mwe-upwiz-error-no-image-retrieved', 'Flickr' ).escaped() );\n\t\t\t\tthis.$spinner.remove();\n\t\t\t\tthis.ui.flickrInterfaceReset();\n\t\t\t\treturn $.Deferred().reject();\n\t\t\t}\n\t\t} );\n\t},\n\n\tcheckLicense: function ( licenseId ) {\n\t\t// The returned data.photo.license is just an ID that we use to look up the license name\n\t\tconst licenseName = mw.FlickrChecker.licenseList[ licenseId ];\n\t\t// Use the license name to retrieve the template values\n\t\tconst licenseValue = mw.FlickrChecker.licenseMaps[ licenseName ];\n\n\t\t// Set the license message to show the user.\n\t\tlet licenseMessage;\n\t\tif ( licenseValue === 'invalid' ) {\n\t\t\tlicenseMessage = mw.msg( 'mwe-upwiz-license-external-invalid', 'Flickr', licenseName );\n\t\t} else {\n\t\t\tlicenseMessage = mw.msg( 'mwe-upwiz-license-external', 'Flickr', licenseName );\n\t\t}\n\n\t\treturn {\n\t\t\tlicenseName: licenseName,\n\t\t\tlicenseMessage: licenseMessage,\n\t\t\tlicenseValue: licenseValue\n\t\t};\n\t}\n};\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.GroupProgressBar.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeIn","line":51,"column":4,"nodeType":"CallExpression","endLine":51,"endColumn":70,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeOut","line":121,"column":4,"nodeType":"CallExpression","endLine":121,"endColumn":71,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.QuickTitleChecker.js","messages":[],"suppressedMessages":[{"ruleId":"no-control-regex","severity":2,"message":"Unexpected control character(s) in regular expression: \\x00, \\x1f.","line":30,"column":4,"nodeType":"Literal","messageId":"unexpected","endLine":30,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.UploadWizard.js","messages":[{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"uw\" type.","line":4,"column":1,"nodeType":"Block","endLine":4,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"config\" type.","line":9,"column":1,"nodeType":"Block","endLine":9,"endColumn":1},{"ruleId":"no-shadow","severity":1,"message":"'steps' is already declared in the upper scope on line 86 column 10.","line":112,"column":16,"nodeType":"Identifier","messageId":"noShadow","endLine":112,"endColumn":21}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Object that represents the entire multi-step Upload Wizard\n *\n * @param uw\n */\n( function ( uw ) {\n\n\t/**\n\t * @param config\n\t * @class\n\t */\n\tmw.UploadWizard = function ( config ) {\n\t\tthis.api = this.getApi( { ajax: { timeout: 0 } } );\n\n\t\t// making a sort of global for now, should be done by passing in config or fragments of config\n\t\t// when needed elsewhere\n\t\tmw.UploadWizard.config = config;\n\t\t// Shortcut for local references\n\t\tthis.config = config;\n\n\t\tthis.steps = {};\n\n\t\tconst maxSimPref = mw.user.options.get( 'upwiz_maxsimultaneous' );\n\n\t\tif ( maxSimPref !== 'default' ) {\n\t\t\tconfig.maxSimultaneousConnections = Math.max( 1, maxSimPref );\n\t\t}\n\n\t\tthis.maxSimultaneousConnections = config.maxSimultaneousConnections;\n\n\t\tif ( mw.loader.getState( 'ext.uls.mediawiki' ) !== null ) {\n\t\t\tmw.loader.load( 'ext.uls.mediawiki' );\n\t\t}\n\t};\n\n\tmw.UploadWizard.DEBUG = true;\n\n\tmw.UploadWizard.userAgent = 'UploadWizard';\n\n\tmw.UploadWizard.prototype = {\n\t\t/**\n\t\t * Create the basic interface to make an upload in this div\n\t\t *\n\t\t * @param {string} selector\n\t\t */\n\t\tcreateInterface: function ( selector ) {\n\t\t\tthis.ui = new uw.ui.Wizard( selector );\n\n\t\t\tconst promise = this.initialiseSteps();\n\n\t\t\tif (\n\t\t\t\tthis.config.wikibase.enabled &&\n\t\t\t\t// .depicts is for backward compatibility\n\t\t\t\t( this.config.wikibase.statements || this.config.wikibase.depicts )\n\t\t\t) {\n\t\t\t\t// mediainfo has a couple of widgets that we'll be using, but they're not\n\t\t\t\t// necessarily a hard dependency for UploadWizard\n\t\t\t\t// let's just attempt to load them - if not available we'll just do without\n\t\t\t\tpromise.then( () => {\n\t\t\t\t\t// disable wikibase until its components are loaded - this is just a safeguard\n\t\t\t\t\t// against the 'details' page being loaded with captions/depicts before\n\t\t\t\t\t// the wikibase components have loaded\n\t\t\t\t\tthis.config.wikibase.enabled = false;\n\t\t\t\t\treturn mw.loader.using( [\n\t\t\t\t\t\t'wikibase.mediainfo.statements',\n\t\t\t\t\t\t'wikibase.datamodel',\n\t\t\t\t\t\t'wikibase.mediainfo.base'\n\t\t\t\t\t] ).then( () => {\n\t\t\t\t\t\tthis.config.wikibase.enabled = true;\n\t\t\t\t\t} );\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\tpromise.then( ( steps ) => {\n\t\t\t\t// \"select\" the first step - highlight, make it visible, hide all others\n\t\t\t\tsteps.tutorial.load( [] );\n\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * Initialise the steps in the wizard\n\t\t *\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tinitialiseSteps: function () {\n\t\t\tconst steps = {};\n\n\t\t\tsteps.tutorial = new uw.controller.Tutorial( this.api, this.config );\n\t\t\tsteps.file = new uw.controller.Upload( this.api, this.config );\n\t\t\tsteps.deeds = new uw.controller.Deed( this.api, this.config );\n\t\t\tsteps.details = new uw.controller.Details( this.api, this.config );\n\t\t\tsteps.thanks = new uw.controller.Thanks( this.api, Object.assign(\n\t\t\t\t{ showInBreadcrumb: false },\n\t\t\t\tthis.config\n\t\t\t) );\n\n\t\t\tsteps.tutorial.setNextStep( steps.file );\n\n\t\t\tsteps.file.setPreviousStep( steps.tutorial );\n\t\t\tsteps.file.setNextStep( steps.deeds );\n\n\t\t\tsteps.deeds.setPreviousStep( steps.file );\n\t\t\tsteps.deeds.setNextStep( steps.details );\n\n\t\t\tsteps.details.setPreviousStep( steps.deeds );\n\t\t\tsteps.details.setNextStep( steps.thanks );\n\n\t\t\t// thanks doesn't need a \"previous\" step, there's no undoing uploads!\n\t\t\tsteps.thanks.setNextStep( steps.file );\n\n\t\t\treturn $.Deferred().resolve( steps ).promise()\n\t\t\t\t.always( ( steps ) => {\n\t\t\t\t\tthis.steps = steps;\n\t\t\t\t\tthis.ui.initialiseSteps( steps );\n\t\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * mw.Api's ajax calls are not very consistent in their error handling.\n\t\t * As long as the response comes back, the response will be fine: it'll\n\t\t * get rejected with the error details there. However, if no response\n\t\t * comes back for whatever reason, things can get confusing.\n\t\t * I'll monkeypatch around such cases so that we can always rely on the\n\t\t * error response the way we want it to be.\n\t\t *\n\t\t * TODO: Instead of this monkeypatching, we could call api.getErrorMessage()\n\t\t * in the error handlers to get nice messages.\n\t\t *\n\t\t * @param {Object} options\n\t\t * @return {mw.Api}\n\t\t */\n\t\tgetApi: function ( options ) {\n\t\t\tconst api = new mw.Api( options );\n\n\t\t\tapi.ajax = function ( parameters, ajaxOptions ) {\n\t\t\t\tObject.assign( parameters, {\n\t\t\t\t\terrorformat: 'html',\n\t\t\t\t\terrorlang: mw.config.get( 'wgUserLanguage' ),\n\t\t\t\t\terrorsuselocal: 1,\n\t\t\t\t\tformatversion: 2\n\t\t\t\t} );\n\n\t\t\t\tconst original = mw.Api.prototype.ajax.apply( this, [ parameters, ajaxOptions ] );\n\n\t\t\t\t// we'll attach a default error handler that makes sure error\n\t\t\t\t// output is always, reliably, in the same format\n\t\t\t\tconst override = original.then(\n\t\t\t\t\tnull, // done handler - doesn't need overriding\n\t\t\t\t\t( code, result ) => { // fail handler\n\t\t\t\t\t\tlet response = { errors: [ {\n\t\t\t\t\t\t\tcode: code,\n\t\t\t\t\t\t\thtml: result.textStatus || mw.message( 'api-clientside-error-invalidresponse' ).parse()\n\t\t\t\t\t\t} ] };\n\n\t\t\t\t\t\tif ( result.errors && result.errors[ 0 ] ) {\n\t\t\t\t\t\t\t// in case of success-but-has-errors, we have a valid result\n\t\t\t\t\t\t\tresponse = result;\n\t\t\t\t\t\t} else if ( result && result.textStatus === 'timeout' ) {\n\t\t\t\t\t\t\t// in case of $.ajax.fail(), there is no response json\n\t\t\t\t\t\t\tresponse.errors[ 0 ].html = mw.message( 'api-clientside-error-timeout' ).parse();\n\t\t\t\t\t\t} else if ( result && result.textStatus === 'parsererror' ) {\n\t\t\t\t\t\t\tresponse.errors[ 0 ].html = mw.message( 'api-error-parsererror' ).parse();\n\t\t\t\t\t\t} else if ( code === 'http' && result && result.xhr && result.xhr.status === 0 ) {\n\t\t\t\t\t\t\t// failed to even connect to server\n\t\t\t\t\t\t\tresponse.errors[ 0 ].html = mw.message( 'api-clientside-error-noconnect' ).parse();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\treturn $.Deferred().reject( code, response, response );\n\t\t\t\t\t}\n\t\t\t\t);\n\n\t\t\t\t/*\n\t\t\t\t * After attaching (.then) our error handler, a new promise is\n\t\t\t\t * returned. The original promise had an 'abort' method, which\n\t\t\t\t * we'll also want to make use of...\n\t\t\t\t */\n\t\t\t\treturn override.promise( { abort: original.abort } );\n\t\t\t};\n\n\t\t\treturn api;\n\t\t}\n\t};\n\n\t/**\n\t * Helper method to put a thumbnail somewhere.\n\t *\n\t * @param {string|jQuery} selector String representing a jQuery selector, or a jQuery object\n\t * @param {HTMLCanvasElement|HTMLImageElement|null} image\n\t */\n\tmw.UploadWizard.placeThumbnail = function ( selector, image ) {\n\t\tif ( image === null ) {\n\t\t\t$( selector ).addClass( 'mwe-upwiz-file-preview-broken' );\n\t\t\treturn;\n\t\t}\n\n\t\t$( selector )\n\t\t\t.css( { background: 'none' } )\n\t\t\t.prepend(\n\t\t\t\t$( '<a>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-thumbnail-link' )\n\t\t\t\t\t.append( image )\n\t\t\t);\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.UploadWizardDeedChooser.js","messages":[{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":32,"column":42,"nodeType":"ObjectExpression","endLine":32,"endColumn":89},{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":40,"column":17,"nodeType":"CallExpression","endLine":40,"endColumn":86},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":44,"column":21,"nodeType":"CallExpression","endLine":47,"endColumn":5}],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":2,"message":"'thorough' is defined but never used.","line":94,"column":61,"nodeType":"Identifier","messageId":"unusedVar","endLine":94,"endColumn":69,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Selector extensions are not allowed","line":131,"column":3,"nodeType":"CallExpression","endLine":131,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Selector extensions are not allowed","line":157,"column":3,"nodeType":"CallExpression","endLine":157,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function () {\n\n\t/**\n\t * Interface widget to choose among various deeds -- for instance, if own work, or not own work, or other such cases.\n\t *\n\t * @class\n\t * @mixes OO.EventEmitter\n\t * @param {Object} config The UW config\n\t * @param {Object} deeds Keyed object of UploadWizardDeed items\n\t * @param {mw.UploadWizardUpload[]} uploads Uploads that this applies to (this is just to make deleting and plurals work)\n\t */\n\tmw.UploadWizardDeedChooser = function ( config, deeds, uploads ) {\n\t\tOO.EventEmitter.call( this );\n\n\t\tthis.uploads = uploads;\n\t\tthis.deeds = deeds;\n\n\t\t// Name for radio button set\n\t\tmw.UploadWizardDeedChooser.prototype.widgetCount++;\n\t\tthis.name = 'deedChooser' + mw.UploadWizardDeedChooser.prototype.widgetCount.toString();\n\n\t\tthis.onLayoutReady = function () {};\n\n\t\tconst $radioContainer = $( '<div>' ).addClass( 'mwe-upwiz-deed-radios' );\n\t\tconst $formContainer = $( '<div>' ).addClass( 'mwe-upwiz-deed-forms' );\n\t\tthis.$element = $( '<div>' ).addClass( 'mwe-upwiz-deeds-container' ).append(\n\t\t\t$radioContainer, $formContainer\n\t\t);\n\n\t\tObject.keys( this.deeds ).forEach( ( name ) => {\n\t\t\tconst deed = this.deeds[ name ],\n\t\t\t\tradio = new OO.ui.RadioSelectWidget( { classes: [ 'mwe-upwiz-deed-radio-' + name ] } ),\n\t\t\t\toption = new OO.ui.RadioOptionWidget(),\n\t\t\t\t// Separate the radio option from its form\n\t\t\t\t$deedRadio = $( '<div>' ).addClass( 'mwe-upwiz-deed-option-title' ).append(\n\t\t\t\t\t$( '<span>' ).addClass( 'mwe-upwiz-deed-header' ).append(\n\t\t\t\t\t\tradio.$element\n\t\t\t\t\t)\n\t\t\t\t),\n\t\t\t\t$deedForm = $( '<div>' ).addClass( 'mwe-upwiz-deed mwe-upwiz-deed-' + deed.name ).append(\n\t\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-deed-form' )\n\t\t\t\t).hide();\n\n\t\t\toption.setLabel( mw.message(\n\t\t\t\t'mwe-upwiz-source-' + deed.name + '-label',\n\t\t\t\tthis.uploads.length\n\t\t\t).text() );\n\n\t\t\tradio.addItems( [ option ] );\n\n\t\t\t// Set the name attribute manually. We can't use RadioInputWidget which has\n\t\t\t// a name config because they don't emit change events. Ideally we would use\n\t\t\t// one RadioSelectWidget and not have to set this property.\n\t\t\tradio.items[ 0 ].radio.$input.attr( 'name', this.name );\n\n\t\t\t// Append intermediate containers\n\t\t\t$radioContainer.append( $deedRadio );\n\t\t\t$formContainer.append( $deedForm );\n\n\t\t\tdeed.setFormFields( $deedForm.find( '.mwe-upwiz-deed-form' ) );\n\n\t\t\tif ( Object.keys( this.deeds ).length === 1 ) {\n\t\t\t\tthis.onLayoutReady = this.selectDeed.bind( this, deed );\n\t\t\t} else {\n\t\t\t\tif ( config.licensing.defaultType === deed.name ) {\n\t\t\t\t\tthis.onLayoutReady = this.selectDeed.bind( this, deed );\n\t\t\t\t}\n\t\t\t\tradio.on( 'choose', () => {\n\t\t\t\t\tthis.selectDeed( deed );\n\t\t\t\t} );\n\t\t\t}\n\t\t} );\n\n\t\t// Deselect all deeds\n\t\tObject.keys( this.deeds ).forEach( ( name ) => {\n\t\t\tthis.deselectDeedInterface( name );\n\t\t} );\n\t};\n\tOO.mixinClass( mw.UploadWizardDeedChooser, OO.EventEmitter );\n\tOO.mixinClass( mw.UploadWizardDeedChooser, mw.uploadWizard.ValidatableElement );\n\n\t/**\n\t * How many deed choosers there are (important for creating unique ids, element names)\n\t */\n\tmw.UploadWizardDeedChooser.prototype.widgetCount = 0;\n\n\t/**\n\t * Check if this form is filled out correctly.\n\t *\n\t * @param {boolean} thorough\n\t * @return {jQuery.Promise<mw.uploadWizard.ValidationStatus>}\n\t */\n\t// eslint-disable-next-line no-unused-vars\n\tmw.UploadWizardDeedChooser.prototype.validate = function ( thorough ) {\n\t\tconst status = new mw.uploadWizard.ValidationStatus();\n\t\tif ( !this.deed ) {\n\t\t\tstatus.addError( mw.message( 'mwe-upwiz-deeds-require-selection' ) );\n\t\t}\n\t\treturn status.getErrors().length === 0 ? status.resolve() : status.reject();\n\t};\n\n\t/**\n\t * Uploads this deed controls\n\t */\n\tmw.UploadWizardDeedChooser.prototype.uploads = [];\n\n\tmw.UploadWizardDeedChooser.prototype.selectDeed = function ( deed ) {\n\t\tthis.choose( deed );\n\t\tthis.selectDeedInterface( deed.name );\n\t};\n\n\tmw.UploadWizardDeedChooser.prototype.choose = function ( deed ) {\n\t\tthis.deed = deed;\n\t\tthis.emit( 'choose' );\n\t};\n\n\t/**\n\t * From the deed choices, make a choice fade to the background a bit, hide the extended form\n\t *\n\t * @param {string} deedName\n\t */\n\tmw.UploadWizardDeedChooser.prototype.deselectDeedInterface = function ( deedName ) {\n\t\tconst $deedRadio = this.$element.find( '.mwe-upwiz-deed-radio-' + deedName + ' input' ),\n\t\t\t$deedForm = this.$element.find( '.mwe-upwiz-deed.mwe-upwiz-deed-' + deedName );\n\n\t\t$deedRadio.prop( 'checked', false );\n\t\t$deedForm.removeClass( 'selected' );\n\t\t// Prevent validation of deselected deeds by disabling all form inputs\n\t\t// TODO: Use a tag selector\n\t\t// eslint-disable-next-line no-jquery/no-sizzle\n\t\t$deedForm.find( ':input' ).prop( 'disabled', true );\n\t\t$deedForm.hide();\n\t};\n\n\t/**\n\t * From the deed choice page, show a particular deed\n\t *\n\t * @param {string} deedName\n\t */\n\tmw.UploadWizardDeedChooser.prototype.selectDeedInterface = function ( deedName ) {\n\t\tconst $deedRadio = this.$element.find( '.mwe-upwiz-deed-radio-' + deedName + ' input' ),\n\t\t\t$deedForm = this.$element.find( '.mwe-upwiz-deed.mwe-upwiz-deed-' + deedName );\n\n\t\t// deselect all other deeds\n\t\tObject.keys( this.deeds ).forEach( ( name ) => {\n\t\t\tif ( name === deedName ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.deselectDeedInterface( name );\n\t\t} );\n\n\t\t$deedRadio.prop( 'checked', true );\n\t\t$deedForm.addClass( 'selected' );\n\t\t// (Re-)enable all form inputs\n\t\t// TODO: Use a tag selector\n\t\t// eslint-disable-next-line no-jquery/no-sizzle\n\t\t$deedForm.find( ':input' ).prop( 'disabled', false );\n\t\t$deedForm.show();\n\t};\n\n\tmw.UploadWizardDeedChooser.prototype.remove = function () {\n\t\tObject.keys( this.deeds ).forEach( ( name ) => {\n\t\t\tthis.deeds[ name ].unload();\n\t\t} );\n\n\t\tthis.$element.remove();\n\t};\n\n\t/**\n\t * @return {Object}\n\t */\n\tmw.UploadWizardDeedChooser.prototype.getSerialized = function () {\n\t\treturn this.deed ? this.deed.getSerialized() : {};\n\t};\n\n\t/**\n\t * @param {Object} serialized\n\t */\n\tmw.UploadWizardDeedChooser.prototype.setSerialized = function ( serialized ) {\n\t\tlet deed;\n\n\t\tif ( serialized.name && serialized.name in this.deeds ) {\n\t\t\tdeed = this.deeds[ serialized.name ];\n\t\t\tdeed.setSerialized( serialized );\n\t\t\tthis.selectDeed( deed );\n\t\t}\n\t};\n\n}() );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.UploadWizardDetails.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":498,"column":5,"nodeType":"CallExpression","endLine":504,"endColumn":8},{"ruleId":"es-x/no-symbol-prototype-description","severity":1,"message":"ES2019 'Symbol.prototype.description' property is forbidden.","line":555,"column":9,"nodeType":"MemberExpression","messageId":"forbidden","endLine":555,"endColumn":36},{"ruleId":"es-x/no-symbol-prototype-description","severity":1,"message":"ES2019 'Symbol.prototype.description' property is forbidden.","line":560,"column":14,"nodeType":"MemberExpression","messageId":"forbidden","endLine":560,"endColumn":41},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.FieldLayout' is undefined.","line":665,"column":1,"nodeType":"Block","endLine":665,"endColumn":1},{"ruleId":"jsdoc/require-param-type","severity":1,"message":"Missing JSDoc @param \"thorough\" type.","line":678,"column":1,"nodeType":"Block","endLine":678,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":715,"column":4,"nodeType":"CallExpression","endLine":717,"endColumn":7},{"ruleId":"es-x/no-symbol-prototype-description","severity":1,"message":"ES2019 'Symbol.prototype.description' property is forbidden.","line":844,"column":20,"nodeType":"MemberExpression","messageId":"forbidden","endLine":844,"endColumn":48},{"ruleId":"jsdoc/require-returns-check","severity":1,"message":"JSDoc @return declaration present but return expression not available in function.","line":935,"column":3,"nodeType":"Block","endLine":943,"endColumn":6},{"ruleId":"es-x/no-symbol-prototype-description","severity":1,"message":"ES2019 'Symbol.prototype.description' property is forbidden.","line":1017,"column":9,"nodeType":"MemberExpression","messageId":"forbidden","endLine":1017,"endColumn":31},{"ruleId":"es-x/no-symbol-prototype-description","severity":1,"message":"ES2019 'Symbol.prototype.description' property is forbidden.","line":1018,"column":45,"nodeType":"MemberExpression","messageId":"forbidden","endLine":1018,"endColumn":67}],"suppressedMessages":[{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":272,"column":24,"nodeType":"CallExpression","endLine":272,"endColumn":57,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":287,"column":7,"nodeType":"CallExpression","endLine":287,"endColumn":24,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":296,"column":24,"nodeType":"CallExpression","endLine":296,"endColumn":57,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":387,"column":26,"nodeType":"CallExpression","endLine":387,"endColumn":59,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":402,"column":9,"nodeType":"CallExpression","endLine":402,"endColumn":26,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":411,"column":26,"nodeType":"CallExpression","endLine":411,"endColumn":59,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":1197,"column":6,"nodeType":"CallExpression","endLine":1197,"endColumn":45,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":1199,"column":6,"nodeType":"CallExpression","endLine":1199,"endColumn":37,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'dataValues' is not defined.","line":1323,"column":37,"nodeType":"Identifier","messageId":"undef","endLine":1323,"endColumn":47,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-undef","severity":2,"message":"'dataValues' is not defined.","line":1335,"column":12,"nodeType":"Identifier","messageId":"undef","endLine":1335,"endColumn":22,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":10,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\tconst NS_FILE = mw.config.get( 'wgNamespaceIds' ).file;\n\n\t/**\n\t * Object that represents the Details (step 2) portion of the UploadWizard\n\t * n.b. each upload gets its own details.\n\t *\n\t * @class\n\t * @param {mw.UploadWizardUpload} upload\n\t * @param {jQuery} $containerDiv The `div` to put the interface into\n\t */\n\tmw.UploadWizardDetails = function ( upload, $containerDiv ) {\n\t\tthis.upload = upload;\n\t\tthis.$containerDiv = $containerDiv;\n\t\tthis.api = upload.api;\n\n\t\tthis.mainFields = [];\n\n\t\tthis.structuredDataSubmissionErrors = false;\n\n\t\tthis.$div = $( '<div>' ).addClass( 'mwe-upwiz-info-file filled' );\n\n\t\tOO.EventEmitter.call( this );\n\t};\n\n\tmw.UploadWizardDetails.prototype = {\n\n\t\t// Has this details object been attached to the DOM already?\n\t\tisAttached: false,\n\n\t\t// Build the interface and attach all elements - do this on demand\n\t\tbuildInterface: function () {\n\t\t\tconst config = mw.UploadWizard.config,\n\t\t\t\tcaptionsAvailable = config.wikibase.enabled && config.wikibase.captions,\n\t\t\t\t// the following only end up getting used if statements are enabled\n\t\t\t\tdataTypesMap = mw.config.get( 'wbDataTypes' ) || {},\n\t\t\t\tdefaultProperties = mw.config.get( 'wbmiDefaultProperties' ) || [],\n\t\t\t\tpropertyTypes = mw.config.get( 'wbmiPropertyTypes' ) || {},\n\t\t\t\tpropertyDataValuesTypes = [],\n\t\t\t\tstatementFields = {};\n\n\t\t\tthis.propertyTitles = Object.assign(\n\t\t\t\t{},\n\t\t\t\tmw.config.get( 'wbmiPropertyTitles' ) || {},\n\t\t\t\tmw.config.get( 'upwizPropertyTitles' ) || {}\n\t\t\t);\n\t\t\tthis.dateProperty = config.wikibase.properties.date || '';\n\n\t\t\tthis.$thumbnailDiv = $( '<div>' ).addClass( 'mwe-upwiz-thumbnail' );\n\n\t\t\tthis.$dataDiv = $( '<div>' ).addClass( 'mwe-upwiz-data' );\n\n\t\t\t//\n\t\t\t// Title\n\t\t\t//\n\t\t\tthis.titleDetails = new uw.TitleDetailsWidget( {\n\t\t\t\t// Normalize file extension, e.g. 'JPEG' to 'jpg'\n\t\t\t\textension: mw.Title.normalizeExtension( this.upload.title.getExtension() ),\n\t\t\t\tminLength: config.minTitleLength,\n\t\t\t\tmaxLength: config.maxTitleLength\n\t\t\t} );\n\t\t\tthis.titleDetails.on( 'change', () => this.emit( 'change' ) );\n\t\t\tthis.titleDetailsField = new uw.FieldLayout( this.titleDetails, {\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-title' ).text(),\n\t\t\t\trequired: true\n\t\t\t} );\n\t\t\tthis.mainFields.push( this.titleDetailsField );\n\n\t\t\t//\n\t\t\t// Captions\n\t\t\t//\n\t\t\tthis.captionsDetails = new uw.MultipleLanguageInputWidget( {\n\t\t\t\tinputWidgetConstructor: OO.ui.TextInputWidget.bind( null, {\n\t\t\t\t\tclasses: [ 'mwe-upwiz-singleLanguageInputWidget-text' ]\n\t\t\t\t} ),\n\t\t\t\trequired: true,\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-caption-add' ),\n\t\t\t\terrorBlank: mw.message( 'mwe-upwiz-error-caption-blank' ),\n\t\t\t\tremove: mw.message( 'mwe-upwiz-remove-caption' ),\n\t\t\t\tminLength: config.minCaptionLength,\n\t\t\t\tmaxLength: config.maxCaptionLength\n\t\t\t} );\n\t\t\tthis.captionsDetails.on( 'change', () => this.emit( 'change' ) );\n\t\t\tthis.captionsDetailsField = new uw.FieldLayout( this.captionsDetails, {\n\t\t\t\trequired: true,\n\t\t\t\tclasses: [ 'mwe-upwiz-caption' ],\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-caption' ).text(),\n\t\t\t\thelp: mw.message( 'mwe-upwiz-tooltip-caption' ).text()\n\t\t\t} );\n\t\t\tif ( captionsAvailable ) {\n\t\t\t\tthis.mainFields.push( this.captionsDetailsField );\n\t\t\t}\n\n\t\t\t//\n\t\t\t// Descriptions\n\t\t\t//\n\t\t\t// Description is not required if a campaign provides alternative wikitext fields,\n\t\t\t// which are assumed to function like a description\n\t\t\tconst descriptionRequired = !(\n\t\t\t\tconfig.fields &&\n\t\t\t\tconfig.fields.length &&\n\t\t\t\tconfig.fields[ 0 ].wikitext\n\t\t\t);\n\t\t\t// Main widget\n\t\t\tthis.descriptionsDetails = new uw.MultipleLanguageInputWidget( {\n\t\t\t\t// if captions are available then the default is to copy them to descriptions,\n\t\t\t\t// so the descriptions field itself is not required\n\t\t\t\trequired: descriptionRequired && !captionsAvailable,\n\t\t\t\tclasses: [ 'mwe-upwiz-caption' ],\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-desc-add' ),\n\t\t\t\terrorBlank: mw.message( 'mwe-upwiz-error-description-blank' ),\n\t\t\t\tremove: mw.message( 'mwe-upwiz-remove-description' ),\n\t\t\t\tminLength: config.minDescriptionLength,\n\t\t\t\tmaxLength: config.maxDescriptionLength\n\t\t\t} );\n\t\t\tthis.descriptionsDetails.on( 'change', () => this.emit( 'change' ) );\n\n\t\t\t// Checkbox telling whether descriptions must be identical to captions.\n\t\t\t// If selected, hide descriptions. This is the default behavior.\n\t\t\tthis.descriptionSameAsCaptionCheckbox = new OO.ui.CheckboxMultioptionWidget( {\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-description-same-as-caption' ).text(),\n\t\t\t\t// set it as selected only when we have captions in the first place,\n\t\t\t\t// otherwise there will be nothing to copy from\n\t\t\t\t// note that in such case, this checkbox should not be displayed,\n\t\t\t\t// but we may still check its value when extracting data\n\t\t\t\tselected: captionsAvailable\n\t\t\t} );\n\t\t\tthis.descriptionSameAsCaption = new OO.ui.CheckboxMultiselectWidget( {\n\t\t\t\tclasses: [ 'mwe-upwiz-description-same-as-caption-checkbox' ],\n\t\t\t\titems: [ this.descriptionSameAsCaptionCheckbox ]\n\t\t\t} );\n\t\t\tthis.descriptionSameAsCaptionCheckbox.on( 'change', () => {\n\t\t\t\tthis.descriptionsDetails.$element.toggle(\n\t\t\t\t\t!this.descriptionSameAsCaptionCheckbox.isSelected()\n\t\t\t\t);\n\t\t\t\t// if descriptions are entered separately rather than being copied\n\t\t\t\t// from captions, they become required (unless they are not, e.g.\n\t\t\t\t// when a campaign provides alternatives) & captions turn optional\n\t\t\t\tthis.descriptionsDetails.setRequired( descriptionRequired && !this.descriptionSameAsCaptionCheckbox.isSelected() );\n\t\t\t\tthis.captionsDetails.setRequired( this.descriptionSameAsCaptionCheckbox.isSelected() );\n\t\t\t\tthis.emit( 'change' );\n\t\t\t} );\n\n\t\t\t// Descriptions are fickle; they are required (unless, as described earlier,\n\t\t\t// a campaign provides alternatives), but are not necessarily visible:\n\t\t\t// they default to being hidden and automatically being copied over from\n\t\t\t// captions. Unless captions aren't even available, in which case they\n\t\t\t// need to be on display after all...\n\t\t\t// uw.FieldLayout doesn't currently lend itself to having additional content\n\t\t\t// between the title and the validated element (descriptionsDetails in this\n\t\t\t// case), and I'd rather avoid reaching into descriptionsDetails to\n\t\t\t// conditionally insert the \"copy\" nodes in the right place.\n\t\t\t// We also can't stick the title to the \"copy\" field, because that's not\n\t\t\t// even guaranteed to be something that is supported.\n\t\t\t// Best I can think of would be to combine both of these in another,\n\t\t\t// separate widget; there's a little complication in forwarding the error\n\t\t\t// handling between uw.FieldLayout (or rather, uw.ValidationMessageElement)\n\t\t\t// and the actual widget (descriptionsDetails), but I guess that's what\n\t\t\t// this comment is for!\n\t\t\tthis.descriptionsWidget = new OO.ui.Widget();\n\t\t\tthis.descriptionsWidget.$element.append(\n\t\t\t\t// only show checkbox to copy from captions if captions are enabled)\n\t\t\t\tcaptionsAvailable ? this.descriptionSameAsCaption.$element : null,\n\t\t\t\t// toggle visibility of descriptions based on availability of captions\n\t\t\t\tthis.descriptionsDetails.$element.toggle( !( captionsAvailable ) )\n\t\t\t);\n\t\t\t// if something changes within this widget, then let this widget\n\t\t\t// itself propagate the change event, to trigger input validation\n\t\t\t// that is managed by uw.FieldLayout (or rather, uw.ValidationMessageElement)\n\t\t\tthis.descriptionSameAsCaption.connect( this.descriptionsWidget, { change: [ 'emit', 'change' ] } );\n\t\t\tthis.descriptionsDetails.connect( this.descriptionsWidget, { change: [ 'emit', 'change' ] } );\n\t\t\t// forward validation checks between the combined widget & descriptionsDetails\n\t\t\tthis.descriptionsWidget.validate = ( thorough ) => this.descriptionsDetails.validate( thorough );\n\n\t\t\tthis.descriptionsDetailsField = new uw.FieldLayout( this.descriptionsWidget, {\n\t\t\t\trequired: descriptionRequired,\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-desc' ).text(),\n\t\t\t\thelp: mw.message( 'mwe-upwiz-tooltip-description' ).text()\n\t\t\t} );\n\t\t\tthis.mainFields.push( this.descriptionsDetailsField );\n\n\t\t\t//\n\t\t\t// Date\n\t\t\t//\n\t\t\tthis.dateDetails = new uw.DateDetailsWidget( { upload: this.upload } );\n\t\t\tthis.dateDetails.on( 'change', () => this.emit( 'change' ) );\n\t\t\tthis.dateDetailsField = new uw.FieldLayout( this.dateDetails, {\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-date-created' ).text(),\n\t\t\t\thelp: mw.message( 'mwe-upwiz-tooltip-date' ).text(),\n\t\t\t\trequired: true\n\t\t\t} );\n\t\t\t// The date isn't prefilled anymore if the user changed its value\n\t\t\tthis.dateDetails.on( 'change', () => this.dateDetails.setPrefilled( false ) );\n\t\t\tthis.mainFields.push( this.dateDetailsField );\n\n\t\t\t//\n\t\t\t// Additional information\n\t\t\t//\n\t\t\t// This is a field set: fields will be added later\n\t\t\tthis.additionalInfoFieldset = new OO.ui.FieldsetLayout( {\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-additional-info' ).text(),\n\t\t\t\thelp: mw.message( 'mwe-upwiz-tooltip-additional-info' ).text(),\n\t\t\t\thelpInline: true,\n\t\t\t\tclasses: [ 'mwe-upwiz-fieldsetLayout' ]\n\t\t\t} );\n\n\t\t\t//\n\t\t\t// TODO improve location: https://phabricator.wikimedia.org/T361052\n\t\t\t//\n\t\t\tthis.locationInput = new uw.LocationDetailsWidget( {\n\t\t\t\ttemplateName: 'Location', // {{Location}}\n\t\t\t\tlatitudeKey: 'latitude',\n\t\t\t\tlongitudeKey: 'longitude',\n\t\t\t\theadingKey: 'heading'\n\t\t\t} );\n\t\t\tthis.locationInput.on( 'change', () => this.emit( 'change' ) );\n\t\t\tthis.locationInputField = new uw.FieldLayout( this.locationInput, {\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-location' ).text(),\n\t\t\t\tclasses: [ 'mwe-upwiz-fieldLayout-additional-info' ]\n\t\t\t} );\n\t\t\tthis.objectLocationInput = new uw.LocationDetailsWidget( {\n\t\t\t\ttemplateName: 'Object location', // {{Object location}}\n\t\t\t\tlatitudeKey: 'objectLatitude',\n\t\t\t\tlongitudeKey: 'objectLongitude',\n\t\t\t\theadingKey: ''\n\t\t\t} );\n\t\t\tthis.objectLocationInputField = new uw.FieldLayout( this.objectLocationInput, {\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-location-object' ).text(),\n\t\t\t\tclasses: [ 'mwe-upwiz-fieldLayout-additional-info' ]\n\t\t\t} );\n\n\t\t\t//\n\t\t\t// Categories\n\t\t\t//\n\t\t\tthis.categoriesDetails = new uw.CategoriesDetailsWidget( {\n\t\t\t\tplaceholder: mw.message( 'mwe-upwiz-categories-placeholder' )\n\t\t\t} );\n\t\t\tthis.categoriesDetails.on( 'change', () => this.emit( 'change' ) );\n\t\t\tthis.categoriesDetailsField = new uw.FieldLayout( this.categoriesDetails, {\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-categories' ).text(),\n\t\t\t\thelp: mw.message( 'mwe-upwiz-tooltip-categories-v2' ).text(),\n\t\t\t\tclasses: [ 'mwe-upwiz-fieldLayout-additional-info' ]\n\t\t\t} );\n\t\t\tif ( OO.ui.isMobile() ) {\n\t\t\t\tthis.categoriesDetails.menu.$element.on( 'scroll', () => {\n\t\t\t\t\t// hide the software keyboard, leaving more space for autocomplete menu\n\t\t\t\t\t// (note: the more obvious thing to do would be blurring the input field,\n\t\t\t\t\t// but that would also trigger this widget to automatically convert any\n\t\t\t\t\t// input into a category tag)\n\t\t\t\t\tthis.categoriesDetails.input.$input[ 0 ].inputMode = 'none';\n\t\t\t\t} );\n\t\t\t\tthis.categoriesDetails.input.$input.on( 'focus mousedown keypress', () => {\n\t\t\t\t\t// while scrolling the autocomplete results, we changed the input mode\n\t\t\t\t\t// to get rid of the software keyboard; now that the user is interacting\n\t\t\t\t\t// we need to make sure they're able to)\n\t\t\t\t\tthis.categoriesDetails.input.$input[ 0 ].inputMode = 'text';\n\t\t\t\t} );\n\t\t\t\tthis.categoriesDetails.input.$input.on( 'mousedown', () => {\n\t\t\t\t\t// mobile devices tend to auto-zoom to input when focused (for small\n\t\t\t\t\t// enough font sizes), but we'd actually prefer they don't in this case,\n\t\t\t\t\t// as we will be adding the categories selection menu to the input,\n\t\t\t\t\t// and such zoom may interfere with the user's ability to see/interact\n\t\t\t\t\t// with that\n\t\t\t\t\t// the way we're going to accomplish this is a bit of a hack: we'll\n\t\t\t\t\t// temporarily disable user-scalable in the viewport meta tag on mousedown\n\t\t\t\t\t// (at which point the auto-zoom is not yet triggered), then reinstated\n\t\t\t\t\t// the original viewport content later on in the click lifecycle, so it\n\t\t\t\t\t// once again becomes available for the users to zoom in/out themselves\n\t\t\t\t\t// if they wish\n\t\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t\tconst $viewport = $( 'head meta[name=\"viewport\"]' );\n\t\t\t\t\tconst viewportContent = $viewport.attr( 'content' );\n\t\t\t\t\tif ( viewportContent ) {\n\t\t\t\t\t\t// keep track of original content; then quickly swap out user-scalable\n\t\t\t\t\t\t$viewport.data( 'content', viewportContent );\n\t\t\t\t\t\t$viewport.attr( 'content', viewportContent.replace( /user-scalable=(1|yes)/, 'user-scalable=0' ) );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\tthis.categoriesDetails.input.$input.on( 'focus', () => {\n\t\t\t\t\t// stop existing animations & scroll categories input to top\n\t\t\t\t\t// (mobile devices tend to center the input when focused, but\n\t\t\t\t\t// since we want to add a menu to it, we'd rather have it up top\n\t\t\t\t\t// to allow for more space to show the menu\n\t\t\t\t\tsetTimeout( () => {\n\t\t\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t\t\t$( 'html, body' )\n\t\t\t\t\t\t\t.stop()\n\t\t\t\t\t\t\t.animate( {\n\t\t\t\t\t\t\t\tscrollTop: this.categoriesDetails.input.$input.eq( 0 ).offset().top - 50\n\t\t\t\t\t\t\t} );\n\t\t\t\t\t} );\n\n\t\t\t\t\t// restore original viewport content\n\t\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t\tconst $viewport = $( 'head meta[name=\"viewport\"]' );\n\t\t\t\t\tconst viewportContentOriginal = $viewport.data( 'content' );\n\t\t\t\t\tif ( viewportContentOriginal ) {\n\t\t\t\t\t\t$viewport.attr( 'content', viewportContentOriginal );\n\t\t\t\t\t\t$viewport.removeData( 'content' );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\t//\n\t\t\t// Any other information\n\t\t\t//\n\t\t\tthis.otherDetails = new uw.OtherDetailsWidget();\n\t\t\tthis.otherDetails.on( 'change', () => this.emit( 'change' ) );\n\t\t\tthis.otherDetailsField = new uw.FieldLayout( this.otherDetails, {\n\t\t\t\tlabel: $( '<span>' ).append(\n\t\t\t\t\tnew OO.ui.IconWidget( { icon: 'expand' } ).$element,\n\t\t\t\t\tnew OO.ui.IconWidget( { icon: 'collapse' } ).$element,\n\t\t\t\t\t' ',\n\t\t\t\t\tmw.message( 'mwe-upwiz-other-v2', mw.user ).text()\n\t\t\t\t),\n\t\t\t\tclasses: [\n\t\t\t\t\t'mwe-upwiz-fieldLayout-additional-info',\n\t\t\t\t\t'mwe-upwiz-fieldLayout-additional-info-clickable'\n\t\t\t\t]\n\t\t\t} );\n\t\t\tthis.otherDetails.$element.makeCollapsible( {\n\t\t\t\tcollapsed: true,\n\t\t\t\t$customTogglers: this.otherDetailsField.$element.find( '.oo-ui-fieldLayout-header' )\n\t\t\t} );\n\t\t\t// Expand collapsed sections if the fields within were changed (e.g. by metadata copier)\n\t\t\tthis.otherDetails.on( 'change', () => {\n\t\t\t\tthis.otherDetails.$element.data( 'mw-collapsible' ).expand();\n\t\t\t} );\n\n\t\t\tthis.mainFields.push( this.categoriesDetailsField );\n\t\t\tthis.mainFields.push( this.locationInputField );\n\t\t\tthis.mainFields.push( this.objectLocationInputField );\n\t\t\tthis.mainFields.push( this.otherDetailsField );\n\n\t\t\t//\n\t\t\t// Structured data - Main subjects AKA depicts\n\t\t\t//\n\t\t\tthis.statementWidgets = {};\n\t\t\tif ( config.wikibase.enabled && config.wikibase.statements ) {\n\t\t\t\tObject.keys( propertyTypes ).forEach( ( propertyId ) => {\n\t\t\t\t\tpropertyDataValuesTypes[ propertyId ] = dataTypesMap[ propertyTypes[ propertyId ] ].dataValueType;\n\t\t\t\t} );\n\n\t\t\t\t( config.defaults.statements || [] ).forEach( ( data ) => {\n\t\t\t\t\tif ( !defaultProperties.includes( data.propertyId ) ) {\n\t\t\t\t\t\tdefaultProperties.push( data.propertyId );\n\t\t\t\t\t}\n\t\t\t\t\tpropertyDataValuesTypes[ data.propertyId ] = data.dataType;\n\t\t\t\t} );\n\n\t\t\t\tdefaultProperties.forEach( ( propertyId ) => {\n\t\t\t\t\t// only wikibase-entityid types are supported\n\t\t\t\t\tif ( propertyDataValuesTypes[ propertyId ] !== 'wikibase-entityid' ) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst widget = this.createStatementWidget( propertyId );\n\t\t\t\t\twidget.input.input.lookupMenu.$element.addClass( 'mwe-upwiz-statements-menu' );\n\t\t\t\t\twidget.on( 'change', () => this.emit( 'change' ) );\n\t\t\t\t\tif ( OO.ui.isMobile() ) {\n\t\t\t\t\t\twidget.input.input.lookupMenu.$element.on( 'scroll', () => {\n\t\t\t\t\t\t\t// hide the software keyboard, leaving more space for autocomplete menu\n\t\t\t\t\t\t\t// (note: the more obvious thing to do would be blurring the input field,\n\t\t\t\t\t\t\t// but that would also trigger this widget to hide the menu)\n\t\t\t\t\t\t\twidget.input.input.$input[ 0 ].inputMode = 'none';\n\t\t\t\t\t\t} );\n\t\t\t\t\t\twidget.input.input.$input.on( 'focus mousedown keypress', () => {\n\t\t\t\t\t\t\t// while scrolling the autocomplete results, we changed the input mode\n\t\t\t\t\t\t\t// to get rid of the software keyboard; now that the user is interacting\n\t\t\t\t\t\t\t// we need to make sure they're able to)\n\t\t\t\t\t\t\twidget.input.input.$input[ 0 ].inputMode = 'text';\n\t\t\t\t\t\t} );\n\t\t\t\t\t\twidget.input.input.$input.on( 'mousedown', () => {\n\t\t\t\t\t\t\t// mobile devices tend to auto-zoom to input when focused (for small\n\t\t\t\t\t\t\t// enough font sizes), but we'd actually prefer they don't in this case,\n\t\t\t\t\t\t\t// as we will be adding the statement selection menu to the input,\n\t\t\t\t\t\t\t// and such zoom may interfere with the user's ability to see/interact\n\t\t\t\t\t\t\t// with that\n\t\t\t\t\t\t\t// the way we're going to accomplish this is a bit of a hack: we'll\n\t\t\t\t\t\t\t// temporarily disable user-scalable in the viewport meta tag on mousedown\n\t\t\t\t\t\t\t// (at which point the auto-zoom is not yet triggered), then reinstated\n\t\t\t\t\t\t\t// the original viewport content later on in the click lifecycle, so it\n\t\t\t\t\t\t\t// once again becomes available for the users to zoom in/out themselves\n\t\t\t\t\t\t\t// if they wish\n\t\t\t\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t\t\t\tconst $viewport = $( 'head meta[name=\"viewport\"]' );\n\t\t\t\t\t\t\tconst viewportContent = $viewport.attr( 'content' );\n\t\t\t\t\t\t\tif ( viewportContent ) {\n\t\t\t\t\t\t\t\t// keep track of original content; then quickly swap out user-scalable\n\t\t\t\t\t\t\t\t$viewport.data( 'content', viewportContent );\n\t\t\t\t\t\t\t\t$viewport.attr( 'content', viewportContent.replace( /user-scalable=(1|yes)/, 'user-scalable=0' ) );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} );\n\t\t\t\t\t\twidget.input.input.$input.on( 'focus', () => {\n\t\t\t\t\t\t\t// stop existing animations & scroll statement input to top\n\t\t\t\t\t\t\t// (mobile devices tend to center the input when focused, but\n\t\t\t\t\t\t\t// since we want to add a menu to it, we'd rather have it up top\n\t\t\t\t\t\t\t// to allow for more space to show the menu\n\t\t\t\t\t\t\tsetTimeout( () => {\n\t\t\t\t\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t\t\t\t\t$( 'html, body' )\n\t\t\t\t\t\t\t\t\t.stop()\n\t\t\t\t\t\t\t\t\t.animate( {\n\t\t\t\t\t\t\t\t\t\tscrollTop: widget.input.input.$input.eq( 0 ).offset().top - 50\n\t\t\t\t\t\t\t\t\t} );\n\t\t\t\t\t\t\t} );\n\n\t\t\t\t\t\t\t// restore original viewport content\n\t\t\t\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t\t\t\tconst $viewport = $( 'head meta[name=\"viewport\"]' );\n\t\t\t\t\t\t\tconst viewportContentOriginal = $viewport.data( 'content' );\n\t\t\t\t\t\t\tif ( viewportContentOriginal ) {\n\t\t\t\t\t\t\t\t$viewport.attr( 'content', viewportContentOriginal );\n\t\t\t\t\t\t\t\t$viewport.removeData( 'content' );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} );\n\t\t\t\t\t}\n\n\t\t\t\t\tstatementFields[ propertyId ] = new uw.FieldLayout( widget, {\n\t\t\t\t\t\t// unknown labels will get filled in later on\n\t\t\t\t\t\tlabel: this.propertyTitles[ propertyId ] || propertyId,\n\t\t\t\t\t\tclasses: [ 'mwe-upwiz-fieldLayout-additional-info' ]\n\t\t\t\t\t} );\n\n\t\t\t\t\tthis.additionalInfoFieldset.addItems( [ statementFields[ propertyId ] ] );\n\t\t\t\t\tthis.statementWidgets[ propertyId ] = widget;\n\t\t\t\t\tthis.mainFields.push( statementFields[ propertyId ] );\n\n\t\t\t\t\t// properties without a specified title default to their property id,\n\t\t\t\t\t// but we'll grab the property label from Wikibase and update the\n\t\t\t\t\t// field's label once we have it\n\t\t\t\t\tif ( !( propertyId in this.propertyTitles ) ) {\n\t\t\t\t\t\tthis.getPropertyLabel( propertyId ).then( ( text ) => {\n\t\t\t\t\t\t\tstatementFields[ propertyId ].setLabel( text );\n\t\t\t\t\t\t} );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\tthis.additionalInfoFieldset.addItems(\n\t\t\t\t[\n\t\t\t\t\tthis.categoriesDetailsField,\n\t\t\t\t\tthis.locationInputField,\n\t\t\t\t\tthis.objectLocationInputField,\n\t\t\t\t\tthis.otherDetailsField\n\t\t\t\t]\n\t\t\t);\n\n\t\t\tthis.$form = $( '<form id=\"mwe-upwiz-detailsform' + this.upload.index + '\"></form>' ).addClass( 'detailsForm' );\n\t\t\tthis.$form.append(\n\t\t\t\tthis.titleDetailsField.$element,\n\t\t\t\tcaptionsAvailable ? this.captionsDetailsField.$element : null,\n\t\t\t\tthis.descriptionsDetailsField.$element,\n\t\t\t\tthis.dateDetailsField.$element,\n\t\t\t\tthis.additionalInfoFieldset.$element\n\t\t\t);\n\n\t\t\tthis.$form.on( 'submit', ( e ) => {\n\t\t\t\t// Prevent actual form submission\n\t\t\t\te.preventDefault();\n\t\t\t} );\n\n\t\t\t//\n\t\t\t// Campaigns\n\t\t\t//\n\t\t\tthis.campaignDetailsFields = [];\n\t\t\tconfig.fields.forEach( ( field ) => {\n\t\t\t\tlet customDetails, customDetailsField;\n\n\t\t\t\tif ( field.wikitext ) {\n\t\t\t\t\tcustomDetails = new uw.CampaignDetailsWidget( field );\n\t\t\t\t\tcustomDetailsField = new uw.FieldLayout( customDetails, {\n\t\t\t\t\t\tlabel: $( $.parseHTML( field.label ) ),\n\t\t\t\t\t\trequired: !!field.required\n\t\t\t\t\t} );\n\n\t\t\t\t\tif ( field.initialValue ) {\n\t\t\t\t\t\tcustomDetails.setSerialized( { value: field.initialValue } );\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.$form.append( customDetailsField.$element );\n\t\t\t\t\tthis.campaignDetailsFields.push( customDetailsField );\n\t\t\t\t}\n\t\t\t} );\n\n\t\t\t//\n\t\t\t// Remove upload button\n\t\t\t//\n\t\t\tthis.removeCtrl = new OO.ui.ButtonWidget( {\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-remove' ).text(),\n\t\t\t\ttitle: mw.message( 'mwe-upwiz-remove-upload' ).text(),\n\t\t\t\tclasses: [ 'mwe-upwiz-remove-upload' ],\n\t\t\t\tflags: 'destructive',\n\t\t\t\ticon: 'trash',\n\t\t\t\tframed: false\n\t\t\t} ).on( 'click', () => {\n\t\t\t\tOO.ui.confirm( mw.message( 'mwe-upwiz-license-confirm-remove' ).text(), {\n\t\t\t\t\ttitle: mw.message( 'mwe-upwiz-license-confirm-remove-title' ).text()\n\t\t\t\t} ).done( ( confirmed ) => {\n\t\t\t\t\tif ( confirmed ) {\n\t\t\t\t\t\tthis.upload.emit( 'remove-upload' );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t} );\n\n\t\t\tthis.$thumbnailDiv.append( this.removeCtrl.$element );\n\n\t\t\tthis.statusMessage = new OO.ui.MessageWidget( { inline: true } );\n\t\t\tthis.statusMessage.toggle( false );\n\t\t\tthis.$spinner = $.createSpinner( { size: 'small', type: 'inline' } );\n\t\t\tthis.$spinner.hide();\n\t\t\tthis.$indicator = $( '<div>' ).addClass( 'mwe-upwiz-file-indicator' ).append(\n\t\t\t\tthis.$spinner,\n\t\t\t\tthis.statusMessage.$element\n\t\t\t);\n\n\t\t\tthis.$submittingDiv = $( '<div>' ).addClass( 'mwe-upwiz-submitting' )\n\t\t\t\t.append(\n\t\t\t\t\tthis.$indicator,\n\t\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-details-texts' ).append(\n\t\t\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-visible-file-filename-text' ),\n\t\t\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-file-status-line' )\n\t\t\t\t\t)\n\t\t\t\t);\n\n\t\t\tthis.$dataDiv.append(\n\t\t\t\tthis.$form,\n\t\t\t\tthis.$submittingDiv\n\t\t\t).morphCrossfader();\n\n\t\t\tthis.$div.append(\n\t\t\t\tthis.$thumbnailDiv,\n\t\t\t\tthis.$dataDiv\n\t\t\t);\n\n\t\t\tconst searchParams = new URLSearchParams( location.search );\n\t\t\tconst captionlang = searchParams.get( 'captionlang' );\n\t\t\tif ( config.defaults.caption || captionlang ) {\n\t\t\t\tthis.captionsDetails.setSerialized( {\n\t\t\t\t\tinputs: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: config.defaults.caption || ''\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t} );\n\t\t\t\tthis.captionsDetails.getItems()[ 0 ].fieldWidget.setLanguage(\n\t\t\t\t\tcaptionlang ?\n\t\t\t\t\t\tthis.captionsDetails.getItems()[ 0 ].fieldWidget.getClosestAllowedLanguage( captionlang ) :\n\t\t\t\t\t\tthis.captionsDetails.getItems()[ 0 ].fieldWidget.getDefaultLanguage()\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst descriptionlang = searchParams.get( 'descriptionlang' );\n\t\t\tif ( config.defaults.description || descriptionlang ) {\n\t\t\t\tthis.descriptionSameAsCaptionCheckbox.setSelected( false );\n\t\t\t\tthis.descriptionsDetails.setSerialized( {\n\t\t\t\t\tinputs: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttext: config.defaults.description || ''\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t} );\n\t\t\t\tthis.descriptionsDetails.getItems()[ 0 ].fieldWidget.setLanguage(\n\t\t\t\t\tdescriptionlang ?\n\t\t\t\t\t\tthis.descriptionsDetails.getItems()[ 0 ].fieldWidget.getClosestAllowedLanguage( descriptionlang ) :\n\t\t\t\t\t\tthis.descriptionsDetails.getItems()[ 0 ].fieldWidget.getDefaultLanguage()\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tthis.populate();\n\n\t\t\tthis.interfaceBuilt = true;\n\n\t\t\tif ( this.savedSerialData ) {\n\t\t\t\tthis.setSerialized( this.savedSerialData );\n\t\t\t\tthis.savedSerialData = undefined;\n\t\t\t}\n\t\t},\n\n\t\tcreateStatementWidget: function ( propertyId, dataType, data ) {\n\t\t\tconst propertyPlaceholders = mw.config.get( 'upwizPropertyPlaceholders' ) || {};\n\n\t\t\treturn new uw.StatementWidget( {\n\t\t\t\tpropertyId: propertyId,\n\t\t\t\ttype: dataType,\n\t\t\t\tclasses: [ 'wbmi-statement-input' ],\n\t\t\t\tdata: data,\n\t\t\t\tplaceholder: propertyId in propertyPlaceholders ? propertyPlaceholders[ propertyId ] : ''\n\t\t\t} );\n\t\t},\n\n\t\tgetStatementProperties: function () {\n\t\t\tconst properties = [];\n\t\t\tfor ( const propertyId in this.statementWidgets ) {\n\t\t\t\tproperties.push( {\n\t\t\t\t\tid: propertyId,\n\t\t\t\t\tlabel: this.propertyTitles[ propertyId ]\n\t\t\t\t} );\n\t\t\t}\n\t\t\treturn properties;\n\t\t},\n\n\t\tgetPropertyLabel: function ( propertyId ) {\n\t\t\tconst FormatValueElement = mw.loader.require( 'wikibase.mediainfo.base' ).FormatValueElement,\n\t\t\t\tformatValueElement = new FormatValueElement(),\n\t\t\t\tdatamodel = require( 'wikibase.datamodel' );\n\n\t\t\t// Format the label & capitalize\n\t\t\treturn formatValueElement.formatValue(\n\t\t\t\tnew datamodel.EntityId( propertyId ),\n\t\t\t\t'text/plain'\n\t\t\t).then( ( text ) => text.charAt( 0 ).toUpperCase() + text.slice( 1 ) );\n\t\t},\n\n\t\t/*\n\t\t * Append the div for this details object to the DOM.\n\t\t * We need to ensure that we add divs in the right order\n\t\t * (the order in which the user selected files).\n\t\t *\n\t\t * Will only append once.\n\t\t */\n\t\tattach: function () {\n\t\t\tconst $window = $( window );\n\n\t\t\tconst maybeBuild = () => {\n\t\t\t\tif ( !this.interfaceBuilt && $window.scrollTop() + $window.height() + 1000 >= this.$div.offset().top ) {\n\t\t\t\t\tthis.buildInterface();\n\t\t\t\t\t$window.off( 'scroll', maybeBuild );\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tif ( !this.isAttached ) {\n\t\t\t\tthis.$containerDiv.append( this.$div );\n\n\t\t\t\tif ( $window.scrollTop() + $window.height() + 1000 >= this.$div.offset().top ) {\n\t\t\t\t\tthis.buildInterface();\n\t\t\t\t} else {\n\t\t\t\t\t$window.on( 'scroll', maybeBuild );\n\t\t\t\t}\n\n\t\t\t\tthis.isAttached = true;\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Get file page title for this upload.\n\t\t *\n\t\t * @return {mw.Title|null}\n\t\t */\n\t\tgetTitle: function () {\n\t\t\t// title will not be set until we've actually submitted the file\n\t\t\tif ( this.title === undefined ) {\n\t\t\t\treturn this.titleDetails.getTitle();\n\t\t\t}\n\n\t\t\t// once the file has been submitted, we'll have confirmation on\n\t\t\t// the filename and trust the authoritative source over own input\n\t\t\treturn this.title;\n\t\t},\n\n\t\t/**\n\t\t * @private\n\t\t *\n\t\t * @return {uw.FieldLayout[]}\n\t\t */\n\t\tgetAllFields: function () {\n\t\t\treturn [].concat(\n\t\t\t\tthis.mainFields,\n\t\t\t\tthis.upload.deedChooser.deed ? this.upload.deedChooser.deed.getFields() : [],\n\t\t\t\tthis.campaignDetailsFields\n\t\t\t);\n\t\t},\n\n\t\t/**\n\t\t * Check all the fields for validity.\n\t\t *\n\t\t * @param thorough\n\t\t * @return {jQuery.Promise<mw.uploadWizard.ValidationStatus>}\n\t\t */\n\t\tvalidate: function ( thorough ) {\n\t\t\tconst fieldPromises = this.getAllFields()\n\t\t\t\t.filter( ( fieldLayout ) => !!fieldLayout.fieldWidget.validate )\n\t\t\t\t.map( ( fieldLayout ) => fieldLayout.fieldWidget.validate( thorough ) );\n\n\t\t\treturn mw.uploadWizard.ValidationStatus.mergePromises( ...fieldPromises );\n\t\t},\n\n\t\t/**\n\t\t * Get a thumbnail caption for this upload (basically, the first caption).\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tgetThumbnailCaption: function () {\n\t\t\tlet captions = [];\n\t\t\tif ( mw.UploadWizard.config.wikibase.enabled && mw.UploadWizard.config.wikibase.captions ) {\n\t\t\t\tcaptions = this.captionsDetails.getSerialized().inputs;\n\t\t\t} else {\n\t\t\t\tcaptions = this.descriptionsDetails.getSerialized().inputs;\n\t\t\t}\n\n\t\t\tif ( captions.length > 0 ) {\n\t\t\t\treturn mw.Escaper.escapeForTemplate( captions[ 0 ].text.trim() );\n\t\t\t} else {\n\t\t\t\treturn '';\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Pull some info into the form ( for instance, extracted from EXIF, desired filename )\n\t\t */\n\t\tpopulate: function () {\n\t\t\tconst $thumbnailDiv = this.$thumbnailDiv;\n\t\t\t// This must match the CSS dimensions of .mwe-upwiz-thumbnail\n\t\t\tthis.upload.getThumbnail( 230 ).done( ( thumb ) => {\n\t\t\t\tmw.UploadWizard.placeThumbnail( $thumbnailDiv, thumb );\n\t\t\t} );\n\t\t\tthis.prefillDate();\n\t\t\tthis.prefillTitle();\n\t\t\tthis.prefillDescription();\n\t\t\tthis.prefillLocation();\n\t\t},\n\n\t\t/**\n\t\t * Check if we got an EXIF date back and enter it into the details\n\t\t * XXX We ought to be using date + time here...\n\t\t * EXIF examples tend to be in ISO 8601, but the separators are sometimes things like colons, and they have lots of trailing info\n\t\t * (which we should actually be using, such as time and timezone)\n\t\t */\n\t\tprefillDate: function () {\n\t\t\tconst yyyyMmDdRegex = /^(\\d\\d\\d\\d)[:/-](\\d\\d)[:/-](\\d\\d)\\D.*/,\n\t\t\t\ttimeRegex = /\\D(\\d\\d):(\\d\\d):(\\d\\d)/;\n\n\t\t\t// XXX surely we have this function somewhere already\n\t\t\tfunction pad( n ) {\n\t\t\t\treturn ( n < 10 ? '0' : '' ) + String( n );\n\t\t\t}\n\n\t\t\tfunction getSensibleTime( date ) {\n\t\t\t\tlet str = '';\n\n\t\t\t\tstr += pad( date.getHours() ) + ':';\n\t\t\t\tstr += pad( date.getMinutes() ) + ':';\n\t\t\t\tstr += pad( date.getSeconds() );\n\n\t\t\t\treturn str;\n\t\t\t}\n\n\t\t\t// If not own work, don't prefill\n\t\t\tif ( this.upload.deedChooser.deed.name === 'thirdparty' ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet dateObj;\n\t\t\tif ( this.upload.imageinfo.metadata ) {\n\t\t\t\tconst metadata = this.upload.imageinfo.metadata;\n\t\t\t\t[ 'datetimeoriginal', 'datetimedigitized', 'datetime', 'date' ].some( ( propName ) => {\n\t\t\t\t\tconst dateInfo = metadata[ propName ];\n\t\t\t\t\tif ( dateInfo ) {\n\t\t\t\t\t\tconst matches = dateInfo.trim().match( yyyyMmDdRegex );\n\t\t\t\t\t\tif ( matches ) {\n\t\t\t\t\t\t\tconst timeMatches = dateInfo.trim().match( timeRegex );\n\t\t\t\t\t\t\tif ( timeMatches ) {\n\t\t\t\t\t\t\t\tdateObj = new Date( parseInt( matches[ 1 ], 10 ),\n\t\t\t\t\t\t\t\t\tparseInt( matches[ 2 ], 10 ) - 1,\n\t\t\t\t\t\t\t\t\tparseInt( matches[ 3 ], 10 ),\n\t\t\t\t\t\t\t\t\tparseInt( timeMatches[ 1 ], 10 ),\n\t\t\t\t\t\t\t\t\tparseInt( timeMatches[ 2 ], 10 ),\n\t\t\t\t\t\t\t\t\tparseInt( timeMatches[ 3 ], 10 ) );\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdateObj = new Date( parseInt( matches[ 1 ], 10 ),\n\t\t\t\t\t\t\t\t\tparseInt( matches[ 2 ], 10 ) - 1,\n\t\t\t\t\t\t\t\t\tparseInt( matches[ 3 ], 10 ) );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true; // break from Array.some\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn false;\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\t// If we don't have EXIF lets try other sources - Flickr\n\t\t\tif ( dateObj === undefined && this.upload.file !== undefined && this.upload.file.date !== undefined ) {\n\t\t\t\tconst dateTimeRegex = /^\\d\\d\\d\\d-\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d/;\n\t\t\t\tconst matches = this.upload.file.date.match( dateTimeRegex );\n\t\t\t\tif ( matches ) {\n\t\t\t\t\tthis.dateDetails.setSerialized( {\n\t\t\t\t\t\tprefilled: true,\n\t\t\t\t\t\tvalue: this.upload.file.date\n\t\t\t\t\t} );\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// if we don't have EXIF or other metadata, just don't put a date in.\n\t\t\t// XXX if we have FileAPI, it might be clever to look at file attrs, saved\n\t\t\t// in the upload object for use here later, perhaps\n\t\t\tif ( dateObj === undefined ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet dateStr = dateObj.getFullYear() + '-' + pad( dateObj.getMonth() + 1 ) + '-' + pad( dateObj.getDate() );\n\n\t\t\t// Add the time\n\t\t\t// If the date but not the time is set in EXIF data, we'll get a bogus\n\t\t\t// time value of '00:00:00'.\n\t\t\t// FIXME: Check for missing time value earlier rather than blacklisting\n\t\t\t// a potentially legitimate time value.\n\t\t\tconst sensibleTime = getSensibleTime( dateObj );\n\t\t\tif ( sensibleTime !== '00:00:00' ) {\n\t\t\t\tdateStr += ' ' + sensibleTime;\n\t\t\t}\n\n\t\t\t// ok by now we should definitely have a dateObj and a date string\n\t\t\tthis.dateDetails.setSerialized( {\n\t\t\t\tprefilled: true,\n\t\t\t\tvalue: dateStr\n\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * Set the title of the thing we just uploaded, visibly\n\t\t */\n\t\tprefillTitle: function () {\n\t\t\tthis.titleDetails.setSerialized( {\n\t\t\t\ttitle: this.upload.title.getNameText()\n\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * Prefill the image description if we have a description\n\t\t *\n\t\t * Note that this is not related to specifying the description from the query\n\t\t * string (that happens earlier). This is for when we have retrieved a\n\t\t * description from an upload_by_url upload (e.g. Flickr transfer)\n\t\t * or from the metadata.\n\t\t */\n\t\tprefillDescription: function () {\n\t\t\tif (\n\t\t\t\tthis.descriptionsDetails.getWikiText() === '' &&\n\t\t\t\tthis.upload.file !== undefined\n\t\t\t) {\n\t\t\t\tconst m = this.upload.imageinfo.metadata;\n\t\t\t\tlet descText = this.upload.file.description ||\n\t\t\t\t\t( m && m.imagedescription && m.imagedescription[ 0 ] && m.imagedescription[ 0 ].value );\n\n\t\t\t\tif ( descText ) {\n\t\t\t\t\t// strip out any HTML tags\n\t\t\t\t\tdescText = descText.replace( /<[^>]+>/g, '' );\n\t\t\t\t\t// & and \" are escaped by Flickr, so we need to unescape\n\t\t\t\t\tdescText = descText.replace( /&/g, '&' ).replace( /"/g, '\"' );\n\n\t\t\t\t\tthis.descriptionSameAsCaptionCheckbox.setSelected( false );\n\t\t\t\t\tthis.descriptionsDetails.setSerialized( {\n\t\t\t\t\t\tinputs: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttext: descText.trim()\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t]\n\t\t\t\t\t} );\n\t\t\t\t\t// The language is probably wrong in many cases...\n\t\t\t\t\tthis.descriptionsDetails.getItems()[ 0 ].fieldWidget.setLanguage(\n\t\t\t\t\t\tthis.descriptionsDetails.getItems()[ 0 ].fieldWidget.getClosestAllowedLanguage( mw.config.get( 'wgContentLanguage' ) )\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Prefill location input from image info and metadata\n\t\t *\n\t\t * As of MediaWiki 1.18, the exif parser translates the rational GPS data tagged by the camera\n\t\t * to decimal format. Let's just use that.\n\t\t */\n\t\tprefillLocation: function () {\n\t\t\tconst m = this.upload.imageinfo.metadata,\n\t\t\t\tvalues = {};\n\n\t\t\tif ( mw.UploadWizard.config.defaults.lat ) {\n\t\t\t\tvalues.latitude = mw.UploadWizard.config.defaults.lat;\n\t\t\t}\n\t\t\tif ( mw.UploadWizard.config.defaults.lon ) {\n\t\t\t\tvalues.longitude = mw.UploadWizard.config.defaults.lon;\n\t\t\t}\n\t\t\tif ( mw.UploadWizard.config.defaults.heading ) {\n\t\t\t\tvalues.heading = mw.UploadWizard.config.defaults.heading;\n\t\t\t}\n\n\t\t\tif ( m ) {\n\t\t\t\tlet dir = m.gpsimgdirection || m.gpsdestbearing;\n\n\t\t\t\tif ( dir ) {\n\t\t\t\t\tif ( /^\\d+\\/\\d+$/.test( dir ) ) {\n\t\t\t\t\t\t// Apparently it can take the form \"x/y\" instead of\n\t\t\t\t\t\t// a decimal value. Mighty silly, but let's save it.\n\t\t\t\t\t\tdir = dir.split( '/' );\n\t\t\t\t\t\tdir = parseInt( dir[ 0 ], 10 ) / parseInt( dir[ 1 ], 10 );\n\t\t\t\t\t}\n\n\t\t\t\t\tvalues.heading = dir;\n\t\t\t\t}\n\n\t\t\t\t// Prefill useful stuff only\n\t\t\t\tif ( Number( m.gpslatitude ) && Number( m.gpslongitude ) ) {\n\t\t\t\t\tvalues.latitude = m.gpslatitude;\n\t\t\t\t\tvalues.longitude = m.gpslongitude;\n\t\t\t\t} else if (\n\t\t\t\t\tthis.upload.file &&\n\t\t\t\t\tthis.upload.file.location &&\n\t\t\t\t\tthis.upload.file.location.latitude &&\n\t\t\t\t\tthis.upload.file.location.longitude\n\t\t\t\t) {\n\t\t\t\t\tvalues.latitude = this.upload.file.location.latitude;\n\t\t\t\t\tvalues.longitude = this.upload.file.location.longitude;\n\t\t\t\t}\n\t\t\t\tif ( Number( m.gpsdestlatitude ) && Number( m.gpsdestlongitude ) ) {\n\t\t\t\t\tvalues.objectLatitude = m.gpsdestlatitude;\n\t\t\t\t\tvalues.objectLongitude = m.gpsdestlongitude;\n\t\t\t\t} else if (\n\t\t\t\t\tthis.upload.file &&\n\t\t\t\t\tthis.upload.file.objectLocation &&\n\t\t\t\t\tthis.upload.file.objectLocation.objectLatitude &&\n\t\t\t\t\tthis.upload.file.objectLocation.objectLongitude\n\t\t\t\t) {\n\t\t\t\t\tvalues.objectLatitude = this.upload.file.objectLocation.objectLatitude;\n\t\t\t\t\tvalues.objectLongitude = this.upload.file.objectLocation.objectLongitude;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthis.locationInput.setSerialized( values );\n\t\t\tthis.objectLocationInput.setSerialized( values );\n\t\t\tthis.objectLocationInputField.$element.toggle( Boolean( values.objectLatitude && values.objectLongitude ) );\n\t\t},\n\n\t\t/**\n\t\t * Get a machine-readable representation of the current state of the upload details. It can be\n\t\t * passed to #setSerialized to restore this state (or to set it for another instance of the same\n\t\t * class).\n\t\t *\n\t\t * Note that this doesn't include custom deed's state.\n\t\t *\n\t\t * @return {Object.<string,Object>}\n\t\t */\n\t\tgetSerialized: function () {\n\t\t\tif ( !this.interfaceBuilt ) {\n\t\t\t\t// We don't have the interface yet, but it'll get filled out as\n\t\t\t\t// needed.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\ttitle: this.titleDetails.getSerialized(),\n\t\t\t\tcaption: this.captionsDetails.getSerialized(),\n\t\t\t\tdescription: this.descriptionSameAsCaptionCheckbox.isSelected() ? undefined : this.descriptionsDetails.getSerialized(),\n\t\t\t\tdate: this.dateDetails.getSerialized(),\n\t\t\t\tcategories: this.categoriesDetails.getSerialized(),\n\t\t\t\tstatements: this.serializeStatements(),\n\t\t\t\tlocation: this.locationInput.getSerialized(),\n\t\t\t\tobjectLocation: this.objectLocationInput.getSerialized(),\n\t\t\t\tother: this.otherDetails.getSerialized(),\n\t\t\t\tcampaigns: this.campaignDetailsFields.map( ( field ) => field.fieldWidget.getSerialized() )\n\t\t\t};\n\t\t},\n\n\t\tserializeStatements: function () {\n\t\t\tconst serialized = {};\n\t\t\tfor ( const propertyId in this.statementWidgets ) {\n\t\t\t\tserialized[ propertyId ] = this.statementWidgets[ propertyId ].getStatementList();\n\t\t\t}\n\t\t\treturn serialized;\n\t\t},\n\n\t\tsetStatementsFromSerialized: function ( serialized ) {\n\t\t\tfor ( const propertyId in serialized ) {\n\t\t\t\tthis.statementWidgets[ propertyId ].resetData( serialized[ propertyId ] );\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Set the state of this widget from machine-readable representation, as returned by\n\t\t * #getSerialized.\n\t\t *\n\t\t * Fields from the representation can be omitted to keep the current value.\n\t\t *\n\t\t * @param {Object.<string,Object>} [serialized]\n\t\t */\n\t\tsetSerialized: function ( serialized ) {\n\t\t\tlet i;\n\n\t\t\tif ( !this.interfaceBuilt ) {\n\t\t\t\t// There's no interface yet! Don't load the data, just keep it\n\t\t\t\t// around.\n\t\t\t\tif ( serialized === undefined ) {\n\t\t\t\t\t// Note: This will happen if we \"undo\" a copy operation while\n\t\t\t\t\t// some of the details interfaces aren't loaded.\n\t\t\t\t\tthis.savedSerialData = undefined;\n\t\t\t\t} else {\n\t\t\t\t\tthis.savedSerialData = $.extend( true,\n\t\t\t\t\t\tthis.savedSerialData || {},\n\t\t\t\t\t\tserialized\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( serialized === undefined ) {\n\t\t\t\t// This is meaningless if the interface is already built.\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( serialized.title ) {\n\t\t\t\tthis.titleDetails.setSerialized( serialized.title );\n\t\t\t}\n\t\t\tif ( serialized.caption ) {\n\t\t\t\tthis.captionsDetails.setSerialized( serialized.caption );\n\t\t\t}\n\t\t\tif ( serialized.description ) {\n\t\t\t\tthis.descriptionsDetails.setSerialized( serialized.description );\n\t\t\t\tthis.descriptionSameAsCaptionCheckbox.setSelected( false );\n\t\t\t}\n\t\t\tif ( serialized.date ) {\n\t\t\t\tthis.dateDetails.setSerialized( serialized.date );\n\t\t\t}\n\t\t\tif ( serialized.categories ) {\n\t\t\t\tthis.categoriesDetails.setSerialized( serialized.categories );\n\t\t\t}\n\t\t\tif ( serialized.statements ) {\n\t\t\t\tthis.setStatementsFromSerialized( serialized.statements );\n\t\t\t}\n\t\t\tif ( serialized.location ) {\n\t\t\t\tthis.locationInput.setSerialized( serialized.location );\n\t\t\t}\n\t\t\tif ( serialized.objectLocation ) {\n\t\t\t\tthis.objectLocationInput.setSerialized( serialized.objectLocation );\n\t\t\t}\n\t\t\tif ( serialized.other ) {\n\t\t\t\tthis.otherDetails.setSerialized( serialized.other );\n\t\t\t}\n\t\t\tif ( serialized.campaigns ) {\n\t\t\t\tfor ( i = 0; i < this.campaignDetailsFields.length; i++ ) {\n\t\t\t\t\tthis.campaignDetailsFields[ i ].fieldWidget.setSerialized( serialized.campaigns[ i ] );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Convert entire details for this file into wikiText, which will then be posted to the file\n\t\t *\n\t\t * This function assumes that all input is valid.\n\t\t *\n\t\t * @return {string} wikitext representing all details\n\t\t */\n\t\tgetWikiText: function () {\n\t\t\tlet wikiText = '';\n\n\t\t\t// https://commons.wikimedia.org/wiki/Template:Information\n\t\t\t// can we be more slick and do this with maps, applys, joins?\n\t\t\tconst information = {\n\t\t\t\t// {{lang|description in lang}}* (required)\n\t\t\t\tdescription: '',\n\t\t\t\t// holds {{Prompt}} template ... gets unset if it has no value\n\t\t\t\t'Other fields 1': '',\n\t\t\t\t// YYYY, YYYY-MM, or YYYY-MM-DD (required) use jquery but allow editing, then double check for sensible date.\n\t\t\t\tdate: '',\n\t\t\t\t// {{own}} or wikitext (optional)\n\t\t\t\tsource: '',\n\t\t\t\t// any wikitext, but particularly {{Creator:Name Surname}} (required)\n\t\t\t\tauthor: '',\n\t\t\t\t// leave blank unless OTRS pending; by default will be \"see below\" (optional)\n\t\t\t\tpermission: '',\n\t\t\t\t// pipe separated list, other versions (optional)\n\t\t\t\t'other versions': ''\n\t\t\t};\n\n\t\t\tif ( this.descriptionSameAsCaptionCheckbox.isSelected() ) {\n\t\t\t\tinformation.description = this.captionsDetails.getWikiText();\n\t\t\t} else {\n\t\t\t\tinformation.description = this.descriptionsDetails.getWikiText();\n\t\t\t}\n\n\t\t\tconst deed = this.upload.deedChooser.deed;\n\t\t\tif ( deed.getAiPromptWikitext() ) {\n\t\t\t\tinformation[ 'Other fields 1' ] = deed.getAiPromptWikitext();\n\t\t\t} else {\n\t\t\t\tdelete information[ 'Other fields 1' ];\n\t\t\t}\n\n\t\t\tthis.campaignDetailsFields.forEach( ( layout ) => {\n\t\t\t\tinformation.description += layout.fieldWidget.getWikiText();\n\t\t\t} );\n\n\t\t\tinformation.date = this.dateDetails.getWikiText();\n\n\t\t\tinformation.source = deed.getSourceWikiText( this.upload );\n\n\t\t\tinformation.author = deed.getAuthorWikiText( this.upload );\n\n\t\t\tlet info = '';\n\n\t\t\tfor ( const key in information ) {\n\t\t\t\tif ( Object.prototype.hasOwnProperty.call( information, key ) ) {\n\t\t\t\t\tinfo += '|' + key.replace( /:/g, '_' );\n\t\t\t\t\tinfo += '=' + mw.Escaper.escapeForTemplate( information[ key ] ) + '\\n';\n\t\t\t\t}\n\t\t\t}\n\n\t\t\twikiText += '=={{int:filedesc}}==\\n';\n\t\t\twikiText += '{{Information\\n' + info + '}}\\n';\n\n\t\t\twikiText += this.locationInput.getWikiText() + '\\n';\n\t\t\twikiText += this.objectLocationInput.getWikiText() + '\\n';\n\n\t\t\t// add an \"anything else\" template if needed\n\t\t\twikiText += this.otherDetails.getWikiText() + '\\n\\n';\n\n\t\t\t// add licensing information\n\t\t\twikiText += '\\n=={{int:license-header}}==\\n';\n\t\t\twikiText += deed.getLicenseWikiText( this.upload ) + '\\n\\n';\n\n\t\t\tif ( mw.UploadWizard.config.autoAdd.wikitext !== undefined ) {\n\t\t\t\twikiText += mw.UploadWizard.config.autoAdd.wikitext + '\\n';\n\t\t\t}\n\n\t\t\t// add parameters for list callback bot\n\t\t\t// this cue will be used to supplement a wiki page with an image thumbnail\n\t\t\tif ( $( '#imgPicker' + this.upload.index ).prop( 'checked' ) ) {\n\t\t\t\twikiText += '\\n<!-- WIKIPAGE_UPDATE_PARAMS ' +\n\t\t\t\t\tmw.UploadWizard.config.defaults.objref +\n\t\t\t\t\t' -->\\n';\n\t\t\t}\n\n\t\t\t// templates for other options\n\t\t\twikiText += deed.getTemplateOptionsWikiText() + '\\n\\n';\n\n\t\t\t// categories\n\t\t\twikiText += '\\n' + this.categoriesDetails.getWikiText();\n\n\t\t\t// sanitize wikitext if TextCleaner is defined (MediaWiki:TextCleaner.js)\n\t\t\tif ( typeof window.TextCleaner !== 'undefined' && typeof window.TextCleaner.sanitizeWikiText === 'function' ) {\n\t\t\t\twikiText = window.TextCleaner.sanitizeWikiText( wikiText, true );\n\t\t\t}\n\n\t\t\t// remove too many newlines in a row\n\t\t\twikiText = wikiText.replace( /\\n{3,}/g, '\\n\\n' );\n\n\t\t\treturn wikiText;\n\t\t},\n\n\t\t/**\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tsubmit: function () {\n\t\t\tthis.$containerDiv.find( 'form' ).trigger( 'submit' );\n\n\t\t\tthis.upload.title = this.getTitle();\n\t\t\tthis.upload.state = 'submitting-details';\n\t\t\tthis.setStatus( mw.message( 'mwe-upwiz-submitting-details' ).text() );\n\t\t\tthis.showIndicator( 'progress' );\n\n\t\t\tconst wikitext = this.getWikiText();\n\t\t\tlet promise = this.submitWikiText( wikitext );\n\n\t\t\tif ( mw.UploadWizard.config.wikibase.enabled ) {\n\t\t\t\tpromise = promise\n\t\t\t\t\t.then( () => {\n\t\t\t\t\t\t// just work out the mediainfo entity id from the page id\n\t\t\t\t\t\tconst status = mw.message( 'mwe-upwiz-submitting-structured-data' );\n\t\t\t\t\t\tthis.setStatus( status.text() );\n\t\t\t\t\t\treturn this.getMediaInfoEntityId(); // (T208545)\n\t\t\t\t\t} )\n\t\t\t\t\t// submit structured data to wikibase\n\t\t\t\t\t.then( this.submitStructuredData.bind( this ) );\n\t\t\t}\n\n\t\t\treturn promise.then( () => {\n\t\t\t\t// FIXME - structuredDataSubmissionErrors gets set to true in the catch block of\n\t\t\t\t// postStructuredData which executes AFTER this, and so the error never gets\n\t\t\t\t// displayed\n\t\t\t\tif ( this.structuredDataSubmissionErrors ) {\n\t\t\t\t\tlet errorString = '<strong>' + mw.message(\n\t\t\t\t\t\t'mwe-upwiz-error-submit-structured-data'\n\t\t\t\t\t).parse() + '</strong>';\n\n\t\t\t\t\terrorString += '<strong>' + mw.message(\n\t\t\t\t\t\t'mwe-upwiz-error-submit-structured-data-remedy',\n\t\t\t\t\t\tthis.upload.imageinfo.canonicaltitle\n\t\t\t\t\t).parse() + '</strong>';\n\n\t\t\t\t\tthis.upload.state = 'sdc-api-error';\n\t\t\t\t\tthis.showError(\n\t\t\t\t\t\t'sd-fail',\n\t\t\t\t\t\terrorString\n\t\t\t\t\t);\n\t\t\t\t\t// If there is a structured data error, then details for how to deal with it are\n\t\t\t\t\t// in the errorString above, no need to show anything else\n\t\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t\t$( '#mwe-upwiz-details-warning-count' ).hide();\n\t\t\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t\t\t$( '.mwe-upwiz-remove-upload' ).hide();\n\t\t\t\t\t// Remove the beforeunload warning, as the image is now as uploaded\n\t\t\t\t\t// as it's going to get\n\t\t\t\t\t$( window ).off( 'beforeunload' );\n\t\t\t\t} else {\n\t\t\t\t\tthis.showIndicator( 'success' );\n\t\t\t\t\tthis.setStatus( mw.message( 'mwe-upwiz-published' ).text() );\n\t\t\t\t}\n\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tgetMediaInfoEntityId: function () {\n\t\t\tif ( this.mediaInfoEntityId !== undefined ) {\n\t\t\t\treturn $.Deferred().resolve( this.mediaInfoEntityId ).promise();\n\t\t\t}\n\n\t\t\treturn this.upload.api.get( {\n\t\t\t\taction: 'query',\n\t\t\t\tprop: 'info',\n\t\t\t\ttitles: this.getTitle().getPrefixedDb()\n\t\t\t} ).then( ( result ) => {\n\t\t\t\tlet message;\n\n\t\t\t\tif ( result.query.pages[ 0 ].missing ) {\n\t\t\t\t\t// page doesn't exist (yet)\n\t\t\t\t\tmessage = mw.message( 'mwe-upwiz-error-pageprops-missing-page' ).parse();\n\t\t\t\t\treturn $.Deferred().reject( 'pageprops-missing-page', { errors: [ { html: message } ] } ).promise();\n\t\t\t\t}\n\n\t\t\t\t// FIXME: This just fetches the pageid and then hard-codes knowing that M+pageid is what we need\n\t\t\t\tthis.mediaInfoEntityId = 'M' + result.query.pages[ 0 ].pageid;\n\t\t\t\treturn this.mediaInfoEntityId;\n\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * Post wikitext as edited here, to the file\n\t\t *\n\t\t * This function is only called if all input seems valid (which doesn't mean that we can't get\n\t\t * an error, see #processError).\n\t\t *\n\t\t * @param {string} wikiText\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tsubmitWikiText: function ( wikiText ) {\n\t\t\tconst tags = [ 'uploadwizard' ],\n\t\t\t\tdeed = this.upload.deedChooser.deed,\n\t\t\t\tconfig = mw.UploadWizard.config;\n\t\t\tlet comment = '';\n\n\t\t\tthis.firstPoll = Date.now();\n\n\t\t\tif ( this.upload.file.source ) {\n\t\t\t\ttags.push( 'uploadwizard-' + this.upload.file.source );\n\t\t\t}\n\n\t\t\tif ( deed.name === 'ownwork' ) {\n\t\t\t\t// This message does not have any parameters, so there's nothing to substitute\n\t\t\t\tcomment = config.uploadComment.ownWork;\n\t\t\t} else {\n\t\t\t\tmw.messages.set(\n\t\t\t\t\t'mwe-upwiz-upload-comment-third-party',\n\t\t\t\t\tconfig.uploadComment.thirdParty\n\t\t\t\t);\n\t\t\t\tcomment = mw.message(\n\t\t\t\t\t'mwe-upwiz-upload-comment-third-party',\n\t\t\t\t\tdeed.getAuthorWikiText(),\n\t\t\t\t\tdeed.getSourceWikiText()\n\t\t\t\t).plain();\n\t\t\t}\n\n\t\t\tconst params = {\n\t\t\t\taction: 'upload',\n\t\t\t\tfilekey: this.upload.fileKey,\n\t\t\t\tfilename: this.getTitle().getMain(),\n\t\t\t\tcomment: comment,\n\t\t\t\ttags: config.CanAddTags ? tags : [],\n\t\t\t\t// we can ignore upload warnings here, we've already checked\n\t\t\t\t// when stashing the file\n\t\t\t\t// not ignoring warnings would prevent us from uploading a file\n\t\t\t\t// that is a duplicate of something in a foreign repo\n\t\t\t\tignorewarnings: true,\n\t\t\t\ttext: wikiText\n\t\t\t};\n\n\t\t\t// Only enable async publishing if file is larger than 10MiB\n\t\t\tif ( this.upload.transportWeight > 10 * 1024 * 1024 ) {\n\t\t\t\tparams.async = true;\n\t\t\t}\n\n\t\t\treturn this.submitWikiTextInternal( params );\n\t\t},\n\n\t\t/**\n\t\t * @param {string} entityId\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tsubmitStructuredData: function ( entityId ) {\n\t\t\tlet promise = $.Deferred().resolve().promise();\n\t\t\tconst config = mw.UploadWizard.config,\n\t\t\t\tdata = {},\n\t\t\t\twbDataModel = mw.loader.require( 'wikibase.datamodel' ),\n\t\t\t\twbSerialization = mw.loader.require( 'wikibase.serialization' ),\n\t\t\t\twbSerializer = new wbSerialization.StatementSerializer();\n\n\t\t\tconst labels = this.prepareLabelsData();\n\t\t\tconst statements = this.prepareStatementsData();\n\n\t\t\tif ( !config.wikibase.enabled ) {\n\t\t\t\treturn promise;\n\t\t\t}\n\n\t\t\tif ( config.wikibase.captions && Object.keys( labels ).length !== 0 ) {\n\t\t\t\tdata.labels = labels;\n\t\t\t}\n\n\t\t\tif ( config.wikibase.statements && statements.length !== 0 ) {\n\t\t\t\tdata.claims = statements;\n\t\t\t}\n\n\t\t\t// eslint-disable-next-line no-undef\n\t\t\tif ( this.dateProperty !== '' && dataValues ) {\n\t\t\t\tpromise = promise\n\t\t\t\t\t.then( () => this.dateDetails.parseDate() )\n\t\t\t\t\t.then(\n\t\t\t\t\t\t( response ) => {\n\t\t\t\t\t\t\tconst date = response.results[ 0 ].value;\n\t\t\t\t\t\t\tconst dateStatement = wbSerializer.serialize(\n\t\t\t\t\t\t\t\tnew wbDataModel.Statement(\n\t\t\t\t\t\t\t\t\tnew wbDataModel.Claim(\n\t\t\t\t\t\t\t\t\t\tnew wbDataModel.PropertyValueSnak(\n\t\t\t\t\t\t\t\t\t\t\tthis.dateProperty,\n\t\t\t\t\t\t\t\t\t\t\t// eslint-disable-next-line no-undef\n\t\t\t\t\t\t\t\t\t\t\tdataValues.TimeValue.newFromJSON( date )\n\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tdata.claims = data.claims ? data.claims.concat( dateStatement ) : [ dateStatement ];\n\t\t\t\t\t\t},\n\t\t\t\t\t\t( errorCode ) => {\n\t\t\t\t\t\t\tmw.log.warn(\n\t\t\t\t\t\t\t\t'uw.DateDetailsWidget::parseDate> ' +\n\t\t\t\t\t\t\t\t'Parsing into a Wikibase date failed. Reason: ' +\n\t\t\t\t\t\t\t\terrorCode\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t// Parsing failed: don't add the date statement,\n\t\t\t\t\t\t\t// but convert back to a resolved promise to keep\n\t\t\t\t\t\t\t// the chain going\n\t\t\t\t\t\t\treturn $.Deferred().resolve().promise();\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn promise.then(\n\t\t\t\t() => {\n\t\t\t\t\tif ( Object.keys( data ).length > 0 ) {\n\t\t\t\t\t\treturn this.postStructuredData( entityId, JSON.stringify( data ) );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t);\n\t\t},\n\n\t\tprepareLabelsData: function () {\n\t\t\tconst captions = this.captionsDetails.getValues(),\n\t\t\t\tlanguages = Object.keys( captions ),\n\t\t\t\tlabels = {};\n\t\t\tfor ( let i = 0; i < languages.length; i++ ) {\n\t\t\t\tlabels[ languages[ i ] ] = {\n\t\t\t\t\tlanguage: languages[ i ],\n\t\t\t\t\tvalue: captions[ languages[ i ] ]\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn labels;\n\t\t},\n\n\t\tprepareStatementsData: function () {\n\t\t\tlet claims = [];\n\t\t\tconst wikibaseSerialization = mw.loader.require( 'wikibase.serialization' ),\n\t\t\t\tstatementListSerializer = new wikibaseSerialization.StatementListSerializer(),\n\t\t\t\tdeed = this.upload.deedChooser.deed;\n\t\t\tfor ( const propertyId in this.statementWidgets ) {\n\t\t\t\tclaims = claims.concat(\n\t\t\t\t\tstatementListSerializer.serialize(\n\t\t\t\t\t\tthis.statementWidgets[ propertyId ].getStatementList()\n\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst sourceSD = deed.getStructuredDataFromSource();\n\t\t\tif ( sourceSD ) {\n\t\t\t\tclaims = claims.concat( sourceSD );\n\t\t\t}\n\t\t\treturn claims;\n\t\t},\n\n\t\t/**\n\t\t * @param {string} id\n\t\t * @param {string} data\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tpostStructuredData: function ( id, data ) {\n\t\t\tconst config = mw.UploadWizard.config,\n\t\t\t\tparams = {\n\t\t\t\t\taction: 'wbeditentity',\n\t\t\t\t\tid: id,\n\t\t\t\t\tdata: data,\n\t\t\t\t\t// baserevid is intentionally left blank: SD can be submitted\n\t\t\t\t\t// without baserevid just fine - baserevid is to prevent edit conflicts,\n\t\t\t\t\t// and this is a new upload so there should be none\n\t\t\t\t\tbaserevid: undefined\n\t\t\t\t},\n\t\t\t\tajaxOptions = { url: config.wikibase.api };\n\n\t\t\treturn this.upload.api.postWithEditToken(\n\t\t\t\tparams, ajaxOptions\n\t\t\t).catch( () => {\n\t\t\t\tthis.structuredDataSubmissionErrors = true;\n\t\t\t} );\n\t\t},\n\n\t\t/**\n\t\t * Perform the API call with given parameters (which is expected to publish this file) and\n\t\t * handle the result.\n\t\t *\n\t\t * @param {Object} params API call parameters\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tsubmitWikiTextInternal: function ( params ) {\n\t\t\tconst apiPromise = this.upload.api.postWithEditToken( params );\n\n\t\t\treturn apiPromise\n\t\t\t\t// process the successful (in terms of HTTP status...) API call first:\n\t\t\t\t// there may be warnings or other issues with the upload that need\n\t\t\t\t// to be dealt with\n\t\t\t\t.then( this.validateWikiTextSubmitResult.bind( this, params ) )\n\t\t\t\t// making it here means the upload is a success, or it would've been\n\t\t\t\t// rejected by now (either by HTTP status code, or in validateWikiTextSubmitResult)\n\t\t\t\t.then( ( result ) => {\n\t\t\t\t\tthis.title = mw.Title.makeTitle( 6, result.upload.filename );\n\t\t\t\t\tthis.upload.extractImageInfo( result.upload.imageinfo );\n\t\t\t\t\tthis.upload.thisProgress = 1.0;\n\t\t\t\t\tthis.upload.state = 'complete';\n\t\t\t\t\treturn result;\n\t\t\t\t} )\n\t\t\t\t// uh-oh - something went wrong!\n\t\t\t\t.catch( ( code, result ) => {\n\t\t\t\t\tthis.upload.state = 'error';\n\t\t\t\t\tthis.processError( code, result );\n\t\t\t\t\treturn $.Deferred().reject( code, result );\n\t\t\t\t} )\n\t\t\t\t.promise( { abort: apiPromise.abort } );\n\t\t},\n\n\t\t/**\n\t\t * Validates the result of a submission & returns a resolved promise with\n\t\t * the API response if all went well, or rejects with error code & error\n\t\t * message as you would expect from failed mediawiki API calls.\n\t\t *\n\t\t * @param {Object} params What we passed to the API that caused this response.\n\t\t * @param {Object} result API result of an upload or status check.\n\t\t * @return {jQuery.Promise}\n\t\t */\n\t\tvalidateWikiTextSubmitResult: function ( params, result ) {\n\t\t\tlet warnings = null;\n\t\t\tlet ignoreTheseWarnings = false;\n\t\t\tconst deferred = $.Deferred();\n\n\t\t\tif ( result && result.upload && result.upload.result === 'Poll' ) {\n\t\t\t\t// if async publishing takes longer than 10 minutes give up\n\t\t\t\tif ( ( Date.now() - this.firstPoll ) > 10 * 60 * 1000 ) {\n\t\t\t\t\treturn deferred.reject( 'server-error', { errors: [ {\n\t\t\t\t\t\tcode: 'server-error',\n\t\t\t\t\t\thtml: 'Unknown server error'\n\t\t\t\t\t} ] } );\n\t\t\t\t} else {\n\t\t\t\t\tif ( result.upload.stage === undefined ) {\n\t\t\t\t\t\treturn deferred.reject( 'no-stage', { errors: [ {\n\t\t\t\t\t\t\tcode: 'no-stage',\n\t\t\t\t\t\t\thtml: 'Unable to check file\\'s status'\n\t\t\t\t\t\t} ] } );\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Messages that can be returned:\n\t\t\t\t\t\t// * mwe-upwiz-queued\n\t\t\t\t\t\t// * mwe-upwiz-publish\n\t\t\t\t\t\t// * mwe-upwiz-assembling\n\t\t\t\t\t\tthis.setStatus( mw.message( 'mwe-upwiz-' + result.upload.stage ).text() );\n\t\t\t\t\t\tsetTimeout( () => {\n\t\t\t\t\t\t\tif ( this.upload.state !== 'aborted' ) {\n\t\t\t\t\t\t\t\tthis.submitWikiTextInternal( {\n\t\t\t\t\t\t\t\t\taction: 'upload',\n\t\t\t\t\t\t\t\t\tcheckstatus: true,\n\t\t\t\t\t\t\t\t\tfilekey: this.upload.fileKey\n\t\t\t\t\t\t\t\t} ).then( deferred.resolve, deferred.reject );\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdeferred.resolve( 'aborted' );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}, 3000 );\n\n\t\t\t\t\t\treturn deferred.promise();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ( result && result.upload && result.upload.warnings ) {\n\t\t\t\twarnings = result.upload.warnings;\n\t\t\t}\n\t\t\tlet existingFile;\n\t\t\tif ( warnings && warnings.exists ) {\n\t\t\t\texistingFile = warnings.exists;\n\t\t\t} else if ( warnings && warnings[ 'exists-normalized' ] ) {\n\t\t\t\texistingFile = warnings[ 'exists-normalized' ];\n\t\t\t\tconst existingFileExt = mw.Title.normalizeExtension( existingFile.split( '.' ).pop() );\n\t\t\t\tconst ourFileExt = mw.Title.normalizeExtension( this.getTitle().getExtension() );\n\n\t\t\t\tif ( existingFileExt !== ourFileExt ) {\n\t\t\t\t\tdelete warnings[ 'exists-normalized' ];\n\t\t\t\t\tignoreTheseWarnings = true;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ( warnings && warnings[ 'was-deleted' ] ) {\n\t\t\t\tdelete warnings[ 'was-deleted' ];\n\t\t\t\tignoreTheseWarnings = true;\n\t\t\t}\n\t\t\tfor ( const wx in warnings ) {\n\t\t\t\tif ( Object.prototype.hasOwnProperty.call( warnings, wx ) ) {\n\t\t\t\t\t// if there are other warnings, deal with those first\n\t\t\t\t\tignoreTheseWarnings = false;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif ( result && result.upload && result.upload.imageinfo ) {\n\t\t\t\treturn $.Deferred().resolve( result );\n\t\t\t} else if ( ignoreTheseWarnings ) {\n\t\t\t\tparams.ignorewarnings = 1;\n\t\t\t\treturn this.submitWikiTextInternal( params );\n\t\t\t} else if ( result && result.upload && result.upload.warnings ) {\n\t\t\t\tlet code, message;\n\t\t\t\tif ( warnings.thumb || warnings[ 'thumb-name' ] ) {\n\t\t\t\t\tcode = 'error-title-thumbnail';\n\t\t\t\t\tmessage = mw.message( 'mwe-upwiz-error-title-thumbnail' ).parse();\n\t\t\t\t} else if ( warnings.badfilename ) {\n\t\t\t\t\tcode = 'title-invalid';\n\t\t\t\t\tmessage = mw.message( 'mwe-upwiz-error-title-invalid' ).parse();\n\t\t\t\t} else if ( warnings[ 'bad-prefix' ] ) {\n\t\t\t\t\tcode = 'title-senselessimagename';\n\t\t\t\t\tmessage = mw.message( 'mwe-upwiz-error-title-senselessimagename' ).parse();\n\t\t\t\t} else if ( existingFile ) {\n\t\t\t\t\tconst existingFileUrl = mw.config.get( 'wgServer' ) + mw.Title.makeTitle( NS_FILE, existingFile ).getUrl();\n\t\t\t\t\tcode = 'api-warning-exists';\n\t\t\t\t\tmessage = mw.message( 'mwe-upwiz-api-warning-exists', existingFileUrl ).parse();\n\t\t\t\t} else if ( warnings.duplicate ) {\n\t\t\t\t\tcode = 'upload-error-duplicate';\n\t\t\t\t\tmessage = mw.message( 'mwe-upwiz-upload-error-duplicate' ).parse();\n\t\t\t\t} else if ( warnings[ 'duplicate-archive' ] !== undefined ) {\n\t\t\t\t\t// warnings[ 'duplicate-archive' ] may be '' (empty string) for revdeleted files\n\t\t\t\t\tif ( this.upload.handler.isIgnoredWarning( 'duplicate-archive' ) ) {\n\t\t\t\t\t\t// We already told the interface to ignore this warning, so\n\t\t\t\t\t\t// let's steamroll over it and re-call this handler.\n\t\t\t\t\t\tparams.ignorewarnings = true;\n\t\t\t\t\t\treturn this.submitWikiTextInternal( params );\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// This should _never_ happen, but just in case....\n\t\t\t\t\t\tcode = 'upload-error-duplicate-archive';\n\t\t\t\t\t\tmessage = mw.message( 'mwe-upwiz-upload-error-duplicate-archive' ).parse();\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst warningsKeys = Object.keys( warnings );\n\t\t\t\t\tcode = 'unknown-warning';\n\t\t\t\t\tmessage = mw.message( 'api-error-unknown-warning', warningsKeys.join( ', ' ) ).parse();\n\t\t\t\t}\n\n\t\t\t\treturn $.Deferred().reject( code, { errors: [ { html: message } ] } );\n\t\t\t} else {\n\t\t\t\treturn $.Deferred().reject( 'this-info-missing', result );\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Create a recoverable error -- show the form again, and highlight the problematic field.\n\t\t *\n\t\t * @param {string} code\n\t\t * @param {string} html Error message to show.\n\t\t */\n\t\trecoverFromError: function ( code, html ) {\n\t\t\tthis.upload.state = 'recoverable-error';\n\t\t\tthis.$dataDiv.morphCrossfade( '.detailsForm' );\n\t\t\tthis.titleDetailsField.setErrors( [ { code: code, html: html } ] );\n\t\t},\n\n\t\t/**\n\t\t * Show error state, possibly using a recoverable error form\n\t\t *\n\t\t * @param {string} code Error code\n\t\t * @param {string} html Error message\n\t\t */\n\t\tshowError: function ( code, html ) {\n\t\t\tthis.showIndicator( 'error' );\n\t\t\tthis.setStatus( html );\n\t\t},\n\n\t\t/**\n\t\t * Decide how to treat various errors\n\t\t *\n\t\t * @param {string} code Error code\n\t\t * @param {Object} result Result from ajax call\n\t\t */\n\t\tprocessError: function ( code, result ) {\n\t\t\tconst recoverable = [\n\t\t\t\t'abusefilter-disallowed',\n\t\t\t\t'abusefilter-warning',\n\t\t\t\t'spamblacklist',\n\t\t\t\t'fileexists-shared-forbidden',\n\t\t\t\t'protectedpage',\n\t\t\t\t'titleblacklist-forbidden',\n\n\t\t\t\t// below are not actual API errors, but recoverable warnings that have\n\t\t\t\t// been discovered in validateWikiTextSubmitResult and fabricated to resemble\n\t\t\t\t// API errors and end up here to be dealt with\n\t\t\t\t'error-title-thumbnail',\n\t\t\t\t'title-invalid',\n\t\t\t\t'title-senselessimagename',\n\t\t\t\t'api-warning-exists',\n\t\t\t\t'upload-error-duplicate',\n\t\t\t\t'upload-error-duplicate',\n\t\t\t\t'upload-error-duplicate-archive',\n\t\t\t\t'unknown-warning'\n\t\t\t];\n\n\t\t\tif ( code === 'badtoken' ) {\n\t\t\t\tthis.api.badToken( 'csrf' );\n\t\t\t\t// TODO Automatically try again instead of requiring the user to bonk the button\n\t\t\t}\n\n\t\t\tif ( code === 'ratelimited' ) {\n\t\t\t\t// None of the remaining uploads is going to succeed, and every failed one is going to\n\t\t\t\t// ping the rate limiter again.\n\t\t\t\tthis.upload.wizard.steps.details.queue.abortExecuting();\n\t\t\t} else if ( code === 'http' && result && result.exception === 'abort' ) {\n\t\t\t\t// This upload has just been aborted because an earlier one got the 'ratelimited' error.\n\t\t\t\t// This could potentially also come up when an upload is removed by the user, but in that\n\t\t\t\t// case the UI is invisible anyway, so whatever.\n\t\t\t\tcode = 'ratelimited';\n\t\t\t}\n\n\t\t\tif ( recoverable.includes( code ) ) {\n\t\t\t\tthis.recoverFromError( code, result.errors[ 0 ].html );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.showError( code, result.errors[ 0 ].html );\n\t\t},\n\n\t\tsetStatus: function ( s ) {\n\t\t\tthis.$div.find( '.mwe-upwiz-file-status-line' ).html( s ).show();\n\t\t},\n\n\t\t// TODO: De-duplicate with code form mw.UploadWizardUploadInterface.js\n\t\tshowIndicator: function ( status ) {\n\t\t\tconst progress = status === 'progress';\n\t\t\tthis.$spinner.toggle( progress );\n\t\t\tthis.statusMessage.toggle( status && !progress ).setType( status );\n\t\t\tthis.$indicator.toggleClass( 'mwe-upwiz-file-indicator-visible', !!status );\n\t\t},\n\n\t\tsetVisibleTitle: function ( s ) {\n\t\t\t$( this.$submittingDiv )\n\t\t\t\t.find( '.mwe-upwiz-visible-file-filename-text' )\n\t\t\t\t.text( s );\n\t\t}\n\t};\n\tOO.mixinClass( mw.UploadWizardDetails, OO.EventEmitter );\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.UploadWizardLicenseInput.js","messages":[{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":50,"column":20,"nodeType":"CallExpression","endLine":50,"endColumn":98},{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":61,"column":41,"nodeType":"ObjectExpression","endLine":67,"endColumn":6},{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":69,"column":49,"nodeType":"ObjectExpression","endLine":75,"endColumn":6},{"ruleId":"es-x/no-iterator-prototype-reduce","severity":1,"message":"ES2025 'Iterator.prototype.reduce' method is forbidden.","line":123,"column":24,"nodeType":"MemberExpression","messageId":"forbidden","endLine":123,"endColumn":62},{"ruleId":"es-x/no-iterator-prototype-reduce","severity":1,"message":"ES2025 'Iterator.prototype.reduce' method is forbidden.","line":287,"column":28,"nodeType":"MemberExpression","messageId":"forbidden","endLine":288,"endColumn":12}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Create a group of radio buttons for licenses. N.B. the licenses are named after the templates they invoke.\n * Note that this is very anti-MVC. The values are held only in the actual form elements themselves.\n *\n * @class\n * @extends OO.ui.Widget\n * @param {Object} config Configuration. Must have following properties:\n * @param {string} config.type Whether inclusive or exclusive license allowed (\"and\"|\"or\")\n * @param {string[]} config.licenses Template string names (matching keys in mw.UploadWizard.config.licenses)\n * @param {Object[]} [config.licenseGroups] Groups of licenses, with more explanation\n * @param {string} [config.special] Indicates, don't put licenses here, instead use a special widget\n * @param {number} count Number of the things we are licensing (it matters to some texts)\n * @param {mw.Api} api API object, used for wikitext previews\n */\nmw.UploadWizardLicenseInput = function ( config, count, api ) {\n\tmw.UploadWizardLicenseInput.super.call( this );\n\tOO.ui.mixin.GroupElement.call( this );\n\n\tthis.count = count;\n\tthis.api = api;\n\n\tif (\n\t\tconfig.type === undefined ||\n\t\t( config.licenses === undefined && config.licenseGroups === undefined )\n\t) {\n\t\tthrow new Error( 'improper initialization' );\n\t}\n\n\tthis.type = config.type === 'or' ? 'radio' : 'checkbox';\n\n\tthis.defaults = [];\n\tif ( config.defaults ) {\n\t\tthis.defaults = config.defaults instanceof Array ? config.defaults : [ config.defaults ];\n\t}\n\n\t// create inputs and licenses from config\n\tif ( config.licenseGroups === undefined ) {\n\t\tconst group = new mw.uploadWizard.LicenseGroup( config, this.type, this.api, this.count );\n\t\tconst groupField = new mw.uploadWizard.FieldLayout( group, {} );\n\t\tgroup.connect( groupField, { change: [ 'emit', 'change' ] } );\n\n\t\tthis.addItems( [ groupField ] );\n\t\tthis.$element.append( this.$group );\n\t} else {\n\t\tconst options = config.licenseGroups.map( ( groupConfig ) => {\n\t\t\tconst classes = [ 'mwe-upwiz-deed-license-group-head', 'mwe-upwiz-deed-license-group-' + groupConfig.head ];\n\n\t\t\tconst $icons = $( '<span>' );\n\t\t\t( groupConfig.icons || [] ).forEach( ( icon ) => {\n\t\t\t\t$icons.append( $( '<span>' ).addClass( 'mwe-upwiz-license-icon mwe-upwiz-' + icon + '-icon' ) );\n\t\t\t} );\n\n\t\t\t// 'url' can be either a single (string) url, or an array of (string) urls;\n\t\t\t// hence this convoluted variable-length parameters assembly...\n\t\t\tconst labelParams = [ groupConfig.head, this.count ].concat( groupConfig.url ).concat( $icons );\n\t\t\tconst label = groupConfig.head && mw.message.apply( mw.message, labelParams ).parse() || '';\n\t\t\tconst labelExtraParams = [ groupConfig[ 'head-extra' ], this.count ].concat( groupConfig.url ).concat( $icons );\n\t\t\tconst labelExtra = groupConfig[ 'head-extra' ] && mw.message.apply( mw.message, labelExtraParams ).parse() || '';\n\n\t\t\tif ( this.type === 'radio' ) {\n\t\t\t\treturn new OO.ui.RadioOptionWidget( {\n\t\t\t\t\tlabel: $( '<span>' )\n\t\t\t\t\t\t.append( label )\n\t\t\t\t\t\t.append( $( '<span>' ).addClass( 'mwe-upwiz-label-extra' ).append( labelExtra ) )\n\t\t\t\t\t\t.contents(),\n\t\t\t\t\tclasses: classes\n\t\t\t\t} );\n\t\t\t} else { // if ( this.type === 'checkbox' ) {\n\t\t\t\treturn new OO.ui.CheckboxMultioptionWidget( {\n\t\t\t\t\tlabel: $( '<span>' )\n\t\t\t\t\t\t.append( label )\n\t\t\t\t\t\t.append( $( '<span>' ).addClass( 'mwe-upwiz-label-extra' ).append( labelExtra ) )\n\t\t\t\t\t\t.contents(),\n\t\t\t\t\tclasses: classes\n\t\t\t\t} );\n\t\t\t}\n\t\t} );\n\t\tthis.widget = this.type === 'radio' ? new OO.ui.RadioSelectWidget() : new OO.ui.CheckboxMultiselectWidget();\n\t\tthis.widget.addItems( options );\n\n\t\tconst groupFields = config.licenseGroups.map( ( groupConfig ) => {\n\t\t\tconst group = new mw.uploadWizard.LicenseGroup(\n\t\t\t\tgroupConfig,\n\t\t\t\t// group config can override overall type; e.g. a single group can be \"and\", while\n\t\t\t\t// the rest of the config can be \"or\"\n\t\t\t\t( groupConfig.type || config.type ) === 'or' ? 'radio' : 'checkbox',\n\t\t\t\tthis.api,\n\t\t\t\tthis.count\n\t\t\t);\n\n\t\t\tconst groupField = new mw.uploadWizard.FieldLayout( group, {} );\n\t\t\tgroupField.$element.addClass( 'mwe-upwiz-deed-subgroup' );\n\t\t\tgroup.connect( groupField, { change: [ 'emit', 'change' ] } );\n\n\t\t\treturn groupField;\n\t\t} );\n\t\tthis.addItems( groupFields );\n\n\t\t// link option to the groups they're associated with, so we can easily move from\n\t\t// one to the other when they need to be interacted with\n\t\tgroupFields.forEach( ( groupField, i ) => {\n\t\t\tgroupField.option = options[ i ];\n\t\t} );\n\n\t\tthis.$element.append(\n\t\t\t$( '<div>' )\n\t\t\t\t.addClass( 'mwe-upwiz-deed-license-group-container' )\n\t\t\t\t.append( this.widget.$group )\n\t\t);\n\n\t\tthis.widget.on( 'select', ( selectedOption, isSelected ) => {\n\t\t\tthis.emit( 'change' );\n\n\t\t\t// radios don't have a second 'selected' arg; they're always true\n\t\t\tisSelected = isSelected || true;\n\n\t\t\t// radio groups won't fire events for group that got deselected\n\t\t\t// (as a results of a new one being selected), so we'll iterate\n\t\t\t// all groups to remove no-longer-active ones\n\t\t\tthis.getItems().forEach( ( groupField ) => {\n\t\t\t\tconst group = groupField.fieldWidget,\n\t\t\t\t\toption = groupField.option,\n\t\t\t\t\tdefaultLicenses = ( group.config.defaults || [] ).reduce( ( defaults, license ) => {\n\t\t\t\t\t\tdefaults[ license ] = true;\n\t\t\t\t\t\treturn defaults;\n\t\t\t\t\t}, {} );\n\n\t\t\t\tif ( !option.isSelected() ) {\n\t\t\t\t\t// collapse & nix any inputs that may have been selected in groups that\n\t\t\t\t\t// are no longer active/selected\n\t\t\t\t\tgroupField.$element.detach();\n\t\t\t\t\tgroup.setValue( {} );\n\t\t\t\t} else {\n\t\t\t\t\t// attach group license selector\n\t\t\t\t\toption.$label.append( groupField.$element );\n\n\t\t\t\t\t// check the defaults (insofar they exist) for newly selected groups;\n\t\t\t\t\t// ignore groups that had already been selected to ensure existing\n\t\t\t\t\t// user input is not tampered with\n\t\t\t\t\tif (\n\t\t\t\t\t\tisSelected &&\n\t\t\t\t\t\toption === selectedOption &&\n\t\t\t\t\t\tObject.keys( group.getValue() ).length <= 0\n\t\t\t\t\t) {\n\t\t\t\t\t\tgroup.setValue( defaultLicenses );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} );\n\t\t} );\n\t}\n\n\tthis.aggregate( { change: 'change' } );\n\n};\nOO.inheritClass( mw.UploadWizardLicenseInput, OO.ui.Widget );\nOO.mixinClass( mw.UploadWizardLicenseInput, OO.ui.mixin.GroupElement );\nOO.mixinClass( mw.UploadWizardLicenseInput, mw.uploadWizard.ValidatableElement );\n\nObject.assign( mw.UploadWizardLicenseInput.prototype, {\n\tunload: function () {\n\t\tthis.getItems().forEach( ( groupField ) => {\n\t\t\tgroupField.fieldWidget.unload();\n\t\t} );\n\t},\n\n\t/**\n\t * Sets the value(s) of a license input. This is a little bit klugey because it relies on an inverted dict, and in some\n\t * cases we are now letting license inputs create multiple templates.\n\t *\n\t * @memberof mw.UploadWizardLicenseInput\n\t * @param {Object} values License-key to boolean values, e.g. { 'cc_by_sa_30': true, gfdl: true, 'flickrreview|cc_by_sa_30': false }\n\t * @param {string} [groupName] Name of group, when values are only relevant to this group\n\t */\n\tsetValues: function ( values, groupName ) {\n\t\tconst selectedGroups = [];\n\n\t\tthis.getItems().forEach( ( groupField ) => {\n\t\t\tconst group = groupField.fieldWidget;\n\t\t\tif ( groupName === undefined || group.getGroup() === groupName ) {\n\t\t\t\tgroup.setValue( values );\n\t\t\t\tif ( Object.keys( group.getValue() ).length > 0 ) {\n\t\t\t\t\tselectedGroups.push( group );\n\t\t\t\t}\n\t\t\t} else if ( this.type === 'radio' ) {\n\t\t\t\t// when we're dealing with radio buttons and there are changes in another\n\t\t\t\t// group, then we'll need to clear out this group...\n\t\t\t\tgroup.setValue( {} );\n\t\t\t}\n\t\t} );\n\n\t\tif ( selectedGroups.length > 1 && this.type === 'radio' ) {\n\t\t\t// leave the last one alone - that one can remain selected\n\t\t\tselectedGroups.pop();\n\n\t\t\t// if we've selected things in multiple groups (= when the group was not defined,\n\t\t\t// which is basically only when dealing with defaults, from config or user\n\t\t\t// preferences), we need to make sure we're left with only 1 selected radio in\n\t\t\t// 1 group\n\t\t\t// in that case, we're only going to select the *last* occurrence, which is what\n\t\t\t// a browser would do when trying to find/select a radio that occurs twice\n\t\t\tselectedGroups.forEach( ( group ) => {\n\t\t\t\tgroup.setValue( {} );\n\t\t\t} );\n\t\t}\n\n\t\t// in the case of multiple option groups (with a parent radio/check to expand/collapse),\n\t\t// we need to make sure the parent option and expanded state match the state of the\n\t\t// group - when the group has things that are selected, it must be active\n\t\tthis.getItems().forEach( ( groupField ) => {\n\t\t\tconst group = groupField.fieldWidget,\n\t\t\t\toption = groupField.option,\n\t\t\t\tselected = Object.keys( group.getValue() ).length > 0;\n\n\t\t\tif ( !option ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\toption.setSelected( selected );\n\t\t\tif ( selected ) {\n\t\t\t\toption.$element.append( groupField.$element );\n\t\t\t\t// there's an event listener bound to respond to changes when an option\n\t\t\t\t// is selected, but that in only triggered by manual (user) selection;\n\t\t\t\t// we're programmatically updating values here, and need to make sure\n\t\t\t\t// it also responds to these\n\t\t\t\tthis.widget.emit( 'select', option, true );\n\t\t\t} else {\n\t\t\t\tgroupField.$element.detach();\n\t\t\t}\n\t\t} );\n\t},\n\n\t/**\n\t * Set the default configured licenses\n\t *\n\t * @memberof mw.UploadWizardLicenseInput\n\t */\n\tsetDefaultValues: function () {\n\t\tconst values = {};\n\t\tthis.defaults.forEach( ( license ) => {\n\t\t\tvalues[ license ] = true;\n\t\t} );\n\t\tthis.setValues( values );\n\t},\n\n\t/**\n\t * Gets the selected license(s). The returned value will be a license\n\t * key => license props map, as defined in UploadWizard.config.php.\n\t *\n\t * @memberof mw.UploadWizardLicenseInput\n\t * @return {Object}\n\t */\n\tgetLicenses: function () {\n\t\tconst licenses = {};\n\n\t\tthis.getItems().forEach( ( groupField ) => {\n\t\t\tconst group = groupField.fieldWidget,\n\t\t\t\tlicenseNames = Object.keys( group.getValue() );\n\n\t\t\tlicenseNames.forEach( ( name ) => {\n\t\t\t\tlicenses[ name ] = mw.UploadWizard.config.licenses[ name ] || {};\n\t\t\t} );\n\t\t} );\n\n\t\treturn licenses;\n\t},\n\n\t/**\n\t * Gets the wikitext associated with all selected inputs. Some inputs also have associated textareas so we append their contents too.\n\t *\n\t * @memberof mw.UploadWizardLicenseInput\n\t * @return {string} of wikitext (empty string if no inputs set)\n\t */\n\tgetWikiText: function () {\n\t\treturn this.getItems().map( ( groupField ) => groupField.fieldWidget.getWikiText() ).join( '' ).trim();\n\t},\n\n\t/**\n\t * See mw.uploadWizard.DetailsWidget\n\t *\n\t * @memberof mw.UploadWizardLicenseInput\n\t * @param {boolean} thorough\n\t * @return {jQuery.Promise<mw.uploadWizard.ValidationStatus>}\n\t */\n\tvalidate: function ( thorough ) {\n\t\t// Gather errors from each item\n\t\tconst status = new mw.uploadWizard.ValidationStatus(),\n\t\t\tselectedGroupPromises = this.getItems()\n\t\t\t\t.reduce( ( promises, groupField ) => {\n\t\t\t\t\t// validate all fields; allowing previously invalid subgroups to be re-validated\n\t\t\t\t\t// (e.g. clearing out errors), even when they're not selected\n\t\t\t\t\tconst promise = groupField.validate( thorough );\n\t\t\t\t\t// but only use the validation result of the selected groups (or the only available\n\t\t\t\t\t// group if there is no choice)\n\t\t\t\t\tif ( groupField.option === undefined || groupField.option.isSelected() ) {\n\t\t\t\t\t\tpromises.push( promise );\n\t\t\t\t\t}\n\t\t\t\t\treturn promises;\n\t\t\t\t}, [] );\n\n\t\tif ( thorough !== true ) {\n\t\t\t// `thorough` is the strict checks executed on submit, but we don't want errors\n\t\t\t// to change/display every change event\n\t\t\treturn status.resolve();\n\t\t}\n\n\t\tif ( selectedGroupPromises.length === 0 ) {\n\t\t\tstatus.addError( mw.message( 'mwe-upwiz-deeds-require-selection' ) );\n\t\t}\n\n\t\treturn mw.uploadWizard.ValidationStatus.mergePromises( ...selectedGroupPromises ).then(\n\t\t\t// license groups are valid\n\t\t\t() => status.getErrors().length === 0 ? status.resolve() : status.reject(),\n\t\t\t// there was an error in one of the license groups; we'll still want\n\t\t\t// to reject, but those child messages need not be added into this status\n\t\t\t// object, since they'll already be displayed within those child widgets\n\t\t\t() => status.reject()\n\t\t);\n\t},\n\n\t/**\n\t * @memberof mw.UploadWizardLicenseInput\n\t * @return {Object}\n\t */\n\tgetSerialized: function () {\n\t\tconst values = {};\n\n\t\tthis.getItems().forEach( ( groupField ) => {\n\t\t\tconst group = groupField.fieldWidget;\n\t\t\tconst groupName = group.getGroup();\n\t\t\tconst value = group.getValue();\n\n\t\t\tif ( Object.keys( value ).length > 0 ) {\n\t\t\t\t// $.extend just in case there are multiple groups with the same name...\n\t\t\t\tvalues[ groupName ] = Object.assign( {}, values[ groupName ] || {}, value );\n\t\t\t}\n\t\t} );\n\n\t\treturn values;\n\t},\n\n\t/**\n\t * @memberof mw.UploadWizardLicenseInput\n\t * @param {Object} serialized\n\t */\n\tsetSerialized: function ( serialized ) {\n\t\tObject.keys( serialized ).forEach( ( groupName ) => {\n\t\t\tthis.setValues( serialized[ groupName ], groupName );\n\t\t} );\n\t}\n\n} );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.UploadWizardPage.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":32,"column":3,"nodeType":"CallExpression","endLine":32,"endColumn":28,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":35,"column":8,"nodeType":"CallExpression","endLine":35,"endColumn":29,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":43,"column":4,"nodeType":"CallExpression","endLine":43,"endColumn":33,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.UploadWizardUpload.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.controller.Step' is undefined.","line":24,"column":1,"nodeType":"Block","endLine":24,"endColumn":1},{"ruleId":"es-x/no-typed-arrays","severity":1,"message":"ES2015 'Uint8Array' is forbidden.","line":224,"column":16,"nodeType":"Identifier","messageId":"forbidden","endLine":224,"endColumn":26},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":380,"column":3,"nodeType":"CallExpression","endLine":380,"endColumn":36},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":380,"column":3,"nodeType":"CallExpression","endLine":380,"endColumn":48},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":445,"column":3,"nodeType":"CallExpression","endLine":445,"endColumn":36},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":445,"column":3,"nodeType":"CallExpression","endLine":445,"endColumn":48},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":766,"column":3,"nodeType":"CallExpression","endLine":768,"endColumn":26},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":766,"column":3,"nodeType":"CallExpression","endLine":780,"endColumn":7},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":774,"column":6,"nodeType":"CallExpression","endLine":774,"endColumn":65},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":777,"column":7,"nodeType":"CallExpression","endLine":777,"endColumn":66}],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":2,"message":"'uw' is defined but never used.","line":11,"column":14,"nodeType":"Identifier","messageId":"unusedVar","endLine":11,"endColumn":16,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-underscore-dangle","severity":2,"message":"Unexpected dangling '_' in '_binary_data'.","line":234,"column":6,"nodeType":"MemberExpression","messageId":"unexpectedUnderscore","endLine":234,"endColumn":23,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier '_binary_data' is not in camel case.","line":234,"column":11,"nodeType":"Identifier","messageId":"notCamelCase","endLine":234,"endColumn":23,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":10,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/**\n * Represents the upload -- in its local and remote state. (Possibly those could be separate objects too...)\n * This is our 'model' object if we are thinking MVC. Needs to be better factored, lots of feature envy with the UploadWizard\n * states:\n * 'new' 'transporting' 'transported' 'metadata' 'stashed' 'details' 'submitting-details' 'complete' 'error'\n * should fork this into two -- local and remote, e.g. filename\n *\n * @param uw\n */\n// eslint-disable-next-line no-unused-vars\n( function ( uw ) {\n\t/**\n\t * Constructor for objects representing uploads. The workhorse of this entire extension.\n\t *\n\t * The upload knows nothing of other uploads. It manages its own interface, and transporting its own data, to\n\t * the server.\n\t *\n\t * Upload objects are usually created without a file, they are just associated with a form.\n\t * There is an \"empty\" fileInput which is invisibly floating above certain buttons in the interface, like \"Add a file\". When\n\t * this fileInput gets a file, this upload becomes 'filled'.\n\t *\n\t * @class\n\t * @mixes OO.EventEmitter\n\t * @param {uw.controller.Step} controller\n\t * @param {File} file\n\t */\n\tmw.UploadWizardUpload = function MWUploadWizardUpload( controller, file ) {\n\t\tOO.EventEmitter.call( this );\n\n\t\tthis.index = mw.UploadWizardUpload.prototype.count;\n\t\tmw.UploadWizardUpload.prototype.count++;\n\n\t\tthis.controller = controller;\n\t\tthis.api = controller.api;\n\t\tthis.file = file;\n\t\tthis.state = 'new';\n\t\tthis.imageinfo = {};\n\t\tthis.title = undefined;\n\t\tthis.thumbnailPromise = {};\n\n\t\tthis.fileKey = undefined;\n\n\t\t// this should be moved to the interface, if we even keep this\n\t\tthis.transportWeight = 1; // default all same\n\n\t\t// details\n\t\tthis.ui = new mw.UploadWizardUploadInterface( this )\n\t\t\t.connect( this, {\n\t\t\t\t/*\n\t\t\t\t * This may be confusing!\n\t\t\t\t * This object also has a `remove` method, which will also be\n\t\t\t\t * called when an upload is removed. But an upload can be\n\t\t\t\t * removed for multiple reasons (one being clicking the \"remove\"\n\t\t\t\t * button, which triggers this event - but another could be\n\t\t\t\t * removing faulty uploads).\n\t\t\t\t * To simplify things, we'll always initiate the remove from the\n\t\t\t\t * controllers, so we'll relay this event to the controllers,\n\t\t\t\t * which will then eventually come back to call `remove` on this\n\t\t\t\t * object.\n\t\t\t\t */\n\t\t\t\t'upload-removed': [ 'emit', 'remove-upload' ]\n\t\t\t} );\n\n\t\tif ( file.licenseName ) {\n\t\t\tthis.ui.setLicenseText( file.licenseName );\n\t\t}\n\t};\n\n\tOO.mixinClass( mw.UploadWizardUpload, OO.EventEmitter );\n\n\t// Upload handler\n\tmw.UploadWizardUpload.prototype.uploadHandler = null;\n\n\t// increments with each upload\n\tmw.UploadWizardUpload.prototype.count = 0;\n\n\t/**\n\t * start\n\t *\n\t * @return {jQuery.Promise}\n\t */\n\tmw.UploadWizardUpload.prototype.start = function () {\n\t\tthis.setTransportProgress( 0.0 );\n\n\t\t// handler -- usually ApiUploadFormDataHandler\n\t\tthis.handler = this.getUploadHandler();\n\t\treturn this.handler.start();\n\t};\n\n\t/**\n\t * Remove this upload. n.b. we trigger a removeUpload this is usually triggered from\n\t */\n\tmw.UploadWizardUpload.prototype.remove = function () {\n\t\t// remove the div that passed along the trigger\n\t\tthis.ui.$div.remove();\n\n\t\tthis.state = 'aborted';\n\t};\n\n\t/**\n\t * Wear our current progress, for observing processes to see\n\t *\n\t * @param {number} fraction\n\t */\n\tmw.UploadWizardUpload.prototype.setTransportProgress = function ( fraction ) {\n\t\tif ( this.state === 'aborted' ) {\n\t\t\t// We shouldn't be transporting anything anymore.\n\t\t\treturn;\n\t\t}\n\t\tthis.state = 'transporting';\n\t\tthis.transportProgress = fraction;\n\t\tthis.ui.$div.trigger( 'transportProgressEvent' );\n\t};\n\n\t/**\n\t * Stop the upload -- we have failed for some reason\n\t *\n\t * @param {string} code Error code from API\n\t * @param {string} html Error message\n\t * @param {jQuery} [$additionalStatus]\n\t */\n\tmw.UploadWizardUpload.prototype.setError = function ( code, html, $additionalStatus ) {\n\t\tif ( this.state === 'aborted' ) {\n\t\t\t// There's no point in reporting an error anymore.\n\t\t\treturn;\n\t\t}\n\t\tthis.state = 'error';\n\t\tthis.transportProgress = 0;\n\t\tthis.ui.showError( code, html, $additionalStatus );\n\t};\n\n\t/**\n\t * Called from any upload success condition\n\t *\n\t * @param {Object} result -- result of AJAX call\n\t */\n\tmw.UploadWizardUpload.prototype.setSuccess = function ( result ) {\n\t\tthis.state = 'transported';\n\t\tthis.transportProgress = 1;\n\n\t\tthis.ui.setStatus( 'mwe-upwiz-getting-metadata' );\n\n\t\tthis.extractUploadInfo( result.upload );\n\t\tthis.state = 'stashed';\n\t\tthis.ui.showStashed();\n\n\t\tthis.emit( 'success' );\n\t\t// check all uploads, if they're complete, show the next button\n\t\t// TODO Make wizard connect to 'success' event\n\t\tthis.controller.showNext();\n\t};\n\n\t/**\n\t * Get just the filename.\n\t *\n\t * @return {string}\n\t */\n\tmw.UploadWizardUpload.prototype.getFilename = function () {\n\t\tif ( this.file.fileName ) {\n\t\t\treturn this.file.fileName;\n\t\t} else {\n\t\t\t// this property has a different name in FF vs Chrome.\n\t\t\treturn this.file.name;\n\t\t}\n\t};\n\n\t/**\n\t * Get the basename of a path.\n\t * For error conditions, returns the empty string.\n\t *\n\t * @return {string} basename\n\t */\n\tmw.UploadWizardUpload.prototype.getBasename = function () {\n\t\tconst path = this.getFilename();\n\n\t\tif ( path === undefined || path === null ) {\n\t\t\treturn '';\n\t\t}\n\n\t\t// find index of last path separator in the path, add 1. (If no separator found, yields 0)\n\t\t// then take the entire string after that.\n\t\treturn path.slice( Math.max( path.lastIndexOf( '/' ), path.lastIndexOf( '\\\\' ) ) + 1 );\n\t};\n\n\t/**\n\t * Sanitize and set the title of the upload.\n\t *\n\t * @param {string} title Unsanitized title.\n\t */\n\tmw.UploadWizardUpload.prototype.setTitle = function ( title ) {\n\t\tthis.title = mw.Title.newFromFileName( title );\n\t};\n\n\t/**\n\t * Extract some JPEG metadata that we need to render thumbnails (EXIF rotation mostly).\n\t *\n\t * For JPEGs, we use the JsJpegMeta library in core to extract metadata,\n\t * including EXIF tags. This is done asynchronously once each file has been\n\t * read.\n\t *\n\t * For all other file types, we don't need or want to run this, and this function does nothing.\n\t *\n\t * @private\n\t * @return {jQuery.Promise} A promise, resolved when we're done\n\t */\n\tmw.UploadWizardUpload.prototype.extractMetadataFromJpegMeta = function () {\n\t\tconst deferred = $.Deferred();\n\t\tif ( this.file && this.file.type === 'image/jpeg' ) {\n\t\t\tconst binReader = new FileReader();\n\t\t\tbinReader.onerror = () => {\n\t\t\t\tdeferred.resolve();\n\t\t\t};\n\t\t\tbinReader.onload = () => {\n\t\t\t\tlet binStr, arr, i, meta;\n\t\t\t\tif ( binReader.result === null ) {\n\t\t\t\t\t// Contrary to documentation, this sometimes fires for unsuccessful loads (T136235)\n\t\t\t\t\tdeferred.resolve();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif ( typeof binReader.result === 'string' ) {\n\t\t\t\t\tbinStr = binReader.result;\n\t\t\t\t} else {\n\t\t\t\t\t// Array buffer; convert to binary string for the library.\n\t\t\t\t\tarr = new Uint8Array( binReader.result );\n\t\t\t\t\tbinStr = '';\n\t\t\t\t\tfor ( i = 0; i < arr.byteLength; i++ ) {\n\t\t\t\t\t\tbinStr += String.fromCharCode( arr[ i ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst jpegmeta = require( 'mediawiki.libs.jpegmeta' );\n\t\t\t\t\tmeta = jpegmeta( binStr, this.file.fileName );\n\t\t\t\t\t// eslint-disable-next-line camelcase, no-underscore-dangle\n\t\t\t\t\tmeta._binary_data = null;\n\t\t\t\t} catch ( e ) {\n\t\t\t\t\tmeta = null;\n\t\t\t\t}\n\t\t\t\tthis.extractMetadataFromJpegMetaCallback( meta );\n\t\t\t\tdeferred.resolve();\n\t\t\t};\n\t\t\tif ( 'readAsBinaryString' in binReader ) {\n\t\t\t\tbinReader.readAsBinaryString( this.file );\n\t\t\t} else if ( 'readAsArrayBuffer' in binReader ) {\n\t\t\t\tbinReader.readAsArrayBuffer( this.file );\n\t\t\t}\n\t\t} else {\n\t\t\tdeferred.resolve();\n\t\t}\n\t\treturn deferred.promise();\n\t};\n\n\t/**\n\t * Map fields from jpegmeta's metadata return into our format (which is more like the imageinfo returned from the API\n\t *\n\t * @param {Object} meta As returned by jpegmeta\n\t */\n\tmw.UploadWizardUpload.prototype.extractMetadataFromJpegMetaCallback = function ( meta ) {\n\t\tlet pixelHeightDim, pixelWidthDim, degrees;\n\n\t\tif ( meta !== undefined && meta !== null && typeof meta === 'object' ) {\n\t\t\tif ( this.imageinfo.metadata === undefined ) {\n\t\t\t\tthis.imageinfo.metadata = {};\n\t\t\t}\n\t\t\tif ( meta.tiff && meta.tiff.Orientation ) {\n\t\t\t\tthis.imageinfo.metadata.orientation = meta.tiff.Orientation.value;\n\t\t\t}\n\t\t\tif ( meta.general ) {\n\t\t\t\tpixelHeightDim = 'height';\n\t\t\t\tpixelWidthDim = 'width';\n\t\t\t\t// this must be called after orientation is set above. If no orientation set, defaults to 0\n\t\t\t\tdegrees = this.getOrientationDegrees();\n\n\t\t\t\t// jpegmeta reports pixelHeight & width\n\t\t\t\tif ( degrees === 90 || degrees === 270 ) {\n\t\t\t\t\tpixelHeightDim = 'width';\n\t\t\t\t\tpixelWidthDim = 'height';\n\t\t\t\t}\n\t\t\t\tif ( meta.general.pixelHeight ) {\n\t\t\t\t\tthis.imageinfo[ pixelHeightDim ] = meta.general.pixelHeight.value;\n\t\t\t\t}\n\t\t\t\tif ( meta.general.pixelWidth ) {\n\t\t\t\t\tthis.imageinfo[ pixelWidthDim ] = meta.general.pixelWidth.value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Accept the result from a successful API upload transport, and fill our own info\n\t *\n\t * @param {Object} resultUpload The JSON object from a successful API upload result.\n\t */\n\tmw.UploadWizardUpload.prototype.extractUploadInfo = function ( resultUpload ) {\n\t\tif ( resultUpload.filekey ) {\n\t\t\tthis.fileKey = resultUpload.filekey;\n\t\t}\n\n\t\tif ( resultUpload.imageinfo ) {\n\t\t\tthis.extractImageInfo( resultUpload.imageinfo );\n\t\t} else if ( resultUpload.stashimageinfo ) {\n\t\t\tthis.extractImageInfo( resultUpload.stashimageinfo );\n\t\t}\n\n\t};\n\n\t/**\n\t * Extract image info into our upload object\n\t * Image info is obtained from various different API methods\n\t * This may overwrite metadata obtained from FileReader.\n\t *\n\t * @param {Object} imageinfo JSON object obtained from API result.\n\t */\n\tmw.UploadWizardUpload.prototype.extractImageInfo = function ( imageinfo ) {\n\t\tfor ( const key in imageinfo ) {\n\t\t\t// we get metadata as list of key-val pairs; convert to object for easier lookup. Assuming that EXIF fields are unique.\n\t\t\tif ( key === 'metadata' ) {\n\t\t\t\tif ( this.imageinfo.metadata === undefined ) {\n\t\t\t\t\tthis.imageinfo.metadata = {};\n\t\t\t\t}\n\t\t\t\tif ( imageinfo.metadata && imageinfo.metadata.length ) {\n\t\t\t\t\timageinfo.metadata.forEach( ( pair ) => {\n\t\t\t\t\t\tif ( pair !== undefined ) {\n\t\t\t\t\t\t\tthis.imageinfo.metadata[ pair.name.toLowerCase() ] = pair.value;\n\t\t\t\t\t\t}\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.imageinfo[ key ] = imageinfo[ key ];\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Get information about stashed images\n\t *\n\t * See API documentation for prop=stashimageinfo for what 'props' can contain\n\t *\n\t * @param {Function} callback Called with null if failure, with imageinfo data structure if success\n\t * @param {Array} props Properties to extract\n\t * @param {number} [width] Width of thumbnail. Will force 'url' to be added to props\n\t * @param {number} [height] Height of thumbnail. Will force 'url' to be added to props\n\t */\n\tmw.UploadWizardUpload.prototype.getStashImageInfo = function ( callback, props, width, height ) {\n\t\tconst params = {\n\t\t\tprop: 'stashimageinfo',\n\t\t\tsiifilekey: this.fileKey,\n\t\t\tsiiprop: props.join( '|' )\n\t\t};\n\n\t\tfunction ok( data ) {\n\t\t\tif ( !data || !data.query || !data.query.stashimageinfo ) {\n\t\t\t\tmw.log.warn( 'mw.UploadWizardUpload::getStashImageInfo> No data?' );\n\t\t\t\tcallback( null );\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcallback( data.query.stashimageinfo );\n\t\t}\n\n\t\tfunction err( code ) {\n\t\t\tmw.log.warn( 'mw.UploadWizardUpload::getStashImageInfo> ' + code );\n\t\t\tcallback( null );\n\t\t}\n\n\t\tif ( props === undefined ) {\n\t\t\tprops = [];\n\t\t}\n\n\t\tif ( width !== undefined || height !== undefined ) {\n\t\t\tif ( !props.includes( 'url' ) ) {\n\t\t\t\tprops.push( 'url' );\n\t\t\t}\n\t\t\tif ( width !== undefined ) {\n\t\t\t\tparams.siiurlwidth = width;\n\t\t\t}\n\t\t\tif ( height !== undefined ) {\n\t\t\t\tparams.siiurlheight = height;\n\t\t\t}\n\t\t}\n\n\t\tthis.api.get( params ).done( ok ).fail( err );\n\t};\n\n\t/**\n\t * Get information about published images\n\t * (There is some overlap with getStashedImageInfo, but it's different at every stage so it's clearer to have separate functions)\n\t * See API documentation for prop=imageinfo for what 'props' can contain\n\t *\n\t * @param {Function} callback Called with null if failure, with imageinfo data structure if success\n\t * @param {Array} props Properties to extract\n\t * @param {number} [width] Width of thumbnail. Will force 'url' to be added to props\n\t * @param {number} [height] Height of thumbnail. Will force 'url' to be added to props\n\t */\n\tmw.UploadWizardUpload.prototype.getImageInfo = function ( callback, props, width, height ) {\n\t\tconst requestedTitle = this.title.getPrefixedText();\n\n\t\tfunction ok( data ) {\n\t\t\tlet found;\n\n\t\t\tif ( data && data.query && data.query.pages ) {\n\t\t\t\tfound = false;\n\t\t\t\tObject.keys( data.query.pages ).forEach( ( pageId ) => {\n\t\t\t\t\tconst page = data.query.pages[ pageId ];\n\t\t\t\t\tif ( page.title && page.title === requestedTitle && page.imageinfo ) {\n\t\t\t\t\t\tfound = true;\n\t\t\t\t\t\tcallback( page.imageinfo );\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\tif ( found ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tmw.log.warn( 'mw.UploadWizardUpload::getImageInfo> No data matching ' + requestedTitle + ' ?' );\n\t\t\tcallback( null );\n\t\t}\n\n\t\tfunction err( code ) {\n\t\t\tmw.log.warn( 'mw.UploadWizardUpload::getImageInfo> ' + code );\n\t\t\tcallback( null );\n\t\t}\n\n\t\tif ( props === undefined ) {\n\t\t\tprops = [];\n\t\t}\n\n\t\tconst params = {\n\t\t\tprop: 'imageinfo',\n\t\t\ttitles: requestedTitle,\n\t\t\tiiprop: props.join( '|' )\n\t\t};\n\n\t\tif ( width !== undefined || height !== undefined ) {\n\t\t\tif ( !props.includes( 'url' ) ) {\n\t\t\t\tprops.push( 'url' );\n\t\t\t}\n\t\t\tif ( width !== undefined ) {\n\t\t\t\tparams.iiurlwidth = width;\n\t\t\t}\n\t\t\tif ( height !== undefined ) {\n\t\t\t\tparams.iiurlheight = height;\n\t\t\t}\n\t\t}\n\n\t\tthis.api.get( params ).done( ok ).fail( err );\n\t};\n\n\t/**\n\t * Get the upload handler per browser capabilities\n\t *\n\t * @return {mw.ApiUploadFormDataHandler|mw.ApiUploadPostHandler} upload handler object\n\t */\n\tmw.UploadWizardUpload.prototype.getUploadHandler = function () {\n\t\tlet constructor; // must be the name of a function in 'mw' namespace\n\n\t\tif ( !this.uploadHandler ) {\n\t\t\tconstructor = 'ApiUploadFormDataHandler';\n\t\t\tif ( mw.UploadWizard.config.debug ) {\n\t\t\t\tmw.log( 'mw.UploadWizard::getUploadHandler> ' + constructor );\n\t\t\t}\n\t\t\tif ( this.file.fromURL ) {\n\t\t\t\tconstructor = 'ApiUploadPostHandler';\n\t\t\t}\n\t\t\tthis.uploadHandler = new mw[ constructor ]( this, this.api );\n\t\t}\n\t\treturn this.uploadHandler;\n\t};\n\n\t/**\n\t * Explicitly fetch a thumbnail for a stashed upload of the desired width.\n\t *\n\t * @private\n\t * @param {number} width Desired width of thumbnail\n\t * @param {number} height Maximum height of thumbnail\n\t * @return {jQuery.Promise} Promise resolved with a HTMLImageElement, or null if thumbnail\n\t * couldn't be generated\n\t */\n\tmw.UploadWizardUpload.prototype.getApiThumbnail = function ( width, height ) {\n\t\tconst deferred = $.Deferred();\n\n\t\tfunction thumbnailPublisher( thumbnails ) {\n\t\t\tif ( thumbnails === null ) {\n\t\t\t\t// the api call failed somehow, no thumbnail data.\n\t\t\t\tdeferred.resolve( null );\n\t\t\t} else {\n\t\t\t\t// ok, the api callback has returned us information on where the thumbnail(s) ARE, but that doesn't mean\n\t\t\t\t// they are actually there yet. Keep trying to set the source ( which should trigger \"error\" or \"load\" event )\n\t\t\t\t// on the image. If it loads publish the event with the image. If it errors out too many times, give up and publish\n\t\t\t\t// the event with a null.\n\t\t\t\tthumbnails.forEach( ( thumb ) => {\n\t\t\t\t\tif ( thumb.thumberror || ( !( thumb.thumburl && thumb.thumbwidth && thumb.thumbheight ) ) ) {\n\t\t\t\t\t\tmw.log.warn( 'mw.UploadWizardUpload::getThumbnail> Thumbnail error or missing information' );\n\t\t\t\t\t\tdeferred.resolve( null );\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\t// try to load this image with exponential backoff\n\t\t\t\t\t// if the delay goes past 8 seconds, it gives up and publishes the event with null\n\t\t\t\t\tlet timeoutMs = 100;\n\t\t\t\t\tconst image = document.createElement( 'img' );\n\t\t\t\t\timage.width = thumb.thumbwidth;\n\t\t\t\t\timage.height = thumb.thumbheight;\n\n\t\t\t\t\t// executing this should cause a .load() or .error() event on the image\n\t\t\t\t\tfunction setSrc() {\n\t\t\t\t\t\t// IE 11 and Opera 12 will not, ever, re-request an image that they have already loaded\n\t\t\t\t\t\t// once, regardless of caching headers. Append bogus stuff to the URL to make it work.\n\t\t\t\t\t\timage.src = thumb.thumburl + '?' + Math.random();\n\t\t\t\t\t}\n\n\t\t\t\t\t$( image )\n\t\t\t\t\t\t.on( 'load', () => {\n\t\t\t\t\t\t\t// publish the image to anyone who wanted it\n\t\t\t\t\t\t\tdeferred.resolve( image );\n\t\t\t\t\t\t} )\n\t\t\t\t\t\t.on( 'error', () => {\n\t\t\t\t\t\t\t// retry with exponential backoff\n\t\t\t\t\t\t\tif ( timeoutMs < 8000 ) {\n\t\t\t\t\t\t\t\tsetTimeout( () => {\n\t\t\t\t\t\t\t\t\ttimeoutMs = timeoutMs * 2 + Math.round( Math.random() * ( timeoutMs / 10 ) );\n\t\t\t\t\t\t\t\t\tsetSrc();\n\t\t\t\t\t\t\t\t}, timeoutMs );\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tdeferred.resolve( null );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} );\n\n\t\t\t\t\t// and, go!\n\t\t\t\t\tsetSrc();\n\t\t\t\t} );\n\t\t\t}\n\t\t}\n\n\t\tif ( this.state !== 'complete' ) {\n\t\t\tthis.getStashImageInfo( thumbnailPublisher, [ 'url' ], width, height );\n\t\t} else {\n\t\t\tthis.getImageInfo( thumbnailPublisher, [ 'url' ], width, height );\n\t\t}\n\n\t\treturn deferred.promise();\n\t};\n\n\t/**\n\t * Return the orientation of the image in degrees. Relies on metadata that\n\t * may have been extracted at filereader stage, or after the upload when we fetch metadata. Default returns 0.\n\t *\n\t * @return {number} orientation in degrees: 0, 90, 180 or 270\n\t */\n\tmw.UploadWizardUpload.prototype.getOrientationDegrees = function () {\n\t\tlet orientation = 0;\n\t\tif ( this.imageinfo && this.imageinfo.metadata && this.imageinfo.metadata.orientation ) {\n\t\t\tswitch ( this.imageinfo.metadata.orientation ) {\n\t\t\t\tcase 8:\n\t\t\t\t\t// 'top left' -> 'left bottom'\n\t\t\t\t\torientation = 90;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 3:\n\t\t\t\t\t// 'top left' -> 'bottom right'\n\t\t\t\t\torientation = 180;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 6:\n\t\t\t\t\t// 'top left' -> 'right top'\n\t\t\t\t\torientation = 270;\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\t// 'top left' -> 'top left'\n\t\t\t\t\torientation = 0;\n\t\t\t\t\tbreak;\n\n\t\t\t}\n\t\t}\n\t\treturn orientation;\n\t};\n\n\t/**\n\t * Fit an image into width & height constraints with scaling factor\n\t *\n\t * @private\n\t * @param {HTMLImageElement} image\n\t * @param {Object} constraints Width & height properties\n\t * @return {number}\n\t */\n\tmw.UploadWizardUpload.prototype.getScalingFromConstraints = function ( image, constraints ) {\n\t\tlet scaling = 1;\n\t\tObject.keys( constraints ).forEach( ( dim ) => {\n\t\t\tconst constraint = constraints[ dim ];\n\t\t\tif ( constraint && image[ dim ] > constraint ) {\n\t\t\t\tconst s = constraint / image[ dim ];\n\t\t\t\tif ( s < scaling ) {\n\t\t\t\t\tscaling = s;\n\t\t\t\t}\n\t\t\t}\n\t\t} );\n\t\treturn scaling;\n\t};\n\n\t/**\n\t * Given an image (already loaded), dimension constraints\n\t * return canvas object scaled & transformed ( & rotated if metadata indicates it's needed )\n\t *\n\t * @deprecated 1.41 browsers apply orientation themselves since 2020. Remove this in 2026'ish\n\t * @private\n\t * @param {HTMLImageElement} image\n\t * @param {Object} constraints Width & height constraints\n\t * @return {HTMLCanvasElement|null}\n\t */\n\tmw.UploadWizardUpload.prototype.getTransformedCanvasElement = function ( image, constraints ) {\n\t\tlet angle, rotation = 0;\n\n\t\t// if this wiki can rotate images to match their EXIF metadata,\n\t\t// we should do the same in our preview if the browser does not apply it already\n\t\tif ( mw.config.get( 'wgFileCanRotate' ) ) {\n\t\t\tangle = this.getOrientationDegrees();\n\t\t\trotation = angle ? 360 - angle : 0;\n\t\t}\n\n\t\tlet scaleConstraints;\n\t\t// swap scaling constraints if needed by rotation...\n\t\tif ( rotation === 90 || rotation === 270 ) {\n\t\t\tscaleConstraints = {};\n\t\t\tif ( 'height' in constraints ) {\n\t\t\t\tscaleConstraints.width = constraints.height;\n\t\t\t}\n\t\t\tif ( 'width' in constraints ) {\n\t\t\t\tscaleConstraints.height = constraints.width;\n\t\t\t}\n\t\t}\n\n\t\tconst scaling = this.getScalingFromConstraints( image, scaleConstraints );\n\n\t\tconst width = image.width * scaling;\n\t\tconst height = image.height * scaling;\n\n\t\tlet dimensions = { width: width, height: height };\n\t\tif ( rotation === 90 || rotation === 270 ) {\n\t\t\tdimensions = { width: height, height: width };\n\t\t}\n\n\t\t// Start drawing at offset 0,0\n\t\tconst dx = 0;\n\t\tconst dy = 0;\n\n\t\tlet x, y;\n\t\tswitch ( rotation ) {\n\t\t\t// If a rotation is applied, the direction of the axis\n\t\t\t// changes as well. You can derive the values below by\n\t\t\t// drawing on paper an axis system, rotate it and see\n\t\t\t// where the positive axis direction is\n\t\t\tcase 90:\n\t\t\t\tx = dx;\n\t\t\t\ty = dy - height;\n\t\t\t\tbreak;\n\t\t\tcase 180:\n\t\t\t\tx = dx - width;\n\t\t\t\ty = dy - height;\n\t\t\t\tbreak;\n\t\t\tcase 270:\n\t\t\t\tx = dx - width;\n\t\t\t\ty = dy;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tx = dx;\n\t\t\t\ty = dy;\n\t\t\t\tbreak;\n\t\t}\n\n\t\tconst $canvas = $( '<canvas>' ).attr( dimensions );\n\t\tconst ctx = $canvas[ 0 ].getContext( '2d' );\n\t\tctx.clearRect( dx, dy, width, height );\n\t\tctx.rotate( rotation / 180 * Math.PI );\n\t\ttry {\n\t\t\t// Calling #drawImage likes to throw all kinds of ridiculous exceptions in various browsers,\n\t\t\t// including but not limited to:\n\t\t\t// * (Firefox) NS_ERROR_NOT_AVAILABLE:\n\t\t\t// * (Internet Explorer / Edge) Not enough storage is available to complete this operation.\n\t\t\t// * (Internet Explorer / Edge) Unspecified error.\n\t\t\t// * (Internet Explorer / Edge) The GPU device instance has been suspended. Use GetDeviceRemovedReason to determine the appropriate action.\n\t\t\t// * (Safari) IndexSizeError: Index or size was negative, or greater than the allowed value.\n\t\t\t// There is nothing we can do about this. It's okay though, there just won't be a thumbnail.\n\t\t\tctx.drawImage( image, x, y, width, height );\n\t\t} catch ( err ) {\n\t\t\treturn null;\n\t\t}\n\n\t\treturn $canvas;\n\t};\n\n\t/**\n\t * Return a browser-scaled image element, given an image and constraints.\n\t *\n\t * @private\n\t * @param {HTMLImageElement} image\n\t * @param {Object} constraints Width and height properties\n\t * @return {HTMLImageElement} with same src, but different attrs\n\t */\n\tmw.UploadWizardUpload.prototype.getBrowserScaledImageElement = function ( image, constraints ) {\n\t\tconst scaling = this.getScalingFromConstraints( image, constraints );\n\t\treturn $( '<img>' )\n\t\t\t.attr( {\n\t\t\t\twidth: parseInt( image.width * scaling, 10 ),\n\t\t\t\theight: parseInt( image.height * scaling, 10 ),\n\t\t\t\tsrc: image.src\n\t\t\t} );\n\t};\n\n\t/**\n\t * Return an element suitable for the preview of a certain size. Uses canvas when possible\n\t *\n\t * @private\n\t * @param {HTMLImageElement} image\n\t * @param {number} width\n\t * @param {number} height\n\t * @return {HTMLCanvasElement|HTMLImageElement}\n\t */\n\tmw.UploadWizardUpload.prototype.getScaledImageElement = function ( image, width, height ) {\n\t\tconst constraints = {};\n\n\t\tif ( width ) {\n\t\t\tconstraints.width = width;\n\t\t}\n\t\tif ( height ) {\n\t\t\tconstraints.height = height;\n\t\t}\n\n\t\tif ( mw.canvas.isAvailable() && !CSS.supports( 'image-orientation', 'from-image' ) ) {\n\t\t\tconst transform = this.getTransformedCanvasElement( image, constraints );\n\t\t\tif ( transform ) {\n\t\t\t\treturn transform;\n\t\t\t}\n\t\t}\n\n\t\t// No canvas support or canvas drawing failed mysteriously, fall back\n\t\treturn this.getBrowserScaledImageElement( image, constraints );\n\t};\n\n\t/**\n\t * Acquire a thumbnail for this upload.\n\t *\n\t * @param {number} width\n\t * @param {number} height\n\t * @return {jQuery.Promise} Promise resolved with the HTMLImageElement or HTMLCanvasElement\n\t * containing a thumbnail, or resolved with `null` when one can't be produced\n\t */\n\tmw.UploadWizardUpload.prototype.getThumbnail = function ( width, height ) {\n\t\tconst deferred = $.Deferred();\n\n\t\tif ( this.thumbnailPromise[ width + 'x' + height ] ) {\n\t\t\treturn this.thumbnailPromise[ width + 'x' + height ];\n\t\t}\n\t\tthis.thumbnailPromise[ width + 'x' + height ] = deferred.promise();\n\n\t\t/**\n\t\t * @param {HTMLImageElement|null} image\n\t\t */\n\t\tconst imageCallback = ( image ) => {\n\t\t\tif ( image === null ) {\n\t\t\t\tthis.ui.setStatus( 'mwe-upwiz-thumbnail-failed' );\n\t\t\t\tdeferred.resolve( image );\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\timage = this.getScaledImageElement( image, width, height );\n\t\t\tdeferred.resolve( image );\n\t\t};\n\n\t\tthis.extractMetadataFromJpegMeta()\n\t\t\t.then( this.makePreview.bind( this, width ) )\n\t\t\t.done( imageCallback )\n\t\t\t.fail( () => {\n\t\t\t\t// Can't generate the thumbnail locally, get the thumbnail via API after\n\t\t\t\t// the file is uploaded. Queries are cached, so if this thumbnail was\n\t\t\t\t// already fetched for some reason, we'll get it immediately.\n\t\t\t\tif ( this.state !== 'new' && this.state !== 'transporting' && this.state !== 'error' ) {\n\t\t\t\t\tthis.getApiThumbnail( width, height ).done( imageCallback );\n\t\t\t\t} else {\n\t\t\t\t\tthis.once( 'success', () => {\n\t\t\t\t\t\tthis.getApiThumbnail( width, height ).done( imageCallback );\n\t\t\t\t\t} );\n\t\t\t\t}\n\t\t\t} );\n\n\t\treturn this.thumbnailPromise[ width + 'x' + height ];\n\t};\n\n\t/**\n\t * Notification that the file input has changed and it's fine...set info.\n\t */\n\tmw.UploadWizardUpload.prototype.fileChangedOk = function () {\n\t\tthis.ui.fileChangedOk( this.imageinfo, this.file );\n\t};\n\n\t/**\n\t * Make a preview for the file.\n\t *\n\t * @private\n\t * @param {number} width\n\t * @return {jQuery.Promise}\n\t */\n\tmw.UploadWizardUpload.prototype.makePreview = function ( width ) {\n\t\tconst deferred = $.Deferred();\n\n\t\t// do preview if we can\n\t\tif ( this.isPreviewable() ) {\n\t\t\t// open video and get frame via canvas\n\t\t\tif ( this.isVideo() ) {\n\t\t\t\tlet first = true;\n\t\t\t\tconst video = document.createElement( 'video' );\n\n\t\t\t\tvideo.addEventListener( 'loadedmetadata', () => {\n\t\t\t\t\t// seek 2 seconds into video or to half if shorter\n\t\t\t\t\tvideo.currentTime = Math.min( 2, video.duration / 2 );\n\t\t\t\t\tvideo.volume = 0;\n\t\t\t\t} );\n\t\t\t\tvideo.addEventListener( 'seeked', () => {\n\t\t\t\t\t// Firefox 16 sometimes does not work on first seek, seek again\n\t\t\t\t\tif ( first ) {\n\t\t\t\t\t\tfirst = false;\n\t\t\t\t\t\tvideo.currentTime = Math.min( 2, video.duration / 2 );\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Chrome sometimes shows black frames if grabbing right away.\n\t\t\t\t\t\t// wait 500ms before grabbing frame\n\t\t\t\t\t\tsetTimeout( () => {\n\t\t\t\t\t\t\tconst canvas = document.createElement( 'canvas' );\n\t\t\t\t\t\t\tcanvas.width = width;\n\t\t\t\t\t\t\tcanvas.height = Math.round( canvas.width * video.videoHeight / video.videoWidth );\n\t\t\t\t\t\t\tconst context = canvas.getContext( '2d' );\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t// More ridiculous exceptions, see the comment in #getTransformedCanvasElement\n\t\t\t\t\t\t\t\tcontext.drawImage( video, 0, 0, canvas.width, canvas.height );\n\t\t\t\t\t\t\t} catch ( err ) {\n\t\t\t\t\t\t\t\tdeferred.reject();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tthis.loadImage( canvas.toDataURL(), deferred );\n\t\t\t\t\t\t\tthis.URL().revokeObjectURL( video.url );\n\t\t\t\t\t\t}, 500 );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t\tconst url = this.URL().createObjectURL( this.file );\n\t\t\t\tvideo.src = url;\n\t\t\t\t// If we can't get a frame within 10 seconds, something is probably seriously wrong.\n\t\t\t\t// This can happen for broken files where we can't actually seek to the time we wanted.\n\t\t\t\tsetTimeout( () => {\n\t\t\t\t\tdeferred.reject();\n\t\t\t\t\tthis.URL().revokeObjectURL( video.url );\n\t\t\t\t}, 10000 );\n\t\t\t} else {\n\t\t\t\tconst dataUrlReader = new FileReader();\n\t\t\t\tdataUrlReader.onload = () => {\n\t\t\t\t\t// this step (inserting image-as-dataurl into image object) is slow for large images, which\n\t\t\t\t\t// is why this is optional and has a control attached to it to load the preview.\n\t\t\t\t\tthis.loadImage( dataUrlReader.result, deferred );\n\t\t\t\t};\n\t\t\t\tdataUrlReader.readAsDataURL( this.file );\n\t\t\t}\n\t\t} else {\n\t\t\tdeferred.reject();\n\t\t}\n\n\t\treturn deferred.promise();\n\t};\n\n\t/**\n\t * Loads an image preview.\n\t *\n\t * @param {string} url\n\t * @param {jQuery.Deferred} deferred\n\t */\n\tmw.UploadWizardUpload.prototype.loadImage = function ( url, deferred ) {\n\t\tconst image = document.createElement( 'img' );\n\t\timage.onload = function () {\n\t\t\tdeferred.resolve( image );\n\t\t};\n\t\timage.onerror = function () {\n\t\t\tdeferred.reject();\n\t\t};\n\t\ttry {\n\t\t\timage.src = url;\n\t\t} catch ( er ) {\n\t\t\t// On Internet Explorer 11 and Edge, this occasionally causes an exception (possibly\n\t\t\t// localised) like \"Not enough storage is available to complete this operation\". (T136239)\n\t\t\tdeferred.reject();\n\t\t}\n\t};\n\n\t/**\n\t * Check if the file is previewable.\n\t *\n\t * @return {boolean}\n\t */\n\tmw.UploadWizardUpload.prototype.isPreviewable = function () {\n\t\treturn this.file && mw.fileApi.isPreviewableFile( this.file );\n\t};\n\n\t/**\n\t * Finds the right URL object to use.\n\t *\n\t * @return {URL}\n\t */\n\tmw.UploadWizardUpload.prototype.URL = function () {\n\t\t// This functionality is missing on IE 11\n\n\t\treturn window.URL || window.webkitURL || window.mozURL;\n\t};\n\n\t/**\n\t * Checks if this upload is a video.\n\t *\n\t * @return {boolean}\n\t */\n\tmw.UploadWizardUpload.prototype.isVideo = function () {\n\t\treturn mw.fileApi.isPreviewableVideo( this.file );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.UploadWizardUploadInterface.js","messages":[{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":110,"column":3,"nodeType":"CallExpression","endLine":110,"endColumn":36},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":215,"column":3,"nodeType":"CallExpression","endLine":218,"endColumn":6}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\t/**\n\t * Create an interface fragment corresponding to a file input, suitable for Upload Wizard.\n\t *\n\t * @class\n\t * @mixes OO.EventEmitter\n\t * @param {mw.UploadWizardUpload} upload\n\t */\n\tmw.UploadWizardUploadInterface = function MWUploadWizardUploadInterface( upload ) {\n\t\tOO.EventEmitter.call( this );\n\n\t\tthis.upload = upload;\n\n\t\t// May need to collaborate with the particular upload type sometimes\n\t\t// for the interface, as well as the uploadwizard. OY.\n\t\tthis.$div = $( '<div>' ).addClass( 'mwe-upwiz-file' );\n\n\t\tthis.isFilled = false;\n\n\t\tthis.statusMessage = new OO.ui.MessageWidget( { inline: true } );\n\t\tthis.statusMessage.toggle( false );\n\t\tthis.$spinner = $.createSpinner( { size: 'small', type: 'inline' } );\n\t\tthis.$spinner.hide();\n\t\tthis.$indicator = $( '<div>' ).addClass( 'mwe-upwiz-file-indicator' ).append(\n\t\t\tthis.$spinner,\n\t\t\tthis.statusMessage.$element\n\t\t);\n\n\t\tthis.$visibleFilenameDiv = $( '<div>' ).addClass( 'mwe-upwiz-visible-file' ).append(\n\t\t\tthis.$indicator,\n\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-visible-file-filename' ).append(\n\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-file-preview' ),\n\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-file-texts' ).append(\n\t\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-visible-file-filename-text' ),\n\t\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-file-license' ),\n\t\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-file-status-line' ).append(\n\t\t\t\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-file-status' )\n\t\t\t\t\t)\n\t\t\t\t)\n\t\t\t)\n\t\t);\n\n\t\tthis.removeCtrl = new OO.ui.ButtonWidget( {\n\t\t\tlabel: mw.message( 'mwe-upwiz-remove' ).text(),\n\t\t\ttitle: mw.message( 'mwe-upwiz-remove-upload' ).text(),\n\t\t\tflags: 'destructive',\n\t\t\ticon: 'trash',\n\t\t\tframed: false\n\t\t} ).on( 'click', () => {\n\t\t\tthis.emit( 'upload-removed' );\n\t\t} );\n\n\t\tif ( mw.UploadWizard.config.defaults && mw.UploadWizard.config.defaults.objref !== '' ) {\n\t\t\tthis.$imagePicker = this.createImagePickerField(\n\t\t\t\tthis.upload.index,\n\t\t\t\tmw.UploadWizard.config.defaults.updateList === ''\n\t\t\t);\n\t\t\tthis.$visibleFilenameDiv.find( '.mwe-upwiz-file-status-line' )\n\t\t\t\t.append( this.$imagePicker );\n\t\t}\n\n\t\tthis.$visibleFilenameDiv.find( '.mwe-upwiz-file-status-line' )\n\t\t\t.append( this.removeCtrl.$element );\n\n\t\tthis.$form = $( '<form>' )\n\t\t\t.addClass( 'mwe-upwiz-form' )\n\t\t\t.append( this.$visibleFilenameDiv );\n\n\t\tthis.$div.append( this.$form );\n\n\t\t// this.progressBar = ( no progress bar for individual uploads yet )\n\t\t// we bind to the ui div since .off() doesn't work for non-DOM objects\n\t\t// TODO Convert this to an OO.EventEmitter, and use OOjs events\n\t\tthis.$div.on( 'transportProgressEvent', () => {\n\t\t\tthis.showTransportProgress();\n\t\t} );\n\t};\n\n\tOO.mixinClass( mw.UploadWizardUploadInterface, OO.EventEmitter );\n\n\t/**\n\t * Change the graphic indicator at the far end of the row for this file\n\t *\n\t * @param {string} [status] Either a OO.ui.MessageWidget type (error/success/...) or 'progress'.\n\t * Omit to hide the indicator\n\t */\n\tmw.UploadWizardUploadInterface.prototype.showIndicator = function ( status ) {\n\t\tconst progress = status === 'progress';\n\t\tthis.$spinner.toggle( progress );\n\t\tthis.statusMessage.toggle( status && !progress ).setType( status );\n\t\tthis.$indicator.toggleClass( 'mwe-upwiz-file-indicator-visible', !!status );\n\t};\n\n\t/**\n\t * @param {string} license License text\n\t */\n\tmw.UploadWizardUploadInterface.prototype.setLicenseText = function ( license ) {\n\t\tthis.$div.find( '.mwe-upwiz-file-license' ).text( license );\n\t};\n\n\t/**\n\t * Set the status line for this upload with an internationalized message string.\n\t *\n\t * @param {string} msgKey Key for the message\n\t * @param {Array} [args] Array of values, in case any need to be fed to the image.\n\t */\n\tmw.UploadWizardUploadInterface.prototype.setStatus = function ( msgKey, args ) {\n\t\t// get the status line for our upload\n\t\tconst $status = this.$div.find( '.mwe-upwiz-file-status' );\n\t\t$status.msg( msgKey, args || [] ).show();\n\t};\n\n\t/**\n\t * Set status line directly with a string\n\t *\n\t * @param {string} html\n\t */\n\tmw.UploadWizardUploadInterface.prototype.setStatusString = function ( html ) {\n\t\tthis.$div.find( '.mwe-upwiz-file-status' ).html( html ).show();\n\t};\n\n\t/**\n\t * Set additional status information\n\t *\n\t * @param {jQuery} [$status] If not given or null, additional status is cleared\n\t */\n\tmw.UploadWizardUploadInterface.prototype.setAdditionalStatus = function ( $status ) {\n\t\tif ( this.$additionalStatus ) {\n\t\t\tthis.$additionalStatus.remove();\n\t\t}\n\t\tthis.$additionalStatus = $status;\n\t\tif ( this.$additionalStatus ) {\n\t\t\tthis.$div.find( '.mwe-upwiz-file-status' ).after( this.$additionalStatus );\n\t\t}\n\t};\n\n\t/**\n\t * Clear the status line for this upload (hide it, in case there are paddings and such which offset other things.)\n\t */\n\tmw.UploadWizardUploadInterface.prototype.clearStatus = function () {\n\t\tthis.$div.find( '.mwe-upwiz-file-status' ).hide();\n\t\tthis.setAdditionalStatus( null );\n\t};\n\n\t/**\n\t * Put the visual state of an individual upload into \"progress\"\n\t *\n\t * @param {number} fraction The fraction of progress. Float between 0 and 1\n\t */\n\tmw.UploadWizardUploadInterface.prototype.showTransportProgress = function () {\n\t\t// if fraction available, update individual progress bar / estimates, etc.\n\t\tthis.showIndicator( 'progress' );\n\t\tthis.setStatus( 'mwe-upwiz-uploading' );\n\t\tthis.setAdditionalStatus( null );\n\t};\n\n\t/**\n\t * Show that upload is transported\n\t */\n\tmw.UploadWizardUploadInterface.prototype.showStashed = function () {\n\t\tthis.showIndicator( 'success' );\n\t\tthis.setStatus( 'mwe-upwiz-stashed-upload' );\n\t\tthis.setAdditionalStatus( null );\n\t};\n\n\t/**\n\t * Show that transport has failed\n\t *\n\t * @param {string} code Error code from API\n\t * @param {string} html Error message\n\t * @param {jQuery} [$additionalStatus]\n\t */\n\tmw.UploadWizardUploadInterface.prototype.showError = function ( code, html, $additionalStatus ) {\n\t\tthis.showIndicator( 'error' );\n\t\tthis.setStatusString( html );\n\t\tthis.setAdditionalStatus( $additionalStatus );\n\t};\n\n\t/**\n\t * Run this when the value of the file input has changed and we know it's acceptable -- this\n\t * will update interface to show as much info as possible, including preview.\n\t * n.b. in older browsers we only will know the filename\n\t *\n\t * @param {Object} imageinfo\n\t * @param {File} file\n\t */\n\tmw.UploadWizardUploadInterface.prototype.fileChangedOk = function ( imageinfo, file ) {\n\t\tconst statusItems = [];\n\n\t\tthis.updateFilename();\n\n\t\t// set the status string - e.g. \"256 Kb, 100 x 200\"\n\t\tif ( imageinfo && imageinfo.width && imageinfo.height ) {\n\t\t\tstatusItems.push( imageinfo.width + '\\u00d7' + imageinfo.height );\n\t\t}\n\n\t\tif ( file && file.size ) {\n\t\t\tstatusItems.push( uw.units.bytes( file.size ) );\n\t\t}\n\n\t\tthis.clearStatus();\n\t\tthis.setStatusString( statusItems.join( ' \\u00b7 ' ) );\n\t};\n\n\t/**\n\t * Display thumbnail preview.\n\t *\n\t * @return {jQuery.Promise} Promise resolved when the thumbnail is displayed or when displaying it\n\t * fails\n\t */\n\tmw.UploadWizardUploadInterface.prototype.showThumbnail = function () {\n\t\tconst $preview = this.$div.find( '.mwe-upwiz-file-preview' ),\n\t\t\tdeferred = $.Deferred();\n\t\t// This must match the CSS dimensions of .mwe-upwiz-file-preview\n\t\tthis.upload.getThumbnail( 120, 120 ).done( ( thumb ) => {\n\t\t\tmw.UploadWizard.placeThumbnail( $preview, thumb );\n\t\t\tdeferred.resolve();\n\t\t} );\n\t\treturn deferred.promise();\n\t};\n\n\t/**\n\t * this does two things:\n\t * 1 ) since the file input has been hidden with some clever CSS ( to avoid x-browser styling issues ),\n\t * update the visible filename\n\t *\n\t * 2 ) update the underlying \"title\" which we are targeting to add to mediawiki.\n\t * TODO silently fix to have unique filename? unnecessary at this point...\n\t */\n\tmw.UploadWizardUploadInterface.prototype.updateFilename = function () {\n\t\tconst path = this.upload.getFilename();\n\n\t\t// visible filename\n\t\tthis.$form.find( '.mwe-upwiz-visible-file-filename-text' )\n\t\t\t.text( path );\n\n\t\tif ( !this.isFilled ) {\n\t\t\tthis.isFilled = true;\n\t\t\tthis.$div.addClass( 'filled' );\n\t\t}\n\t};\n\n\t/**\n\t * Create a checkbox to process the object reference parameter\n\t *\n\t * @param {number} index Number of the file for which the field is being created\n\t * @param {boolean} setDisabled Disable in case there already is an image in the referring list\n\t * @return {jQuery} A `div` containing a checkbox, label, and optional notice\n\t */\n\tmw.UploadWizardUploadInterface.prototype.createImagePickerField = function ( index, setDisabled ) {\n\t\tconst $fieldContainer = $( '<div>' ).addClass( 'mwe-upwiz-objref-pick-image' ),\n\t\t\tattributes = {\n\t\t\t\ttype: 'checkbox',\n\t\t\t\tclass: 'imgPicker',\n\t\t\t\tid: 'imgPicker' + index,\n\t\t\t\tdisabled: false,\n\t\t\t\tchecked: false\n\t\t\t};\n\n\t\tif ( setDisabled ) {\n\t\t\tattributes.disabled = 'disabled';\n\t\t} else if ( index === 0 ) {\n\t\t\tattributes.checked = 'checked';\n\t\t}\n\n\t\t$fieldContainer.append(\n\t\t\t$( '<input>' ).attr( attributes ).on( 'click', function () {\n\t\t\t\t$( this )\n\t\t\t\t\t.prop( 'checked', true )\n\t\t\t\t\t.closest( '.mwe-upwiz-file' )\n\t\t\t\t\t.siblings()\n\t\t\t\t\t.find( '.imgPicker' )\n\t\t\t\t\t.prop( 'checked', false );\n\t\t\t} ),\n\n\t\t\t$( '<label>' ).attr( {\n\t\t\t\tfor: 'imgPicker' + index\n\t\t\t} ).text( this.getPickImageLabel() )\n\t\t);\n\n\t\tif ( setDisabled ) {\n\t\t\t$fieldContainer.append(\n\t\t\t\t$( '<div>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-objref-notice-existing-image' )\n\t\t\t\t\t.text( this.getExistingImageNotice() )\n\t\t\t);\n\t\t}\n\n\t\treturn $fieldContainer;\n\t};\n\n\tmw.UploadWizardUploadInterface.prototype.getExistingImageNotice = function () {\n\t\tif ( mw.UploadWizard.config && mw.UploadWizard.config.display && mw.UploadWizard.config.display.noticeExistingImage ) {\n\t\t\treturn mw.UploadWizard.config.display.noticeExistingImage;\n\t\t} else {\n\t\t\treturn mw.message( 'mwe-upwiz-objref-notice-existing-image' ).text();\n\t\t}\n\t};\n\n\tmw.UploadWizardUploadInterface.prototype.getPickImageLabel = function () {\n\t\tif ( mw.UploadWizard.config && mw.UploadWizard.config.display && mw.UploadWizard.config.display.labelPickImage ) {\n\t\t\treturn mw.UploadWizard.config.display.labelPickImage;\n\t\t} else {\n\t\t\treturn mw.message( 'mwe-upwiz-objref-pick-image' ).text();\n\t\t}\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.canvas.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.errorDialog.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/mw.fileApi.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/transports/mw.FormDataTransport.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":166,"column":5,"nodeType":"CallExpression","endLine":175,"endColumn":8},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":167,"column":6,"nodeType":"CallExpression","endLine":168,"endColumn":67},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":167,"column":6,"nodeType":"CallExpression","endLine":169,"endColumn":31}],"suppressedMessages":[{"ruleId":"no-loop-func","severity":2,"message":"Function declared in a loop contains unsafe references to variable(s) 'prevPromise', 'prevPromise'.","line":161,"column":6,"nodeType":"ArrowFunctionExpression","messageId":"unsafeRefs","endLine":177,"endColumn":5,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function () {\n\t/**\n\t * Represents a \"transport\" for files to upload; using HTML5 FormData.\n\t *\n\t * @class\n\t * @mixes OO.EventEmitter\n\t * @param {mw.Api} api\n\t * @param {Object} formData Additional form fields required for upload api call\n\t * @param {Object} [config]\n\t * @param {Object} [config.chunkSize]\n\t * @param {Object} [config.maxPhpUploadSize]\n\t * @param {Object} [config.useRetryTimeout]\n\t */\n\tmw.FormDataTransport = function ( api, formData, config ) {\n\t\tthis.config = config || mw.UploadWizard.config;\n\n\t\tOO.EventEmitter.call( this );\n\n\t\tthis.formData = formData;\n\t\tthis.aborted = false;\n\t\tthis.api = api;\n\n\t\t// Set chunk size to configured chunk size or max php size,\n\t\t// whichever is smaller.\n\t\tthis.chunkSize = Math.min( this.config.chunkSize, this.config.maxPhpUploadSize );\n\t\tthis.maxRetries = 2;\n\t\tthis.retries = 0;\n\t\tthis.firstPoll = false;\n\n\t\t// running API request\n\t\tthis.request = null;\n\t};\n\n\tOO.mixinClass( mw.FormDataTransport, OO.EventEmitter );\n\n\tmw.FormDataTransport.prototype.abort = function () {\n\t\tthis.aborted = true;\n\n\t\tif ( this.request ) {\n\t\t\tthis.request.abort();\n\t\t}\n\t};\n\n\t/**\n\t * Submits an upload to the API.\n\t *\n\t * @param {Object} params Request params\n\t * @return {jQuery.Promise}\n\t */\n\tmw.FormDataTransport.prototype.post = function ( params ) {\n\t\tconst deferred = $.Deferred();\n\n\t\tthis.request = this.api.post( params, {\n\t\t\t/*\n\t\t\t * $.ajax is not quite equiped to handle File uploads with params.\n\t\t\t * The most convenient way would be to submit it with a FormData\n\t\t\t * object, but mw.Api will already do that for us: it'll transform\n\t\t\t * params if it encounters a multipart/form-data POST request, and\n\t\t\t * submit it accordingly!\n\t\t\t *\n\t\t\t * @see https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Submitting_forms_and_uploading_files\n\t\t\t */\n\t\t\tcontentType: 'multipart/form-data',\n\t\t\t/*\n\t\t\t * $.ajax also has no progress event that will allow us to figure\n\t\t\t * out how much of the upload has already gone out, so let's add it!\n\t\t\t */\n\t\t\txhr: function () {\n\t\t\t\tconst xhr = $.ajaxSettings.xhr();\n\t\t\t\txhr.upload.addEventListener( 'progress', ( evt ) => {\n\t\t\t\t\tlet fraction = null;\n\t\t\t\t\tif ( evt.lengthComputable ) {\n\t\t\t\t\t\tfraction = parseFloat( evt.loaded / evt.total );\n\t\t\t\t\t}\n\t\t\t\t\tdeferred.notify( fraction );\n\t\t\t\t}, false );\n\t\t\t\treturn xhr;\n\t\t\t}\n\t\t} );\n\n\t\t// just pass on success & failures\n\t\tthis.request.then( deferred.resolve, deferred.reject );\n\n\t\treturn deferred.promise();\n\t};\n\n\t/**\n\t * Creates the upload API params.\n\t *\n\t * @param {string} filename\n\t * @param {number} [offset] For chunked uploads\n\t * @return {Object}\n\t */\n\tmw.FormDataTransport.prototype.createParams = function ( filename, offset ) {\n\t\tconst params = OO.cloneObject( this.formData );\n\n\t\tObject.assign( params, {\n\t\t\tfilename: filename,\n\n\t\t\t// ignorewarnings is turned on, since warnings are presented in a\n\t\t\t// later step and this transport doesn't know how to deal with them.\n\t\t\t// Also, it's important to allow people to upload files with (for\n\t\t\t// example) blacklisted names, and then rename them later in the\n\t\t\t// wizard.\n\t\t\tignorewarnings: true,\n\n\t\t\toffset: offset || 0\n\t\t} );\n\n\t\treturn params;\n\t};\n\n\t/**\n\t * Start the upload with the provided file.\n\t *\n\t * @param {File} file\n\t * @param {string} tempFileName\n\t * @return {jQuery.Promise}\n\t */\n\tmw.FormDataTransport.prototype.upload = function ( file, tempFileName ) {\n\t\tlet params, ext;\n\n\t\tthis.tempname = tempFileName;\n\t\t// Limit length to 240 bytes (limit hardcoded in UploadBase.php).\n\t\tif ( this.tempname.length > 240 ) {\n\t\t\text = this.tempname.split( '.' ).pop();\n\t\t\tthis.tempname = this.tempname.slice( 0, 240 - ext.length - 1 ) + '.' + ext;\n\t\t}\n\n\t\tif ( file.size > this.chunkSize ) {\n\t\t\treturn this.chunkedUpload( file );\n\t\t} else {\n\t\t\tparams = this.createParams( this.tempname );\n\t\t\tparams.file = file;\n\t\t\treturn this.post( params );\n\t\t}\n\t};\n\n\t/**\n\t * This function exists to safely chain several hundred promises without using .then() or nested\n\t * promises. We might divide a 4 GB file into 800 chunks of 5 MB each.\n\t *\n\t * In jQuery 2.x, nested promises result in nested call stacks when resolving/rejecting/notifying\n\t * the last promise in the chain and listening on the first one, and browsers have call stack\n\t * limits low enough that we previously ran into them for files around a couple hundred megabytes\n\t * (the worst is Firefox 47 with a limit of 1024 calls).\n\t *\n\t * @param {File} file\n\t * @return {jQuery.Promise} Promise which behaves identically to a regular non-chunked upload\n\t * promise from #upload\n\t */\n\tmw.FormDataTransport.prototype.chunkedUpload = function ( file ) {\n\t\tlet prevPromise = $.Deferred().resolve();\n\t\tconst deferred = $.Deferred(),\n\t\t\tfileSize = file.size,\n\t\t\tchunkSize = this.chunkSize;\n\n\t\tfor ( let off = 0; off < fileSize; off += chunkSize ) {\n\t\t\t// Capture offset in a closure\n\t\t\t// eslint-disable-next-line no-loop-func\n\t\t\t( ( offset ) => {\n\t\t\t\tconst\n\t\t\t\t\tnewPromise = $.Deferred(),\n\t\t\t\t\tisLastChunk = offset + chunkSize >= fileSize,\n\t\t\t\t\tthisChunkSize = isLastChunk ? ( fileSize % chunkSize ) : chunkSize;\n\t\t\t\tprevPromise.done( () => {\n\t\t\t\t\tthis.uploadChunk( file, offset )\n\t\t\t\t\t\t.done( isLastChunk ? deferred.resolve : newPromise.resolve )\n\t\t\t\t\t\t.fail( deferred.reject )\n\t\t\t\t\t\t.progress( ( fraction ) => {\n\t\t\t\t\t\t\t// The progress notifications give us per-chunk progress.\n\t\t\t\t\t\t\t// Calculate progress for the whole file.\n\t\t\t\t\t\t\tdeferred.notify( ( offset + fraction * thisChunkSize ) / fileSize );\n\t\t\t\t\t\t} );\n\t\t\t\t} );\n\t\t\t\tprevPromise = newPromise;\n\t\t\t} )( off );\n\t\t}\n\n\t\treturn deferred.promise();\n\t};\n\n\t/**\n\t * Upload a single chunk.\n\t *\n\t * @param {File} file\n\t * @param {number} offset Offset in bytes.\n\t * @return {jQuery.Promise}\n\t */\n\tmw.FormDataTransport.prototype.uploadChunk = function ( file, offset ) {\n\t\tconst params = this.createParams( this.tempname, offset ),\n\t\t\tbytesAvailable = file.size;\n\n\t\tif ( this.aborted ) {\n\t\t\treturn $.Deferred().reject( 'aborted', {\n\t\t\t\terrors: [ {\n\t\t\t\t\tcode: 'aborted',\n\t\t\t\t\thtml: mw.message( 'api-error-aborted' ).parse()\n\t\t\t\t} ]\n\t\t\t} );\n\t\t}\n\n\t\t// Slice API was changed and has vendor prefix for now\n\t\t// new version now require start/end and not start/length\n\t\tlet chunk;\n\t\tif ( file.mozSlice ) {\n\t\t\tchunk = file.mozSlice( offset, offset + this.chunkSize, file.type );\n\t\t} else if ( file.webkitSlice ) {\n\t\t\tchunk = file.webkitSlice( offset, offset + this.chunkSize, file.type );\n\t\t} else {\n\t\t\tchunk = file.slice( offset, offset + this.chunkSize, file.type );\n\t\t}\n\n\t\t// only enable async if file is larger 10Mb\n\t\tif ( bytesAvailable > 10 * 1024 * 1024 ) {\n\t\t\tparams.async = true;\n\t\t}\n\n\t\t// If offset is 0, we're uploading the file from scratch. filekey may be set if we're retrying\n\t\t// the first chunk. The API errors out if a filekey is given with zero offset (as it's\n\t\t// nonsensical). TODO Why do we need to retry in this case, if we managed to upload something?\n\t\tif ( this.filekey && offset !== 0 ) {\n\t\t\tparams.filekey = this.filekey;\n\t\t}\n\t\tparams.filesize = bytesAvailable;\n\t\tparams.chunk = chunk;\n\n\t\treturn this.post( params ).then( ( response ) => {\n\t\t\tif ( response.upload && response.upload.filekey ) {\n\t\t\t\tthis.filekey = response.upload.filekey;\n\t\t\t}\n\n\t\t\tif ( response.upload && response.upload.result ) {\n\t\t\t\tswitch ( response.upload.result ) {\n\t\t\t\t\tcase 'Continue':\n\t\t\t\t\t\t// Reset retry counter\n\t\t\t\t\t\tthis.retries = 0;\n\t\t\t\t\t\t/* falls through */\n\t\t\t\t\tcase 'Success':\n\t\t\t\t\t\t// Just pass the response through.\n\t\t\t\t\t\treturn response;\n\t\t\t\t\tcase 'Poll':\n\t\t\t\t\t\t// Need to retry with checkStatus.\n\t\t\t\t\t\treturn this.retryWithMethod( 'checkStatus' );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn this.maybeRetry(\n\t\t\t\t\t'on unknown response',\n\t\t\t\t\tresponse.error ? response.error.code : 'unknown-error',\n\t\t\t\t\tresponse,\n\t\t\t\t\t'uploadChunk',\n\t\t\t\t\tfile, offset\n\t\t\t\t);\n\t\t\t}\n\t\t}, ( code, result ) => {\n\t\t\t// Ain't this some great machine readable output eh\n\t\t\tif (\n\t\t\t\tresult.errors &&\n\t\t\t\tresult.errors[ 0 ].code === 'stashfailed' &&\n\t\t\t\tresult.errors[ 0 ].html === mw.message( 'apierror-stashfailed-complete' ).parse()\n\t\t\t) {\n\t\t\t\treturn this.retryWithMethod( 'checkStatus' );\n\t\t\t}\n\n\t\t\t// Failed to upload, try again in 3 seconds\n\t\t\t// This is really dumb, we should only do this for cases where retrying has a chance to work\n\t\t\t// (so basically, network failures). If your upload was blocked by AbuseFilter you're\n\t\t\t// shafted anyway. But some server-side errors really are temporary...\n\t\t\treturn this.maybeRetry(\n\t\t\t\t'on error event',\n\t\t\t\tcode,\n\t\t\t\tresult,\n\t\t\t\t'uploadChunk',\n\t\t\t\tfile, offset\n\t\t\t);\n\t\t} );\n\t};\n\n\t/**\n\t * Handle possible retry event - rejected if maximum retries already fired.\n\t *\n\t * @param {string} contextMsg\n\t * @param {string} code\n\t * @param {Object} response\n\t * @param {string} retryMethod\n\t * @param {File} [file]\n\t * @param {number} [offset]\n\t * @return {jQuery.Promise}\n\t */\n\tmw.FormDataTransport.prototype.maybeRetry = function ( contextMsg, code, response, retryMethod, file, offset ) {\n\t\tthis.retries++;\n\n\t\tif ( this.tooManyRetries() ) {\n\t\t\tmw.log.warn( 'Max retries exceeded ' + contextMsg );\n\t\t\treturn $.Deferred().reject( code, response );\n\t\t} else if ( this.aborted ) {\n\t\t\treturn $.Deferred().reject( code, response );\n\t\t} else {\n\t\t\tmw.log( 'Retry #' + this.retries + ' ' + contextMsg );\n\t\t\treturn this.retryWithMethod( retryMethod, file, offset );\n\t\t}\n\t};\n\n\t/**\n\t * Have we retried too many times already?\n\t *\n\t * @return {boolean}\n\t */\n\tmw.FormDataTransport.prototype.tooManyRetries = function () {\n\t\treturn this.maxRetries > 0 && this.retries >= this.maxRetries;\n\t};\n\n\t/**\n\t * Either retry uploading or checking the status.\n\t *\n\t * @param {'uploadChunk'|'checkStatus'} methodName\n\t * @param {File} [file]\n\t * @param {number} [offset]\n\t * @return {jQuery.Promise}\n\t */\n\tmw.FormDataTransport.prototype.retryWithMethod = function ( methodName, file, offset ) {\n\t\tconst\n\t\t\tretryDeferred = $.Deferred(),\n\t\t\tretry = () => {\n\t\t\t\tthis[ methodName ]( file, offset ).then( retryDeferred.resolve, retryDeferred.reject );\n\t\t\t};\n\n\t\tif ( this.config.useRetryTimeout !== false ) {\n\t\t\tsetTimeout( retry, 3000 );\n\t\t} else {\n\t\t\tretry();\n\t\t}\n\n\t\treturn retryDeferred.promise();\n\t};\n\n\t/**\n\t * Check the status of the upload.\n\t *\n\t * @return {jQuery.Promise}\n\t */\n\tmw.FormDataTransport.prototype.checkStatus = function () {\n\t\tconst params = OO.cloneObject( this.formData );\n\n\t\tif ( this.aborted ) {\n\t\t\treturn $.Deferred().reject( 'aborted', {\n\t\t\t\terrors: [ {\n\t\t\t\t\tcode: 'aborted',\n\t\t\t\t\thtml: mw.message( 'api-error-aborted' ).parse()\n\t\t\t\t} ]\n\t\t\t} );\n\t\t}\n\n\t\tif ( !this.firstPoll ) {\n\t\t\tthis.firstPoll = Date.now();\n\t\t}\n\t\tparams.checkstatus = true;\n\t\tparams.filekey = this.filekey;\n\n\t\tthis.request = this.api.post( params );\n\n\t\treturn this.request.then(\n\t\t\t( response ) => {\n\t\t\t\tif ( response.upload && response.upload.result === 'Poll' ) {\n\t\t\t\t\t// If concatenation takes longer than 10 minutes give up\n\t\t\t\t\tif ( ( Date.now() - this.firstPoll ) > 10 * 60 * 1000 ) {\n\t\t\t\t\t\treturn $.Deferred().reject( 'server-error', { errors: [ {\n\t\t\t\t\t\t\tcode: 'server-error',\n\t\t\t\t\t\t\thtml: mw.message( 'api-clientside-error-timeout' ).parse()\n\t\t\t\t\t\t} ] } );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif ( response.upload.stage === undefined ) {\n\t\t\t\t\t\t\tmw.log.warn( 'Unable to check file\\'s status' );\n\t\t\t\t\t\t\treturn $.Deferred().reject( 'server-error', { errors: [ {\n\t\t\t\t\t\t\t\tcode: 'server-error',\n\t\t\t\t\t\t\t\thtml: mw.message( 'api-clientside-error-invalidresponse' ).parse()\n\t\t\t\t\t\t\t} ] } );\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Statuses that can be returned:\n\t\t\t\t\t\t\t// * queued\n\t\t\t\t\t\t\t// * publish\n\t\t\t\t\t\t\t// * assembling\n\t\t\t\t\t\t\tthis.emit( 'update-stage', response.upload.stage );\n\t\t\t\t\t\t\treturn this.retryWithMethod( 'checkStatus' );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn response;\n\t\t\t},\n\t\t\t( code, result ) => $.Deferred().reject( code, result )\n\t\t);\n\t};\n}() );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ui/steps/uw.ui.Deed.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ui.Step' is undefined.","line":23,"column":1,"nodeType":"Block","endLine":23,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":51,"column":3,"nodeType":"CallExpression","endLine":55,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":86,"column":5,"nodeType":"CallExpression","endLine":88,"endColumn":8},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":108,"column":6,"nodeType":"CallExpression","endLine":110,"endColumn":9}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\t/**\n\t * Represents the UI for the wizard's Deed step.\n\t *\n\t * @class\n\t * @extends uw.ui.Step\n\t */\n\tuw.ui.Deed = function UWUIDeed() {\n\t\tuw.ui.Step.call(\n\t\t\tthis,\n\t\t\t'deeds'\n\t\t);\n\n\t\tthis.addPreviousButton();\n\t\tthis.addNextButton();\n\t};\n\n\tOO.inheritClass( uw.ui.Deed, uw.ui.Step );\n\n\tuw.ui.Deed.prototype.load = function ( uploads ) {\n\t\tuw.ui.Step.prototype.load.call( this, uploads );\n\n\t\tthis.$deedsContainer = $( '<div>' ).attr( 'id', 'mwe-upwiz-deeds' );\n\t\tthis.$thumbsContainer = $( '<div>' ).attr( 'id', 'mwe-upwiz-deeds-thumbnails' );\n\n\t\tthis.$div.prepend(\n\t\t\tthis.$thumbsContainer.addClass( 'ui-helper-clearfix' ),\n\t\t\t$( '<div>' )\n\t\t\t\t.attr( 'id', 'mwe-upwiz-deeds-intro' )\n\t\t\t\t.msg( 'mwe-upwiz-deeds-intro', uploads.length, mw.user ),\n\t\t\tthis.$deedsContainer.addClass( 'ui-helper-clearfix' )\n\t\t);\n\n\t\tthis.nextButtonPromise.done( () => {\n\t\t\t// hide \"next\" button, controller will only show it once license has\n\t\t\t// been selected\n\t\t\tthis.nextButton.$element.hide();\n\t\t} );\n\t};\n\n\tuw.ui.Deed.prototype.clearForm = function () {\n\t\tthis.$deedsContainer.empty();\n\t\tthis.$thumbsContainer.empty();\n\t};\n\n\t/**\n\t * @param {OO.ui.RadioSelectWidget} multiDeedRadio\n\t */\n\tuw.ui.Deed.prototype.showMultiDeedRadio = function ( multiDeedRadio ) {\n\t\tthis.$div.prepend( multiDeedRadio.$element );\n\t};\n\n\t/**\n\t * @param {mw.UploadWizardDeedChooser} deedChooser\n\t */\n\tuw.ui.Deed.prototype.showCommonForm = function ( deedChooser ) {\n\n\t\tthis.clearForm();\n\n\t\tthis.$deedsContainer.append( deedChooser.$element );\n\t\tdeedChooser.onLayoutReady();\n\n\t\tdeedChooser.uploads.forEach( ( upload ) => {\n\t\t\tconst $element = $( '<div>' ).addClass( 'mwe-upwiz-thumbnail' );\n\n\t\t\t// Add previews and details to the DOM\n\t\t\tif ( !upload.file.fromURL ) {\n\t\t\t\t// This must match the CSS dimensions of .mwe-upwiz-thumbnail\n\t\t\t\tupload.getThumbnail( 120, 120 ).done( ( thumb ) => {\n\t\t\t\t\tmw.UploadWizard.placeThumbnail( $element, thumb );\n\t\t\t\t} );\n\n\t\t\t\tthis.$thumbsContainer.append( $element );\n\t\t\t}\n\t\t} );\n\t};\n\n\t/**\n\t * @param {mw.UploadWizardDeedChooser[]} deedChoosers\n\t */\n\tuw.ui.Deed.prototype.showIndividualForm = function ( deedChoosers ) {\n\t\tthis.clearForm();\n\n\t\tdeedChoosers.forEach( ( deedChooser ) => {\n\t\t\tdeedChooser.uploads.forEach( ( upload ) => {\n\t\t\t\tconst $thumbContainer = $( '<div>' ).addClass( 'mwe-upwiz-deeds-individual-thumbnail' ),\n\t\t\t\t\t$element = $( '<div>' ).addClass( 'mwe-upwiz-thumbnail' );\n\n\t\t\t\t// Add previews and details to the DOM\n\t\t\t\tif ( !upload.file.fromURL ) {\n\t\t\t\t\tupload.getThumbnail( 150, 150 ).done( ( thumb ) => {\n\t\t\t\t\t\tmw.UploadWizard.placeThumbnail( $element, thumb );\n\t\t\t\t\t} );\n\n\t\t\t\t\t$thumbContainer.append( $element );\n\t\t\t\t}\n\n\t\t\t\tthis.$deedsContainer.append(\n\t\t\t\t\t$( '<div>' )\n\t\t\t\t\t\t.addClass( 'mwe-upwiz-deeds-individual' )\n\t\t\t\t\t\t.append(\n\t\t\t\t\t\t\t$thumbContainer,\n\t\t\t\t\t\t\tdeedChooser.$element\n\t\t\t\t\t\t)\n\t\t\t\t);\n\t\t\t\tdeedChooser.onLayoutReady();\n\t\t\t} );\n\t\t} );\n\t};\n\n\t/**\n\t * @param {boolean} visible\n\t */\n\tuw.ui.Deed.prototype.toggleNext = function ( visible ) {\n\t\tif ( visible ) {\n\t\t\tthis.nextButton.$element.show();\n\t\t} else {\n\t\t\tthis.nextButton.$element.hide();\n\t\t}\n\t};\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ui/steps/uw.ui.Details.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ui.Step' is undefined.","line":23,"column":1,"nodeType":"Block","endLine":23,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":108,"column":3,"nodeType":"CallExpression","endLine":145,"endColumn":6}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\t/**\n\t * Represents the UI for the wizard's Details step.\n\t *\n\t * @class\n\t * @extends uw.ui.Step\n\t */\n\tuw.ui.Details = function UWUIDetails() {\n\t\tconst startDetails = () => {\n\t\t\tthis.emit( 'start-details' );\n\t\t};\n\n\t\tuw.ui.Step.call(\n\t\t\tthis,\n\t\t\t'details'\n\t\t);\n\n\t\tthis.nextButton = new OO.ui.ButtonWidget( {\n\t\t\tlabel: mw.message( 'mwe-upwiz-publish-details' ).text(),\n\t\t\tflags: [ 'progressive', 'primary' ]\n\t\t} ).on( 'click', startDetails );\n\n\t\tthis.nextButtonDespiteFailures = new OO.ui.ButtonWidget( {\n\t\t\tlabel: mw.message( 'mwe-upwiz-next-file-despite-failures' ).text(),\n\t\t\tflags: [ 'progressive' ]\n\t\t} ).on( 'click', () => {\n\t\t\tthis.emit( 'finalize-details-after-removal' );\n\t\t} );\n\n\t\tthis.retryButtonSomeFailed = new OO.ui.ButtonWidget( {\n\t\t\tlabel: mw.message( 'mwe-upwiz-file-retry' ).text(),\n\t\t\tflags: [ 'progressive', 'primary' ]\n\t\t} ).on( 'click', startDetails );\n\n\t\tthis.retryButtonAllFailed = new OO.ui.ButtonWidget( {\n\t\t\tlabel: mw.message( 'mwe-upwiz-file-retry' ).text(),\n\t\t\tflags: [ 'progressive', 'primary' ]\n\t\t} ).on( 'click', startDetails );\n\n\t\tthis.addPreviousButton();\n\t\tthis.addNextButton();\n\t};\n\n\tOO.inheritClass( uw.ui.Details, uw.ui.Step );\n\n\tuw.ui.Details.prototype.load = function ( uploads ) {\n\t\tuw.ui.Step.prototype.load.call( this, uploads );\n\n\t\tif ( mw.UploadWizard.config.wikibase.enabled && mw.UploadWizard.config.wikibase.captions ) {\n\t\t\tthis.$div.prepend(\n\t\t\t\t$( '<div>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-license-metadata ui-corner-all' )\n\t\t\t\t\t.append(\n\t\t\t\t\t\t$( '<strong>' ).text( mw.msg( 'mwe-upwiz-license-metadata-title-v2' ) ),\n\t\t\t\t\t\t' ',\n\t\t\t\t\t\t$( '<span>' ).append( mw.message( 'mwe-upwiz-license-metadata-content' ).parseDom() )\n\t\t\t\t\t\t\t// wikitext links in i18n messages don't support target=_blank, but we\n\t\t\t\t\t\t\t// really don't want to take people away from their uploads...\n\t\t\t\t\t\t\t.find( 'a' ).attr( 'target', '_blank' ).end()\n\t\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\tif ( uploads.filter( this.needsPatentAgreement.bind( this ) ).length > 0 ) {\n\t\t\tthis.$div.prepend(\n\t\t\t\t$( '<div>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-patent-weapon-policy ui-corner-all' )\n\t\t\t\t\t.append(\n\t\t\t\t\t\t$( '<p>' ).text( mw.msg( 'mwe-upwiz-patent-weapon-policy' ) ),\n\t\t\t\t\t\t$( '<p>' ).append(\n\t\t\t\t\t\t\t$( '<a>' )\n\t\t\t\t\t\t\t\t.text( mw.msg( 'mwe-upwiz-patent-weapon-policy-link' ) )\n\t\t\t\t\t\t\t\t.attr( { target: '_blank', href: mw.UploadWizard.config.patents.url.weapons } )\n\t\t\t\t\t\t)\n\t\t\t\t\t)\n\t\t\t);\n\t\t}\n\n\t\tthis.$div.prepend(\n\t\t\t$( '<div>' )\n\t\t\t\t.attr( 'id', 'mwe-upwiz-macro-files' )\n\t\t\t\t.addClass( 'mwe-upwiz-filled-filelist ui-corner-all' )\n\t\t);\n\n\t\t// set default buttons visibility (can be altered in controller later)\n\t\tthis.$div.find( '.mwe-upwiz-file-next-some-failed, .mwe-upwiz-file-next-all-failed' ).hide();\n\t\tthis.$div.find( '.mwe-upwiz-file-next-all-ok' ).show();\n\t};\n\n\tuw.ui.Details.prototype.addNextButton = function () {\n\t\tthis.nextButtonPromise.done( () => {\n\t\t\tthis.$buttons.append(\n\t\t\t\t$( '<div>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-file-next-all-ok mwe-upwiz-file-endchoice' )\n\t\t\t\t\t.append( this.nextButton.$element )\n\t\t\t);\n\n\t\t\tthis.$buttons.append(\n\t\t\t\t$( '<div>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-file-next-some-failed mwe-upwiz-file-endchoice' )\n\t\t\t\t\t.append(\n\t\t\t\t\t\tnew OO.ui.HorizontalLayout( {\n\t\t\t\t\t\t\titems: [\n\t\t\t\t\t\t\t\tnew OO.ui.LabelWidget( {\n\t\t\t\t\t\t\t\t\tlabel: mw.message( 'mwe-upwiz-file-some-failed' ).text()\n\t\t\t\t\t\t\t\t} ),\n\t\t\t\t\t\t\t\tthis.nextButtonDespiteFailures,\n\t\t\t\t\t\t\t\tthis.retryButtonSomeFailed\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t} ).$element\n\t\t\t\t\t)\n\t\t\t);\n\n\t\t\tthis.$buttons.append(\n\t\t\t\t$( '<div>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-file-next-all-failed mwe-upwiz-file-endchoice' )\n\t\t\t\t\t.append(\n\t\t\t\t\t\tnew OO.ui.HorizontalLayout( {\n\t\t\t\t\t\t\titems: [\n\t\t\t\t\t\t\t\tnew OO.ui.LabelWidget( {\n\t\t\t\t\t\t\t\t\tlabel: mw.message( 'mwe-upwiz-file-all-failed' ).text()\n\t\t\t\t\t\t\t\t} ),\n\t\t\t\t\t\t\t\tthis.retryButtonAllFailed\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t} ).$element\n\t\t\t\t\t)\n\t\t\t);\n\t\t} );\n\t};\n\n\t/**\n\t * Hide buttons for moving to the next step.\n\t */\n\tuw.ui.Details.prototype.hideEndButtons = function () {\n\t\tthis.$div\n\t\t\t.find( '.mwe-upwiz-buttons .mwe-upwiz-file-endchoice' )\n\t\t\t.hide();\n\t};\n\n\t/**\n\t * Disable edits to the details.\n\t */\n\tuw.ui.Details.prototype.disableEdits = function () {\n\t\tthis.$div\n\t\t\t.find( '.mwe-upwiz-data' )\n\t\t\t.morphCrossfade( '.mwe-upwiz-submitting' );\n\n\t\tthis.previousButton.$element.hide();\n\t\tthis.$div.find( '.mwe-upwiz-patent-weapon-policy' ).hide();\n\t};\n\n\t/**\n\t * Re-enabled edits to the details.\n\t */\n\tuw.ui.Details.prototype.enableEdits = function () {\n\t\tthis.previousButton.$element.show();\n\t\tthis.$div.find( '.mwe-upwiz-patent-weapon-policy' ).show();\n\t};\n\n\t/**\n\t * @param {mw.UploadWizardUpload} upload\n\t * @return {boolean}\n\t */\n\tuw.ui.Details.prototype.needsPatentAgreement = function ( upload ) {\n\t\tconst extensions = mw.UploadWizard.config.patents.extensions;\n\n\t\treturn extensions.includes( upload.title.getExtension().toLowerCase() );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ui/steps/uw.ui.Thanks.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ui.Step' is undefined.","line":23,"column":1,"nodeType":"Block","endLine":23,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":150,"column":3,"nodeType":"CallExpression","endLine":152,"endColumn":6},{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":180,"column":4,"nodeType":"CallExpression","endLine":180,"endColumn":94}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\t/**\n\t * Represents the UI for the wizard's Thanks step.\n\t *\n\t * @class\n\t * @extends uw.ui.Step\n\t * @param {Object} config\n\t */\n\tuw.ui.Thanks = function UWUIThanks( config ) {\n\t\tthis.config = config;\n\n\t\tuw.ui.Step.call(\n\t\t\tthis,\n\t\t\t'thanks'\n\t\t);\n\n\t\tthis.$div.prepend(\n\t\t\t$( '<div>' ).attr( 'id', 'mwe-upwiz-thanks' )\n\t\t);\n\n\t\tif ( this.isObjectReferenceGiven() ) {\n\t\t\tthis.getDelayNotice().prependTo( this.$div );\n\t\t}\n\n\t\tconst thanksMessage = new OO.ui.MessageWidget( {\n\t\t\ttype: 'success',\n\t\t\tlabel: ( this.config.display && this.config.display.thanksLabel ) ?\n\t\t\t\tnew OO.ui.HtmlSnippet( this.config.display.thanksLabel ) :\n\t\t\t\tmw.msg( 'mwe-upwiz-thanks-message' ),\n\t\t\tclasses: [ 'mwe-upwiz-thanks-message' ]\n\t\t} );\n\t\tthanksMessage.$element.prependTo( this.$div );\n\n\t\tconst homeButtonTarget = this.getButtonConfig( 'homeButton', 'target' );\n\t\tlet homeButtonHref;\n\t\tif ( !homeButtonTarget ) {\n\t\t\thomeButtonHref = mw.config.get( 'wgArticlePath' ).replace( '$1', '' );\n\t\t} else if ( homeButtonTarget === 'useObjref' ) {\n\t\t\thomeButtonHref = homeButtonTarget;\n\t\t} else {\n\t\t\ttry {\n\t\t\t\tconst homeButtonUrl = new URL( homeButtonTarget );\n\t\t\t\t// URL parsing went fine: check the protocol.\n\t\t\t\t// If `homeButtonTarget` is a wiki page in a non-main namespace,\n\t\t\t\t// it will still be parsed into a URL with protocol == namespace.\n\t\t\t\tif ( homeButtonUrl.protocol.startsWith( 'http' ) ) {\n\t\t\t\t\t// HTTP URL: as is\n\t\t\t\t\thomeButtonHref = homeButtonUrl.href;\n\t\t\t\t} else {\n\t\t\t\t\t// Page title in a non-main namespace\n\t\t\t\t\thomeButtonHref = mw.config\n\t\t\t\t\t\t.get( 'wgArticlePath' )\n\t\t\t\t\t\t.replace( '$1', homeButtonTarget );\n\t\t\t\t}\n\t\t\t} catch ( error ) {\n\t\t\t\t// Not a URL: assume a page title\n\t\t\t\thomeButtonHref = mw.config\n\t\t\t\t\t.get( 'wgArticlePath' )\n\t\t\t\t\t.replace( '$1', homeButtonTarget );\n\t\t\t}\n\t\t}\n\t\tthis.homeButton = new OO.ui.ButtonWidget( {\n\t\t\tlabel: this.getButtonConfig( 'homeButton', 'label' ) || mw.message( 'mwe-upwiz-home' ).text(),\n\t\t\thref: homeButtonHref\n\t\t} );\n\n\t\tthis.beginButton = new OO.ui.ButtonWidget( {\n\t\t\tlabel: this.getButtonConfig( 'beginButton', 'label' ) || mw.message( 'mwe-upwiz-upload-another' ).text(),\n\t\t\tflags: [ 'progressive', 'primary' ]\n\t\t} );\n\n\t\t// TODO: make the step order configurable by campaign definitions instead of using these hacks\n\t\tlet beginButtonTarget = this.getButtonConfig( 'beginButton', 'target' );\n\t\tif ( !beginButtonTarget || ( beginButtonTarget === 'dropObjref' && !this.isObjectReferenceGiven() ) ) {\n\t\t\tthis.beginButton.on( 'click', () => {\n\t\t\t\tthis.emit( 'next-step' );\n\t\t\t} );\n\t\t} else {\n\t\t\tif ( beginButtonTarget === 'dropObjref' ) {\n\t\t\t\tbeginButtonTarget = this.dropParameterFromURL( location.href, 'updateList' );\n\t\t\t}\n\t\t\tthis.beginButton.setHref( beginButtonTarget );\n\t\t}\n\t\tthis.beginButton.on( 'click', () => {\n\t\t\tmw.DestinationChecker.clearCache();\n\t\t} );\n\n\t\tthis.buttonGroup = new OO.ui.HorizontalLayout( {\n\t\t\titems: [ this.homeButton, this.beginButton ]\n\t\t} );\n\n\t\tthis.$buttons.append( this.buttonGroup.$element );\n\t};\n\n\tOO.inheritClass( uw.ui.Thanks, uw.ui.Step );\n\n\t/**\n\t * Adds an upload to the Thanks interface.\n\t *\n\t * @param {mw.UploadWizardUpload} upload\n\t */\n\tuw.ui.Thanks.prototype.addUpload = function ( upload ) {\n\t\tconst thumbWikiText = '[[' + [\n\t\t\tupload.details.getTitle().getPrefixedText(),\n\t\t\t'thumb',\n\t\t\tupload.details.getThumbnailCaption()\n\t\t].join( '|' ) + ']]';\n\n\t\tconst $thanksDiv = $( '<div>' )\n\t\t\t.addClass( 'mwe-upwiz-thanks ui-helper-clearfix' );\n\t\tconst $thumbnailWrapDiv = $( '<div>' )\n\t\t\t.addClass( 'mwe-upwiz-thumbnail-side' )\n\t\t\t.appendTo( $thanksDiv );\n\t\tconst $thumbnailDiv = $( '<div>' )\n\t\t\t.addClass( 'mwe-upwiz-thumbnail' )\n\t\t\t.appendTo( $thumbnailWrapDiv );\n\t\tconst $thumbnailCaption = $( '<div>' )\n\t\t\t.css( { 'text-align': 'left', 'font-size': 'small' } )\n\t\t\t.appendTo( $thumbnailWrapDiv );\n\t\tconst $thumbnailLink = $( '<a>' )\n\t\t\t.text( upload.details.getTitle().getMainText() )\n\t\t\t.appendTo( $thumbnailCaption );\n\n\t\t$( '<div>' )\n\t\t\t.addClass( 'mwe-upwiz-data' )\n\t\t\t.appendTo( $thanksDiv )\n\t\t\t.append(\n\t\t\t\tthis.makeReadOnlyInput( thumbWikiText, mw.message( 'mwe-upwiz-thanks-wikitext' ).text(), true ),\n\t\t\t\tthis.makeReadOnlyInput( upload.imageinfo.descriptionurl, mw.message( 'mwe-upwiz-thanks-url' ).text() )\n\t\t\t);\n\n\t\t// This must match the CSS dimensions of .mwe-upwiz-thumbnail\n\t\tupload.getThumbnail( 200, 200 ).done( ( thumb ) => {\n\t\t\tmw.UploadWizard.placeThumbnail( $thumbnailDiv, thumb );\n\t\t} );\n\n\t\t// Set the thumbnail links so that they point to the image description page\n\t\t$thumbnailLink.add( $thumbnailDiv.find( '.mwe-upwiz-thumbnail-link' ) ).attr( {\n\t\t\thref: upload.imageinfo.descriptionurl,\n\t\t\ttarget: '_blank'\n\t\t} );\n\n\t\tthis.$div.find( '.mwe-upwiz-buttons' ).before( $thanksDiv );\n\t};\n\n\t/**\n\t * Make an mw.widgets.CopyTextLayout, which features a button\n\t * to copy the text provided.\n\t *\n\t * @param {string} value Text it will contain\n\t * @param {string} label Label\n\t * @param {string} [useEditFont] Use edit font (for wikitext values)\n\t * @return {jQuery}\n\t */\n\tuw.ui.Thanks.prototype.makeReadOnlyInput = function ( value, label, useEditFont ) {\n\t\tconst copyText = new mw.widgets.CopyTextLayout( {\n\t\t\talign: 'top',\n\t\t\tlabel: label,\n\t\t\tcopyText: value\n\t\t} );\n\n\t\tif ( useEditFont ) {\n\t\t\tcopyText.textInput.$element.addClass( 'mw-editfont-' + mw.user.options.get( 'editfont' ) );\n\t\t}\n\n\t\treturn copyText.$element;\n\t};\n\n\t/**\n\t * Get button configuration options from a campaign definition\n\t *\n\t * @param {string} buttonName name of the button as defined in campaign configuration\n\t * @param {string} configField name of the button's attributes\n\t * @return {Object|undefined}\n\t */\n\tuw.ui.Thanks.prototype.getButtonConfig = function ( buttonName, configField ) {\n\t\tif ( !this.config.display || !this.config.display[ buttonName ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\treturn this.config.display[ buttonName ][ configField ];\n\t};\n\n\t/**\n\t * Drops a parameter from the given url\n\t *\n\t * @param {string} url URL from which to drop a parameter\n\t * @param {string} paramName parameter to be dropped\n\t * @return {string}\n\t * @private\n\t */\n\tuw.ui.Thanks.prototype.dropParameterFromURL = function ( url, paramName ) {\n\t\tconst newUrl = new URL( url );\n\t\tnewUrl.searchParams.delete( paramName );\n\t\tnewUrl.searchParams.delete( paramName + '[]' );\n\t\treturn newUrl.toString();\n\t};\n\n\tuw.ui.Thanks.prototype.getDelayNotice = function () {\n\t\tconst $delayNotice = $( '<p>' )\n\t\t\t.addClass( 'mwe-upwiz-thanks-update-delay' )\n\t\t\t.msg( 'mwe-upwiz-objref-notice-update-delay' );\n\n\t\tif ( this.config.display && this.config.display.noticeUpdateDelay ) {\n\t\t\t$delayNotice.html( this.config.display.noticeUpdateDelay );\n\t\t}\n\t\treturn $delayNotice;\n\t};\n\n\tuw.ui.Thanks.prototype.isObjectReferenceGiven = function () {\n\t\treturn this.config.defaults && this.config.defaults.objref !== '';\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ui/steps/uw.ui.Tutorial.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ui.Step' is undefined.","line":50,"column":1,"nodeType":"Block","endLine":50,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":126,"column":3,"nodeType":"CallExpression","endLine":132,"endColumn":6}],"suppressedMessages":[{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":84,"column":24,"nodeType":"CallExpression","endLine":84,"endColumn":55,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":88,"column":3,"nodeType":"CallExpression","endLine":88,"endColumn":38,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":2,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\t/**\n\t * Checkbox with popup information.\n\t *\n\t * @param {Object} config\n\t */\n\tfunction PopupCheckboxInputWidget( config ) {\n\t\t// Parent constructor\n\t\tPopupCheckboxInputWidget.super.call( this, config );\n\n\t\t// Mixin constructors\n\t\tOO.ui.mixin.PopupElement.call( this, config );\n\n\t\t// Events\n\t\tthis.connect( this, { change: 'onChange' } );\n\n\t\t// Initialization\n\t\tthis.$element\n\t\t\t.addClass( 'oo-ui-popupCheckboxInputWidget' )\n\t\t\t.attr( 'aria-haspopup', 'true' )\n\t\t\t.append( this.popup.$element );\n\t}\n\tOO.inheritClass( PopupCheckboxInputWidget, OO.ui.CheckboxInputWidget );\n\tOO.mixinClass( PopupCheckboxInputWidget, OO.ui.mixin.PopupElement );\n\tPopupCheckboxInputWidget.prototype.onChange = function () {\n\t\tthis.popup.toggle( this.isSelected() );\n\t};\n\n\t/**\n\t * Represents the UI for the wizard's Tutorial step.\n\t *\n\t * @class\n\t * @extends uw.ui.Step\n\t */\n\tuw.ui.Tutorial = function UWUITutorial() {\n\t\tuw.ui.Step.call(\n\t\t\tthis,\n\t\t\t'tutorial'\n\t\t);\n\n\t\t// 'Skip tutorial' checkbox\n\t\tthis.skipCheckbox = new PopupCheckboxInputWidget( {\n\t\t\tid: 'mwe-upwiz-skip',\n\t\t\t// Add a friendly \"Here's how to get it back\" tooltip for users who check the \"Skip next time\" checkbox\n\t\t\tpopup: {\n\t\t\t\t$content: $( '<p>' ).msg(\n\t\t\t\t\t'mwe-upwiz-tooltip-skiptutorial',\n\t\t\t\t\tmw.config.get( 'wgServer' ) + mw.util.getUrl( 'Special:Preferences' ) + '#mw-prefsection-uploads',\n\t\t\t\t\tmw.message( 'prefs-uploads' ).text(),\n\t\t\t\t\tmw.message( 'prefs-upwiz-interface' ).text()\n\t\t\t\t),\n\t\t\t\tautoClose: false,\n\t\t\t\tpadded: true\n\t\t\t}\n\t\t} );\n\t\tthis.skipCheckboxLabel = new OO.ui.LabelWidget( {\n\t\t\tinput: this.skipCheckbox,\n\t\t\tlabel: mw.message( 'mwe-upwiz-skip-tutorial-future' ).text()\n\t\t} );\n\n\t\tthis.skipCheckbox.on( 'change', () => {\n\t\t\tthis.emit( 'skip-tutorial-click', this.skipCheckbox.isSelected() );\n\t\t} );\n\n\t\t// grab the tutorial HTML that was injected into this document\n\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\tthis.$tutorialHtml = $( '#mwe-upwiz-tutorial-html' );\n\n\t\t// Helpdesk link click\n\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t$( '#mwe-upwiz-tutorial-helpdesk' ).on( 'click', () => {\n\t\t\tthis.emit( 'helpdesk-click' );\n\t\t} );\n\n\t\tthis.addPreviousButton();\n\t\tthis.addNextButton();\n\t};\n\n\tOO.inheritClass( uw.ui.Tutorial, uw.ui.Step );\n\n\tuw.ui.Tutorial.prototype.setSelected = function ( selected ) {\n\t\tthis.skipCheckbox.setSelected( selected );\n\t};\n\n\tuw.ui.Tutorial.prototype.load = function ( uploads ) {\n\t\tuw.ui.Step.prototype.load.call( this, uploads );\n\n\t\tthis.$div.prepend(\n\t\t\t$( '<div>' )\n\t\t\t\t.attr( 'id', 'mwe-upwiz-tutorial' )\n\t\t\t\t.append(\n\t\t\t\t\t// TODO move this to JavaScript, too.\n\t\t\t\t\tthis.$tutorialHtml.show()\n\t\t\t\t)\n\t\t);\n\n\t\tthis.skipCheckbox.popup.updateDimensions();\n\t};\n\n\tuw.ui.Tutorial.prototype.addNextButton = function () {\n\t\tthis.nextButton = new OO.ui.ButtonWidget( {\n\t\t\tclasses: [ 'mwe-upwiz-button-next' ],\n\t\t\tlabel: mw.message( 'mwe-upwiz-next' ).text(),\n\t\t\tflags: [ 'progressive', 'primary' ]\n\t\t} ).on( 'click', () => {\n\t\t\tthis.emit( 'next-step' );\n\t\t} );\n\n\t\tthis.nextButtonPromise.done( () => {\n\t\t\tthis.$buttons.append(\n\t\t\t\tnew OO.ui.HorizontalLayout( {\n\t\t\t\t\titems: [ this.skipCheckbox, this.skipCheckboxLabel, this.nextButton ]\n\t\t\t\t} ).$element\n\t\t\t);\n\t\t} );\n\t};\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ui/steps/uw.ui.Upload.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ui.Step' is undefined.","line":23,"column":1,"nodeType":"Block","endLine":23,"endColumn":1},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":263,"column":39,"nodeType":"CallExpression","endLine":263,"endColumn":56},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":268,"column":33,"nodeType":"CallExpression","endLine":268,"endColumn":51},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":334,"column":6,"nodeType":"CallExpression","endLine":336,"endColumn":9},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":344,"column":3,"nodeType":"CallExpression","endLine":392,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":528,"column":3,"nodeType":"CallExpression","endLine":530,"endColumn":6}],"suppressedMessages":[{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Positional selector extensions are not allowed","line":228,"column":4,"nodeType":"CallExpression","endLine":228,"endColumn":39,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Positional selector extensions are not allowed","line":230,"column":4,"nodeType":"CallExpression","endLine":230,"endColumn":40,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":6,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\t/**\n\t * Represents the UI for the wizard's Upload step.\n\t *\n\t * @class\n\t * @extends uw.ui.Step\n\t * @param {Object} config UploadWizard config object.\n\t */\n\tuw.ui.Upload = function UWUIUpload( config ) {\n\t\tthis.config = config;\n\n\t\tuw.ui.Step.call(\n\t\t\tthis,\n\t\t\t'file'\n\t\t);\n\n\t\tthis.$addFileContainer = $( '<div>' )\n\t\t\t.attr( 'id', 'mwe-upwiz-add-file-container' )\n\t\t\t.addClass( 'mwe-upwiz-add-files-0' );\n\n\t\tthis.$uploadCtrl = $( '<div>' )\n\t\t\t.attr( 'id', 'mwe-upwiz-upload-ctrls' )\n\t\t\t.addClass( 'mwe-upwiz-file ui-helper-clearfix' )\n\t\t\t.append( this.$addFileContainer );\n\n\t\tthis.addFile = new OO.ui.SelectFileInputWidget( {\n\t\t\tclasses: [ 'mwe-upwiz-add-file' ],\n\t\t\tmultiple: true,\n\t\t\tshowDropTarget: true,\n\t\t\tbutton: {\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-add-file-0-free' ).text(),\n\t\t\t\tflags: [ 'progressive', 'primary' ]\n\t\t\t}\n\t\t} );\n\t\tthis.addFile.on( 'change', ( files ) => {\n\t\t\tthis.emit( 'files-added', files );\n\t\t\tthis.addFile.setValue( null );\n\t\t} );\n\n\t\tthis.$addFileContainer.append( this.addFile.$element );\n\n\t\tif ( this.isFlickrImportEnabled() ) {\n\t\t\tthis.$flickrAddFileContainer = $( '<div>' )\n\t\t\t\t.attr( 'id', 'mwe-upwiz-upload-ctrl-flickr-container' );\n\n\t\t\tthis.$uploadCenterDivide = $( '<p>' )\n\t\t\t\t.attr( 'id', 'mwe-upwiz-upload-ctr-divide' )\n\t\t\t\t.text( mw.message( 'mwe-upwiz-add-flickr-or' ).text() );\n\n\t\t\tthis.addFlickrFile = new OO.ui.ButtonWidget( {\n\t\t\t\tid: 'mwe-upwiz-add-flickr-file',\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-add-file-flickr' ).text(),\n\t\t\t\tflags: 'progressive'\n\t\t\t} ).on( 'click', () => {\n\t\t\t\tthis.flickrInterfaceInit();\n\t\t\t} );\n\n\t\t\tthis.$flickrAddFileContainer.append(\n\t\t\t\tthis.$uploadCenterDivide,\n\t\t\t\tthis.addFlickrFile.$element\n\t\t\t);\n\n\t\t\tthis.$addFileContainer\n\t\t\t\t.append( this.$flickrAddFileContainer );\n\n\t\t\tthis.$flickrSelectList = $( '<div>' )\n\t\t\t\t.attr( 'id', 'mwe-upwiz-flickr-select-list' );\n\n\t\t\tthis.$flickrSelectListContainer = $( '<div>' )\n\t\t\t\t.attr( 'id', 'mwe-upwiz-flickr-select-list-container' )\n\t\t\t\t.addClass( 'ui-corner-all' )\n\t\t\t\t.append(\n\t\t\t\t\t$( '<div>' )\n\t\t\t\t\t\t.text( mw.message(\n\t\t\t\t\t\t\t'mwe-upwiz-multi-file-select2',\n\t\t\t\t\t\t\tconfig.maxFlickrUploads\n\t\t\t\t\t\t) ),\n\t\t\t\t\tthis.$flickrSelectList\n\t\t\t\t);\n\n\t\t\t// Button to move on & upload the files that were selected\n\t\t\tthis.flickrSelectButton = new OO.ui.ButtonWidget( {\n\t\t\t\tid: 'mwe-upwiz-select-flickr',\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-add-file-0-free' ).text(),\n\t\t\t\tflags: [ 'progressive', 'primary' ]\n\t\t\t} );\n\t\t\tthis.$flickrSelectListContainer.append( this.flickrSelectButton.$element );\n\n\t\t\t// A container holding a form\n\t\t\tthis.$flickrContainer = $( '<div>' ).attr( 'id', 'mwe-upwiz-upload-add-flickr-container' );\n\n\t\t\t// Form whose submit event will be listened to and prevented\n\t\t\tthis.$flickrForm = $( '<form>' ).attr( 'id', 'mwe-upwiz-flickr-url-form' )\n\t\t\t\t.appendTo( this.$flickrContainer )\n\t\t\t\t.on( 'submit', () => {\n\t\t\t\t\tconst checker = new mw.FlickrChecker( this, this.flickrSelectButton );\n\t\t\t\t\tthis.flickrButton.setDisabled( true );\n\t\t\t\t\tthis.flickrChecker( checker );\n\t\t\t\t\t// TODO Any particular reason to stopPropagation ?\n\t\t\t\t\treturn false;\n\t\t\t\t} );\n\n\t\t\t// The input that will hold a flickr URL entered by the user; will be appended to a form\n\t\t\tthis.flickrInput = new OO.ui.TextInputWidget( {\n\t\t\t\tplaceholder: mw.message( 'mwe-upwiz-flickr-input-placeholder' ).text()\n\t\t\t} );\n\n\t\t\tthis.flickrButton = new OO.ui.ButtonInputWidget( {\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-add-flickr' ).text(),\n\t\t\t\tflags: [ 'progressive', 'primary' ],\n\t\t\t\ttype: 'submit'\n\t\t\t} );\n\n\t\t\tthis.flickrField = new OO.ui.ActionFieldLayout(\n\t\t\t\tthis.flickrInput, this.flickrButton, {\n\t\t\t\t\talign: 'top',\n\t\t\t\t\tclasses: [ 'mwe-upwiz-flickr-field' ]\n\t\t\t\t}\n\t\t\t);\n\n\t\t\tthis.$flickrForm.append( this.flickrField.$element );\n\n\t\t\t// Add disclaimer\n\t\t\t$( '<div>' ).attr( 'id', 'mwe-upwiz-flickr-disclaimer' )\n\t\t\t\t.append(\n\t\t\t\t\tmw.message( 'mwe-upwiz-flickr-disclaimer1' ).parseDom(),\n\t\t\t\t\t$( '<br>' ), mw.message( 'mwe-upwiz-flickr-disclaimer2' ).parseDom()\n\t\t\t\t)\n\t\t\t\t.appendTo( this.$flickrContainer );\n\t\t}\n\n\t\tthis.nextStepButtonAllOk = new OO.ui.ButtonWidget( {\n\t\t\tlabel: mw.message( 'mwe-upwiz-next-file' ).text(),\n\t\t\tflags: [ 'progressive', 'primary' ]\n\t\t} ).on( 'click', () => {\n\t\t\tthis.emit( 'next-step' );\n\t\t} );\n\n\t\tthis.retryButtonSomeFailed = new OO.ui.ButtonWidget( {\n\t\t\tlabel: mw.message( 'mwe-upwiz-file-retry' ).text(),\n\t\t\tflags: [ 'progressive' ]\n\t\t} ).on( 'click', () => {\n\t\t\tthis.hideEndButtons();\n\t\t\tthis.emit( 'retry' );\n\t\t} );\n\n\t\tthis.nextStepButtonSomeFailed = new OO.ui.ButtonWidget( {\n\t\t\tlabel: mw.message( 'mwe-upwiz-next-file-despite-failures' ).text(),\n\t\t\tflags: [ 'progressive', 'primary' ]\n\t\t} ).on( 'click', () => {\n\t\t\tthis.emit( 'next-step' );\n\t\t} );\n\n\t\tthis.retryButtonAllFailed = new OO.ui.ButtonWidget( {\n\t\t\tlabel: mw.message( 'mwe-upwiz-file-retry' ).text(),\n\t\t\tflags: [ 'progressive' ]\n\t\t} ).on( 'click', () => {\n\t\t\tthis.hideEndButtons();\n\t\t\tthis.emit( 'retry' );\n\t\t} );\n\n\t\tthis.$fileList = $( '<div>' )\n\t\t\t.attr( 'id', 'mwe-upwiz-filelist' )\n\t\t\t.addClass( 'ui-corner-all' );\n\n\t\tthis.$progress = $( '<div>' )\n\t\t\t.attr( 'id', 'mwe-upwiz-progress' )\n\t\t\t.addClass( 'ui-helper-clearfix' );\n\n\t\tthis.addPreviousButton();\n\t\tthis.addNextButton();\n\t};\n\n\tOO.inheritClass( uw.ui.Upload, uw.ui.Step );\n\n\tuw.ui.Upload.prototype.showProgressBar = function () {\n\t\tthis.$progress.show();\n\t};\n\n\t/**\n\t * Updates the interface based on the number of uploads.\n\t *\n\t * @param {boolean} haveUploads Whether there are any uploads at all.\n\t * @param {boolean} fewerThanMax Whether we can add more uploads.\n\t */\n\tuw.ui.Upload.prototype.updateFileCounts = function ( haveUploads, fewerThanMax ) {\n\t\tthis.$fileList.toggleClass( 'mwe-upwiz-filled-filelist', haveUploads );\n\t\tthis.$addFileContainer.toggleClass( 'mwe-upwiz-add-files-0', !haveUploads );\n\n\t\tthis.setAddButtonText( haveUploads );\n\n\t\tif ( haveUploads ) {\n\t\t\t// we have uploads ready to go, so allow us to proceed\n\t\t\tthis.$addFileContainer.add( this.$buttons ).show();\n\n\t\t\tif ( this.isFlickrImportEnabled() ) {\n\t\t\t\tthis.$uploadCenterDivide.hide();\n\t\t\t}\n\n\t\t\t// fix the rounded corners on file elements.\n\t\t\t// we want them to be rounded only when their edge touched the top or bottom of the filelist.\n\t\t\tthis.$fileListings = this.$fileList.find( '.filled' );\n\n\t\t\tthis.$visibleFileListings = this.$fileListings.find( '.mwe-upwiz-visible-file' );\n\t\t\tthis.$visibleFileListings.removeClass( 'ui-corner-top ui-corner-bottom' );\n\t\t\tthis.$visibleFileListings.first().addClass( 'ui-corner-top' );\n\t\t\tthis.$visibleFileListings.last().addClass( 'ui-corner-bottom' );\n\t\t\tthis.showNoticeForImageMetadata( true );\n\n\t\t\t// eslint-disable-next-line no-jquery/no-sizzle\n\t\t\tthis.$fileListings.filter( ':odd' ).addClass( 'odd' );\n\t\t\t// eslint-disable-next-line no-jquery/no-sizzle\n\t\t\tthis.$fileListings.filter( ':even' ).removeClass( 'odd' );\n\t\t} else {\n\t\t\tthis.hideEndButtons();\n\t\t\tthis.showNoticeForImageMetadata( false );\n\n\t\t\tif ( this.isFlickrImportEnabled() ) {\n\t\t\t\tthis.$uploadCenterDivide.show();\n\t\t\t}\n\t\t}\n\n\t\tthis.addFile.setDisabled( !fewerThanMax );\n\n\t\tif ( this.isFlickrImportEnabled() ) {\n\t\t\tthis.addFlickrFile.setDisabled( !fewerThanMax );\n\t\t}\n\t};\n\n\t/**\n\t * Changes the initial centered invitation button to something like \"add another file\"\n\t *\n\t * @param {boolean} more\n\t */\n\tuw.ui.Upload.prototype.setAddButtonText = function ( more ) {\n\t\tlet msg = 'mwe-upwiz-add-file-',\n\t\t\tfmsg = 'mwe-upwiz-add-file-flickr';\n\n\t\tif ( more ) {\n\t\t\tmsg += 'n';\n\t\t\tfmsg += '-n';\n\t\t} else {\n\t\t\tmsg += '0-free';\n\t\t}\n\n\t\tthis.addFile.selectButton.setLabel( mw.message( msg ).text() );\n\n\t\t// if Flickr uploading is available to this user, show the \"add more files from flickr\" button\n\t\tif ( this.isFlickrImportEnabled() ) {\n\t\t\t// changes the flickr add button to \"add more files from flickr\" if necessary.\n\t\t\tthis.addFlickrFile.setLabel( mw.message( fmsg ).text() );\n\t\t\t// jQuery likes to restore the wrong 'display' value when doing .show()\n\t\t\tthis.$flickrAddFileContainer.css( 'display', '' );\n\t\t}\n\t};\n\n\tuw.ui.Upload.prototype.load = function ( uploads ) {\n\t\tuw.ui.Step.prototype.load.call( this, uploads );\n\n\t\tif ( uploads.length === 0 ) {\n\t\t\tthis.$fileList.removeClass( 'mwe-upwiz-filled-filelist' );\n\t\t\tthis.$addFileContainer.show();\n\t\t}\n\n\t\tconst $noticeMessage = $( '<span>' )\n\t\t\t.append(\n\t\t\t\t$( '<strong>' ).text( mw.message( 'mwe-upwiz-metadata-notice-header' ).text() ),\n\t\t\t\t$( '<br>' ),\n\t\t\t\tmw.message( 'mwe-upwiz-metadata-notice-description' ).parseDom()\n\t\t\t);\n\n\t\tthis.notice = new OO.ui.MessageWidget( {\n\t\t\ttype: 'notice',\n\t\t\ticon: 'pageSettings',\n\t\t\tclasses: [ 'mwe-upwiz-metadata-notice' ],\n\t\t\tlabel: $noticeMessage\n\t\t} );\n\n\t\tthis.$div.prepend(\n\t\t\t$( '<div>' )\n\t\t\t\t.attr( 'id', 'mwe-upwiz-files' )\n\t\t\t\t.append(\n\t\t\t\t\tthis.$flickrSelectListContainer,\n\t\t\t\t\tthis.$fileList,\n\t\t\t\t\tthis.$uploadCtrl,\n\t\t\t\t\tthis.notice.$element\n\t\t\t\t)\n\t\t);\n\n\t\tthis.displayUploads( uploads );\n\t};\n\n\tuw.ui.Upload.prototype.displayUploads = function ( uploads ) {\n\t\tlet thumbPromise,\n\t\t\t$uploadInterfaceDivs = $( [] );\n\n\t\tuploads.forEach( ( upload ) => {\n\t\t\t// We'll attach all interfaces to the DOM at once rather than one-by-one, for better\n\t\t\t// performance\n\t\t\t$uploadInterfaceDivs = $uploadInterfaceDivs.add( upload.ui.$div );\n\t\t} );\n\n\t\t// Attach all interfaces to the DOM\n\t\tthis.$fileList.append( $uploadInterfaceDivs );\n\n\t\t// Display thumbnails, but not all at once because they're somewhat expensive to generate.\n\t\t// This will wait for each thumbnail to be complete before starting the next one.\n\t\tthumbPromise = $.Deferred().resolve();\n\t\tuploads.forEach( ( upload ) => {\n\t\t\tthumbPromise = thumbPromise.then( () => {\n\t\t\t\tconst deferred = $.Deferred();\n\t\t\t\tsetTimeout( function () {\n\t\t\t\t\tif ( this.movedFrom ) {\n\t\t\t\t\t\t// We're no longer displaying any of these thumbnails, stop\n\t\t\t\t\t\tdeferred.reject();\n\t\t\t\t\t}\n\t\t\t\t\tupload.ui.showThumbnail().done( () => {\n\t\t\t\t\t\tdeferred.resolve();\n\t\t\t\t\t} );\n\t\t\t\t} );\n\t\t\t\treturn deferred.promise();\n\t\t\t} );\n\t\t} );\n\t};\n\n\tuw.ui.Upload.prototype.addNextButton = function () {\n\t\tthis.nextButtonPromise.done( () => {\n\t\t\tthis.$buttons.append(\n\t\t\t\t$( '<div>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-file-next-all-ok mwe-upwiz-file-endchoice' )\n\t\t\t\t\t.append(\n\t\t\t\t\t\tnew OO.ui.HorizontalLayout( {\n\t\t\t\t\t\t\titems: [\n\t\t\t\t\t\t\t\tnew OO.ui.LabelWidget( {\n\t\t\t\t\t\t\t\t\tlabel: mw.message( 'mwe-upwiz-file-all-ok' ).text()\n\t\t\t\t\t\t\t\t} ),\n\t\t\t\t\t\t\t\tthis.nextStepButtonAllOk\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t} ).$element\n\t\t\t\t\t)\n\t\t\t);\n\n\t\t\tthis.$buttons.append(\n\t\t\t\t$( '<div>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-file-next-some-failed mwe-upwiz-file-endchoice' )\n\t\t\t\t\t.append(\n\t\t\t\t\t\tnew OO.ui.HorizontalLayout( {\n\t\t\t\t\t\t\titems: [\n\t\t\t\t\t\t\t\tnew OO.ui.LabelWidget( {\n\t\t\t\t\t\t\t\t\tlabel: mw.message( 'mwe-upwiz-file-some-failed' ).text()\n\t\t\t\t\t\t\t\t} ),\n\t\t\t\t\t\t\t\tthis.retryButtonSomeFailed,\n\t\t\t\t\t\t\t\tthis.nextStepButtonSomeFailed\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t} ).$element\n\t\t\t\t\t)\n\t\t\t);\n\n\t\t\tthis.$buttons.append(\n\t\t\t\t$( '<div>' )\n\t\t\t\t\t.addClass( 'mwe-upwiz-file-next-all-failed mwe-upwiz-file-endchoice' )\n\t\t\t\t\t.append(\n\t\t\t\t\t\tnew OO.ui.HorizontalLayout( {\n\t\t\t\t\t\t\titems: [\n\t\t\t\t\t\t\t\tnew OO.ui.LabelWidget( {\n\t\t\t\t\t\t\t\t\tlabel: mw.message( 'mwe-upwiz-file-all-failed' ).text()\n\t\t\t\t\t\t\t\t} ),\n\t\t\t\t\t\t\t\tthis.retryButtonAllFailed\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t} ).$element\n\t\t\t\t\t)\n\t\t\t);\n\n\t\t\tthis.$buttons.append( this.$progress );\n\t\t} );\n\t};\n\n\t/**\n\t * Hide the buttons for moving to the next step.\n\t */\n\tuw.ui.Upload.prototype.hideEndButtons = function () {\n\t\tthis.$div\n\t\t\t.find( '.mwe-upwiz-buttons .mwe-upwiz-file-endchoice' )\n\t\t\t.hide();\n\t};\n\n\t/**\n\t * @param {boolean} show\n\t */\n\tuw.ui.Upload.prototype.showNoticeForImageMetadata = function ( show ) {\n\t\tthis.$div.find( '.mwe-upwiz-metadata-notice' ).toggle( show );\n\t};\n\n\t/**\n\t * Shows an error dialog informing the user that some uploads have been omitted\n\t * since they went over the max files limit.\n\t *\n\t * @param {number} filesUploaded The number of files that have been attempted to upload\n\t */\n\tuw.ui.Upload.prototype.showTooManyFilesError = function ( filesUploaded ) {\n\t\tmw.errorDialog(\n\t\t\tmw.message(\n\t\t\t\t'mwe-upwiz-too-many-files-text',\n\t\t\t\tthis.config.maxUploads,\n\t\t\t\tfilesUploaded\n\t\t\t).text(),\n\t\t\tmw.message( 'mwe-upwiz-too-many-files' ).text()\n\t\t);\n\t};\n\n\t/**\n\t * Shows an error dialog informing the user that an upload omitted because\n\t * it is too large.\n\t *\n\t * @param {number} maxSize The max upload file size\n\t * @param {number} size The actual upload file size\n\t */\n\tuw.ui.Upload.prototype.showFileTooLargeError = function ( maxSize, size ) {\n\t\tmw.errorDialog(\n\t\t\tmw.message(\n\t\t\t\t'mwe-upwiz-file-too-large-text',\n\t\t\t\tuw.units.bytes( maxSize ),\n\t\t\t\tuw.units.bytes( size )\n\t\t\t).text(),\n\t\t\tmw.message( 'mwe-upwiz-file-too-large' ).text()\n\t\t);\n\t};\n\n\t/**\n\t * @param {string} filename\n\t * @param {string} extension\n\t */\n\tuw.ui.Upload.prototype.showBadExtensionError = function ( filename, extension ) {\n\t\tconst $errorMessage = $( '<p>' ).msg( 'mwe-upwiz-upload-error-bad-filename-extension', extension );\n\t\tthis.showFilenameError( $errorMessage );\n\t};\n\n\tuw.ui.Upload.prototype.showMissingExtensionError = function () {\n\t\tconst $errorMessage = $( '<p>' ).msg( 'mwe-upwiz-upload-error-bad-filename-no-extension' );\n\t\tthis.showFilenameError(\n\t\t\t$( '<div>' ).append(\n\t\t\t\t$errorMessage,\n\t\t\t\t$( '<p>' ).msg( 'mwe-upwiz-allowed-filename-extensions' ),\n\t\t\t\t$( '<blockquote>' ).append( $( '<tt>' ).append(\n\t\t\t\t\tmw.UploadWizard.config.fileExtensions.join( ' ' )\n\t\t\t\t) )\n\t\t\t)\n\t\t);\n\t};\n\n\t/**\n\t * @param {string} filename\n\t * @param {string} basename\n\t */\n\tuw.ui.Upload.prototype.showDuplicateError = function ( filename, basename ) {\n\t\tthis.showFilenameError( $( '<p>' ).msg( 'mwe-upwiz-upload-error-duplicate-filename-error', basename ) );\n\t};\n\n\t/**\n\t * @param {string} filename\n\t */\n\tuw.ui.Upload.prototype.showUnparseableFilenameError = function ( filename ) {\n\t\tthis.showFilenameError( mw.message( 'mwe-upwiz-unparseable-filename', filename ).escaped() );\n\t};\n\n\t/**\n\t * Shows an error dialog informing the user that an upload has been omitted\n\t * over its filename.\n\t *\n\t * @param {jQuery|string} message The error message\n\t */\n\tuw.ui.Upload.prototype.showFilenameError = function ( message ) {\n\t\tmw.errorDialog( message );\n\t};\n\n\t/**\n\t * Checks whether flickr import is enabled and the current user has the rights to use it\n\t *\n\t * @return {boolean}\n\t */\n\tuw.ui.Upload.prototype.isFlickrImportEnabled = function () {\n\t\treturn this.config.UploadFromUrl && this.config.flickrApiKey !== '';\n\t};\n\n\t/**\n\t * Initiates the Interface to upload media from Flickr.\n\t * Called when the user clicks on the 'Add images from Flickr' button.\n\t */\n\tuw.ui.Upload.prototype.flickrInterfaceInit = function () {\n\t\t// Hide containers for selecting files, and show the flickr interface instead\n\t\tthis.$addFileContainer.hide();\n\t\tthis.$flickrAddFileContainer.hide();\n\t\tthis.$flickrContainer.show();\n\t\tthis.flickrSelectButton.$element.show();\n\t\tthis.flickrButton.setDisabled( false );\n\n\t\t// Insert form into the page\n\t\tthis.$div.find( '#mwe-upwiz-files' ).prepend( this.$flickrContainer );\n\n\t\tthis.flickrInput.focus();\n\t};\n\n\t/**\n\t * Responsible for fetching license of the provided media.\n\t *\n\t * @param {mw.FlickrChecker} checker\n\t */\n\tuw.ui.Upload.prototype.flickrChecker = function ( checker ) {\n\t\tconst flickrInputUrl = this.flickrInput.getValue();\n\n\t\tchecker.getLicenses().done( () => {\n\t\t\tchecker.checkFlickr( flickrInputUrl );\n\t\t} );\n\t};\n\n\t/**\n\t * Reset the interface if there is a problem while fetching the images from\n\t * the URL entered by the user.\n\t */\n\tuw.ui.Upload.prototype.flickrInterfaceReset = function () {\n\t\t// first destroy it completely, then reshow the add button\n\t\tthis.flickrInterfaceDestroy();\n\t\tthis.flickrButton.setDisabled( false );\n\t\tthis.$flickrContainer.show();\n\t\tthis.flickrSelectButton.$element.show();\n\t};\n\n\t/**\n\t * Removes the flickr interface.\n\t */\n\tuw.ui.Upload.prototype.flickrInterfaceDestroy = function () {\n\t\tthis.flickrInput.setValue( '' );\n\t\tthis.$flickrSelectList.empty();\n\t\tthis.$flickrSelectListContainer.off().hide();\n\t\tthis.$flickrContainer.hide();\n\t\tthis.flickrButton.setDisabled( true );\n\t\tthis.flickrSelectButton.$element.hide();\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ui/uw.ui.Step.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":111,"column":3,"nodeType":"CallExpression","endLine":113,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":120,"column":3,"nodeType":"CallExpression","endLine":122,"endColumn":6},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":166,"column":12,"nodeType":"CallExpression","endLine":166,"endColumn":90},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":180,"column":7,"nodeType":"CallExpression","endLine":180,"endColumn":61},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":197,"column":5,"nodeType":"CallExpression","endLine":197,"endColumn":57}],"suppressedMessages":[{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":39,"column":3,"nodeType":"CallExpression","endLine":39,"endColumn":28,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":73,"column":18,"nodeType":"CallExpression","endLine":73,"endColumn":27,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":84,"column":3,"nodeType":"CallExpression","endLine":84,"endColumn":20,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Selector extensions are not allowed","line":136,"column":35,"nodeType":"CallExpression","endLine":136,"endColumn":106,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":142,"column":4,"nodeType":"CallExpression","endLine":142,"endColumn":21,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Selector extensions are not allowed","line":176,"column":11,"nodeType":"CallExpression","endLine":176,"endColumn":40,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Selector extensions are not allowed","line":182,"column":7,"nodeType":"CallExpression","endLine":182,"endColumn":45,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-unused-expressions","severity":2,"message":"Expected an assignment or function call and instead saw an expression.","line":221,"column":3,"nodeType":"ExpressionStatement","messageId":"unusedExpression","endLine":221,"endColumn":40,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\t/**\n\t * Represents a generic UI for a step.\n\t *\n\t * @class\n\t * @mixes OO.EventEmitter\n\t * @param {string} name The name of this step\n\t */\n\tuw.ui.Step = function UWUIStep( name ) {\n\t\tOO.EventEmitter.call( this );\n\n\t\tthis.name = name;\n\n\t\tthis.$buttons = $( '<div>' ).addClass( 'mwe-upwiz-buttons' );\n\n\t\tthis.$div = $( '<div>' )\n\t\t\t.attr( 'id', 'mwe-upwiz-stepdiv-' + this.name )\n\t\t\t.addClass( 'mwe-upwiz-stepdiv' )\n\t\t\t.hide();\n\n\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t$( '#mwe-upwiz-content' ).append( this.$div );\n\n\t\tthis.nextButton = new OO.ui.ButtonWidget( {\n\t\t\tclasses: [ 'mwe-upwiz-button-next' ],\n\t\t\tlabel: mw.message( 'mwe-upwiz-next' ).text(),\n\t\t\tflags: [ 'progressive', 'primary' ]\n\t\t} ).on( 'click', () => {\n\t\t\tthis.emit( 'next-step' );\n\t\t} );\n\t\tthis.previousButton = new OO.ui.ButtonWidget( {\n\t\t\tclasses: [ 'mwe-upwiz-button-previous' ],\n\t\t\tlabel: mw.message( 'mwe-upwiz-previous' ).text()\n\t\t} ).on( 'click', () => {\n\t\t\tthis.emit( 'previous-step' );\n\t\t} );\n\n\t\t// this will make sure that buttons will only be added if they've been\n\t\t// set in the controller, otherwise there's nowhere to go...\n\t\tthis.nextButtonPromise = $.Deferred();\n\t\tthis.previousButtonPromise = $.Deferred();\n\n\t\tthis.$errorCount = $( '<div>' ).attr( 'id', 'mwe-upwiz-details-error-count' );\n\t\tthis.$buttons.append( this.$errorCount );\n\t};\n\n\tOO.mixinClass( uw.ui.Step, OO.EventEmitter );\n\n\t/**\n\t * Initialize this step.\n\t *\n\t * @param {mw.UploadWizardUpload[]} uploads\n\t */\n\tuw.ui.Step.prototype.load = function ( uploads ) {\n\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\tconst offset = $( 'h1' ).first().offset();\n\n\t\tthis.movedFrom = false;\n\n\t\tthis.uploads = uploads;\n\t\tthis.$div.append( this.$buttons ).show();\n\n\t\t// clear any errors that may have been visible\n\t\tthis.updateErrorSummary();\n\n\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t$( 'html, body' ).animate( {\n\t\t\tscrollTop: offset.top,\n\t\t\tscrollLeft: offset.left\n\t\t}, 'slow' );\n\t};\n\n\t/**\n\t * Cleanup this step.\n\t */\n\tuw.ui.Step.prototype.unload = function () {\n\t\tthis.movedFrom = true;\n\n\t\tthis.$div.children().detach();\n\t};\n\n\tuw.ui.Step.prototype.enableNextButton = function () {\n\t\tthis.nextButtonPromise.resolve();\n\t};\n\n\tuw.ui.Step.prototype.enablePreviousButton = function () {\n\t\tthis.previousButtonPromise.resolve();\n\t};\n\n\t/**\n\t * Add a 'next' button to the step's button container\n\t */\n\tuw.ui.Step.prototype.addNextButton = function () {\n\t\tthis.nextButtonPromise.done( () => {\n\t\t\tthis.$buttons.append( this.nextButton.$element );\n\t\t} );\n\t};\n\n\t/**\n\t * Add a 'previous' button to the step's button container\n\t */\n\tuw.ui.Step.prototype.addPreviousButton = function () {\n\t\tthis.previousButtonPromise.done( () => {\n\t\t\tthis.$buttons.append( this.previousButton.$element );\n\t\t} );\n\t};\n\n\t/**\n\t * Show errors/warnings/notices in the form.\n\t * Some pages can be vertically long, so sometimes it is not obvious there are errors above.\n\t * This counts them and puts the count right next to the submit button,\n\t * so it should be obvious to the user they need to fix things.\n\t * This is a bit of a hack. We should already know how many errors there are, and where.\n\t * This method also opens up collapsed elements if the form has errors.\n\t */\n\n\tuw.ui.Step.prototype.updateErrorSummary = function () {\n\t\t// eslint-disable-next-line no-jquery/no-sizzle\n\t\tconst getElements = ( kind ) => this.$div.find( '.mwe-upwiz-fieldLayout-' + kind ).filter( ':visible' );\n\t\tconst uploadCount = ( this.uploads || [] ).length;\n\n\t\tconst scrollToFirst = ( $elements ) => {\n\t\t\t// Immediately stop existing animations, then scroll to error\n\t\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\t\t$( 'html, body' ).stop().animate( { scrollTop: $elements.eq( 0 ).offset().top - 50 }, 'slow' );\n\t\t};\n\n\t\tconst updateSummary = ( kind, message, $elements ) => {\n\t\t\tconst errorCount = $elements.length;\n\n\t\t\t// reset to pristine state: no error, no scroll button, visible next button\n\t\t\tthis.$errorCount.empty();\n\t\t\tthis.$div.find( '.mwe-upwiz-details-error-scroll' ).remove();\n\t\t\tthis.nextButton.$element.show();\n\n\t\t\tif ( errorCount === 0 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst warningWidget = new OO.ui.MessageWidget( {\n\t\t\t\ttype: kind,\n\t\t\t\tinline: true,\n\t\t\t\tlabel: message.params( [ errorCount, uploadCount ] ).text()\n\t\t\t} );\n\t\t\tthis.$errorCount.append( warningWidget.$element );\n\n\t\t\tconst scrollWidget = new OO.ui.ButtonWidget( {\n\t\t\t\tclasses: [ 'mwe-upwiz-details-error-scroll' ],\n\t\t\t\tlabel: mw.message( 'mwe-upwiz-details-' + kind + '-scroll', errorCount, uploadCount ).text(),\n\t\t\t\tflags: [ 'progressive' ]\n\t\t\t} );\n\t\t\tscrollWidget.on( 'click', () => scrollToFirst( $elements ) );\n\t\t\tthis.nextButton.$element.hide().before( scrollWidget.$element );\n\t\t};\n\n\t\tconst observe = ( element, kind, $observedElements ) => {\n\t\t\tconst observer = new MutationObserver( () => {\n\t\t\t\t// eslint-disable-next-line no-jquery/no-sizzle\n\t\t\t\tif ( !$( element ).is( ':visible' ) ) {\n\t\t\t\t\tobserver.disconnect();\n\t\t\t\t\tupdateSummary(\n\t\t\t\t\t\tkind,\n\t\t\t\t\t\tmw.message( 'mwe-upwiz-details-' + kind + '-generic' ),\n\t\t\t\t\t\t// eslint-disable-next-line no-jquery/no-sizzle\n\t\t\t\t\t\t$observedElements.filter( ':visible' )\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} );\n\t\t\tobserver.observe(\n\t\t\t\telement.parentNode,\n\t\t\t\t{ childList: true }\n\t\t\t);\n\t\t};\n\n\t\tconst show = ( kind ) => {\n\t\t\tconst $elements = getElements( kind );\n\n\t\t\tupdateSummary(\n\t\t\t\tkind,\n\t\t\t\tmw.message( 'mwe-upwiz-details-' + kind + '-count' ),\n\t\t\t\t$elements\n\t\t\t);\n\n\t\t\tif ( $elements.length > 0 ) {\n\t\t\t\t$elements.each( function () {\n\t\t\t\t\tobserve( this, kind, $elements );\n\n\t\t\t\t\t// Open collapsed elements that contain errors\n\t\t\t\t\tconst $collapsibleWrapper = $( this ).closest( '.mw-collapsible' );\n\t\t\t\t\tif ( $collapsibleWrapper.length ) {\n\t\t\t\t\t\t$collapsibleWrapper.data( 'mw-collapsible' ).expand();\n\t\t\t\t\t}\n\t\t\t\t} );\n\n\t\t\t\tscrollToFirst( $elements );\n\t\t\t}\n\n\t\t\treturn $elements.length;\n\t\t};\n\n\t\t// show errors first; warnings only when there are no errors\n\t\t// don't bother with notices; no need to inform user about those merely showing them near the input\n\t\t// eslint-disable-next-line no-unused-expressions\n\t\tshow( 'error' ) || show( 'warning' );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ui/uw.ui.Wizard.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.controller.Step' is undefined.","line":128,"column":1,"nodeType":"Block","endLine":128,"endColumn":1},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":141,"column":25,"nodeType":"CallExpression","endLine":141,"endColumn":72},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.controller.Step' is undefined.","line":166,"column":1,"nodeType":"Block","endLine":166,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.controller.Step' is undefined.","line":167,"column":1,"nodeType":"Block","endLine":167,"endColumn":1}],"suppressedMessages":[{"ruleId":"no-jquery/no-global-selector","severity":2,"message":"Avoid queries which search the entire DOM. Keep DOM nodes in memory where possible.","line":49,"column":23,"nodeType":"CallExpression","endLine":49,"endColumn":41,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-sizzle","severity":2,"message":"Positional selector extensions are not allowed","line":86,"column":3,"nodeType":"CallExpression","endLine":86,"endColumn":51,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\t/**\n\t * Represents the UI for the wizard.\n\t *\n\t * @class\n\t * @mixes OO.EventEmitter\n\t * @param {string} selector Where to put all of the wizard interface.\n\t */\n\tuw.ui.Wizard = function UWUIWizard( selector ) {\n\t\tOO.EventEmitter.call( this );\n\n\t\tthis.$div = $( '<div>' )\n\t\t\t.attr( 'id', 'mwe-upwiz-content' );\n\n\t\t$( selector ).append(\n\t\t\tthis.$div,\n\t\t\t$( '<div>' ).addClass( 'mwe-upwiz-clearing' )\n\t\t);\n\n\t\tthis.initHeader( mw.UploadWizard.config );\n\t};\n\n\tOO.mixinClass( uw.ui.Wizard, OO.EventEmitter );\n\n\t/**\n\t * Initializes the static stuff above the wizard.\n\t *\n\t * @param {Object} config\n\t */\n\tuw.ui.Wizard.prototype.initHeader = function ( config ) {\n\t\t// eslint-disable-next-line no-jquery/no-global-selector\n\t\tconst $contentSub = $( '#contentSub' );\n\n\t\tlet feedbackLink;\n\t\tif ( config.feedbackLink ) {\n\t\t\t// Preferred. Send user to bug tracker (defaults to UW's own\n\t\t\t// Phabricator project)\n\t\t\tfeedbackLink = config.feedbackLink;\n\t\t} else if ( config.feedbackPage ) {\n\t\t\t// Backwards compatibility...send user to talk page to give\n\t\t\t// feedback.\n\t\t\tfeedbackLink = mw.util.getUrl( config.feedbackPage );\n\t\t}\n\n\t\tif ( feedbackLink ) {\n\t\t\tthis.$feedbackLink = $( '<a>' )\n\t\t\t\t.addClass( 'contentSubLink' )\n\t\t\t\t.prop( 'href', feedbackLink )\n\t\t\t\t.msg( 'mwe-upwiz-feedback-prompt' );\n\n\t\t\t$contentSub.append( this.$feedbackLink );\n\t\t}\n\n\t\tif ( config.alternativeUploadToolsPage ) {\n\t\t\tthis.$alternativeUploads = $( '<a>' )\n\t\t\t\t.addClass( 'contentSubLink' )\n\t\t\t\t.prop( 'href', new mw.Title( config.alternativeUploadToolsPage ).getUrl() )\n\t\t\t\t.msg( 'mwe-upwiz-subhead-alternatives' );\n\n\t\t\t$contentSub.append( this.$alternativeUploads );\n\t\t}\n\n\t\tif ( config.altUploadForm ) {\n\t\t\tthis.initAltUploadForm( config.altUploadForm );\n\t\t}\n\n\t\t// Separate each link in the header with a dot.\n\t\t// eslint-disable-next-line no-jquery/no-sizzle\n\t\t$contentSub.find( '.contentSubLink:not(:last)' ).after( ' · ' );\n\t};\n\n\t/**\n\t * Initializes a link to the alternate upload form, if any.\n\t *\n\t * @param {Object|string} configAltUploadForm A link or map of languages to links, pointing at an alternate form.\n\t */\n\tuw.ui.Wizard.prototype.initAltUploadForm = function ( configAltUploadForm ) {\n\t\tlet altUploadForm, userLanguage, title;\n\n\t\tif ( typeof configAltUploadForm === 'object' ) {\n\t\t\tuserLanguage = mw.config.get( 'wgUserLanguage' );\n\n\t\t\tif ( configAltUploadForm[ userLanguage ] ) {\n\t\t\t\taltUploadForm = configAltUploadForm[ userLanguage ];\n\t\t\t} else if ( configAltUploadForm.default ) {\n\t\t\t\taltUploadForm = configAltUploadForm.default;\n\t\t\t}\n\t\t} else {\n\t\t\taltUploadForm = configAltUploadForm;\n\t\t}\n\n\t\t// altUploadForm is expected to be a page title like 'Commons:Upload', so convert to URL\n\t\tif ( typeof altUploadForm === 'string' && altUploadForm.length > 0 ) {\n\t\t\ttry {\n\t\t\t\ttitle = new mw.Title( altUploadForm );\n\n\t\t\t\t$( '<a>' )\n\t\t\t\t\t.msg( 'mwe-upwiz-subhead-alt-upload' )\n\t\t\t\t\t.addClass( 'contentSubLink' )\n\t\t\t\t\t.attr( 'href', title.getUrl() )\n\t\t\t\t\t.appendTo( '#contentSub' );\n\t\t\t} catch ( e ) {\n\t\t\t\t// page was empty, or impossible on this wiki (missing namespace or some other issue). Give up.\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Initializes the arrow steps above the wizard.\n\t *\n\t * @param {Object.<uw.controller.Step>} steps\n\t */\n\tuw.ui.Wizard.prototype.initialiseSteps = function ( steps ) {\n\t\tconst $steps = $( '<ul>' )\n\t\t\t\t.attr( 'id', 'mwe-upwiz-steps' )\n\t\t\t\t.addClass( 'ui-helper-clearfix' )\n\t\t\t\t.insertBefore( '#mwe-upwiz-content' ),\n\t\t\tsortedSteps = this.sortSteps( Object.keys( steps ).map( ( key ) => steps[ key ] ) );\n\n\t\tsortedSteps.forEach( ( step ) => {\n\t\t\tconst $arrow = $( '<li>' )\n\t\t\t\t.attr( 'id', 'mwe-upwiz-step-' + step.stepName )\n\t\t\t\t.append(\n\t\t\t\t\t$( '<div>' ).text( mw.message( 'mwe-upwiz-step-' + step.stepName ).text() )\n\t\t\t\t);\n\t\t\tif ( step.showInBreadcrumb ) {\n\t\t\t\t$steps.append( $arrow );\n\t\t\t}\n\n\t\t\t// once a (new) step loads, highlight it\n\t\t\tstep.on( 'load', ( ( $arr ) => {\n\t\t\t\tif ( step.showInBreadcrumb ) {\n\t\t\t\t\t$steps.arrowStepsHighlight( $arr );\n\t\t\t\t}\n\t\t\t\t$steps.show();\n\t\t\t} ).bind( step, $arrow ) );\n\n\t\t\tstep.on( 'finished', () => {\n\t\t\t\t$steps.hide();\n\t\t\t} );\n\t\t} );\n\n\t\t$steps.arrowSteps();\n\t};\n\n\t/**\n\t * Sorts the steps in the order they'll actually be used.\n\t *\n\t * @param {uw.controller.Step[]} steps\n\t * @return {uw.controller.Step[]}\n\t */\n\tuw.ui.Wizard.prototype.sortSteps = function ( steps ) {\n\t\tlet first = steps[ 0 ];\n\n\t\t// find the very first step (element at position [0] is not guaranteed\n\t\t// to be first (it was just added first)\n\t\t// The actual internal relationship is defined in previousStep & nextStep\n\t\t// properties ...)\n\t\twhile ( first.previousStep !== null ) {\n\t\t\tfirst = first.previousStep;\n\t\t}\n\n\t\tconst sorted = [ first ];\n\t\tfor ( let i = 1; i < steps.length; i++ ) {\n\t\t\tsorted.push( sorted[ i - 1 ].nextStep );\n\t\t}\n\n\t\treturn sorted;\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/ui/uw.ui.base.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/uw.ConcurrentQueue.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/uw.CopyMetadataWidget.js","messages":[],"suppressedMessages":[{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeOut","line":148,"column":3,"nodeType":"CallExpression","endLine":151,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"no-jquery/no-fade","severity":2,"message":"Prefer CSS transitions to .fadeOut","line":165,"column":3,"nodeType":"CallExpression","endLine":168,"endColumn":30,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/uw.DetailsWidget.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/uw.FieldLayout.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/uw.LicenseGroup.js","messages":[{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":148,"column":48,"nodeType":"ObjectExpression","endLine":152,"endColumn":5},{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":181,"column":56,"nodeType":"ObjectExpression","endLine":185,"endColumn":5},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":423,"column":18,"nodeType":"CallExpression","endLine":423,"endColumn":89},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":435,"column":14,"nodeType":"CallExpression","endLine":435,"endColumn":87},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":443,"column":5,"nodeType":"CallExpression","endLine":444,"endColumn":73},{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":451,"column":5,"nodeType":"CallExpression","endLine":452,"endColumn":73},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.FieldLayout' is undefined.","line":496,"column":1,"nodeType":"Block","endLine":496,"endColumn":1},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":619,"column":3,"nodeType":"CallExpression","endLine":619,"endColumn":57},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":619,"column":3,"nodeType":"CallExpression","endLine":619,"endColumn":71}],"suppressedMessages":[{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":165,"column":39,"nodeType":"ObjectExpression","endLine":165,"endColumn":75,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":198,"column":47,"nodeType":"ObjectExpression","endLine":198,"endColumn":83,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":9,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\t/**\n\t * @extends OO.ui.LicenseGroup\n\t *\n\t * @class\n\t * @inheritdoc\n\t * @param {Object} config License configuration\n\t * @param {Array} config.licenses Array of license names\n\t * @param {string} [config.head] Header for the group of licenses (if present, the group of\n\t * licenses will be collapsed and this will be the clickable title to expand the group)\n\t * @param {string} [config.subhead] Subtitle for the group of licenses\n\t * @param {string} [config.special] 'custom' if a text input field should be added\n\t * @param {string} [config.template] Filter templates. If 'filterTemplate' was 'filter',\n\t * then [ 'fooLicense', 'barLicense' ] -> {{filter|fooLicense|barLicense}}\n\t * @param {Array} [config.prependTemplates] Array of templates to prepend. If prependTemplates\n\t * were [ 'pre', 'pended' ], then [ 'fooLicense' ] -> \"{{pre}}{{pended}}{{fooLicense}}\"\n\t * @param {string} type 'radio' or 'checkbox'\n\t * @param {mw.Api} api API object, used for wikitext previews\n\t * @param {number} count Number of the things we are licensing (it matters to some texts)\n\t */\n\tuw.LicenseGroup = function UWLicenseGroup( config, type, api, count ) {\n\t\tuw.LicenseGroup.super.call( this, {} );\n\n\t\tif ( typeof config.licenses !== 'object' ) {\n\t\t\tthrow new Error( 'improper license config' );\n\t\t}\n\n\t\tif ( ![ 'radio', 'checkbox' ].includes( type ) ) {\n\t\t\tthrow new Error( 'Invalid type: ' + type );\n\t\t}\n\n\t\t// [wikitext => list of templates used in wikitext] map, used in\n\t\t// getUsedTemplates to reduce amount of API calls\n\t\tthis.templateCache = {};\n\t\tthis.config = config;\n\t\tthis.type = type;\n\t\tthis.api = api;\n\t\tthis.count = count;\n\t\tthis.customInputs = {};\n\t\tthis.customInputFields = {};\n\t\tthis.previewDialog = new uw.LicensePreviewDialog();\n\t\tthis.windowManager = new OO.ui.WindowManager();\n\t\tthis.windowManager.addWindows( [ this.previewDialog ] );\n\t\t$( document.body ).append( this.windowManager.$element );\n\n\t\tif ( this.type === 'radio' ) {\n\t\t\tthis.group = this.createRadioGroup( [ 'mwe-upwiz-deed-license-group-body' ] );\n\t\t\tthis.group.connect( this, { choose: [ 'emit', 'change', this ] } );\n\t\t} else if ( this.type === 'checkbox' ) {\n\t\t\tthis.group = this.createCheckboxGroup( [ 'mwe-upwiz-deed-license-group-body' ] );\n\t\t\tthis.group.connect( this, { select: [ 'emit', 'change', this ] } );\n\t\t}\n\n\t\t// when selecting an item that has a custom input, we'll immediately focus it\n\t\t// this would be easier to implement by listening to the \"change\" event that this object\n\t\t// emits, but those are also triggered for keyboard navigation, and we don't want to focus\n\t\t// the input field in that case as it steals focus away from the radios/checkboxes (which,\n\t\t// in this case of nested radios/checkboxes, can't easily be restored - it'll also focus\n\t\t// the parent radio, which would mess up navigation)\n\t\tthis.group.$element.on( 'click', ( event ) => {\n\t\t\t// wrapped inside setTimeout to ensure it doesn't execute until OOUI has done its thing\n\t\t\tsetTimeout( () => {\n\t\t\t\t// first find selected thing(s), then figure out if the one we just clicked is among\n\t\t\t\t// them; if so and if that option has an input field, immediately focus it\n\t\t\t\tlet selectedItems;\n\t\t\t\tif ( this.type === 'radio' ) {\n\t\t\t\t\tselectedItems = this.group.findSelectedItem() ? [ this.group.findSelectedItem() ] : [];\n\t\t\t\t} else if ( this.type === 'checkbox' ) {\n\t\t\t\t\tselectedItems = this.group.findSelectedItems();\n\t\t\t\t}\n\t\t\t\tselectedItems.forEach( ( item ) => {\n\t\t\t\t\tconst name = item.getData();\n\t\t\t\t\tif ( item.$element.has( event.target ) && this.customInputs[ name ] ) {\n\t\t\t\t\t\tthis.customInputs[ name ].focus();\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t} );\n\t\t} );\n\n\t\tthis.fieldset = this.createFieldset( this.group );\n\t\tthis.$element = this.fieldset.$element;\n\t};\n\tOO.inheritClass( uw.LicenseGroup, OO.ui.Widget );\n\tOO.mixinClass( uw.LicenseGroup, uw.ValidatableElement );\n\n\tuw.LicenseGroup.prototype.unload = function () {\n\t\tthis.windowManager.$element.remove();\n\t};\n\n\t/**\n\t * @param {OO.ui.RadioSelectWidget|OO.ui.CheckboxMultiselectInputWidget} group\n\t * @return {OO.ui.FieldsetLayout}\n\t */\n\tuw.LicenseGroup.prototype.createFieldset = function ( group ) {\n\t\tconst fieldset = new OO.ui.FieldsetLayout( {\n\t\t\titems: [ group ],\n\t\t\tclasses: [ 'mwe-upwiz-deed-license-group' ]\n\t\t} );\n\n\t\tif ( this.config.subhead ) {\n\t\t\t// 'url' can be either a single (string) url, or an array of (string) urls;\n\t\t\t// hence this convoluted variable-length parameters assembly...\n\t\t\tconst labelParams = [ this.config.subhead, this.count ].concat( this.config.url );\n\t\t\tconst $subhead = $( '<div>' )\n\t\t\t\t.addClass( 'mwe-upwiz-deed-license-group-subhead mwe-upwiz-deed-title' )\n\t\t\t\t.append( mw.message.apply( mw.message, labelParams ).parseDom() );\n\n\t\t\tif ( this.config[ 'subhead-extra' ] ) {\n\t\t\t\tconst labelExtraParams = [ this.config[ 'subhead-extra' ], this.count ].concat( this.config.url );\n\t\t\t\t$subhead.append(\n\t\t\t\t\t$( '<span>' )\n\t\t\t\t\t\t.addClass( 'mwe-upwiz-label-extra' )\n\t\t\t\t\t\t.append( mw.message.apply( mw.message, labelExtraParams ).parse() )\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// make all links open in a new tab in order to not disrupt the upload process\n\t\t\t$subhead.find( 'a' ).attr( { target: '_blank' } );\n\n\t\t\tfieldset.addItems(\n\t\t\t\t[\n\t\t\t\t\tnew OO.ui.FieldLayout(\n\t\t\t\t\t\tnew OO.ui.Widget( { content: [] } ),\n\t\t\t\t\t\t{ label: $subhead, align: 'top' }\n\t\t\t\t\t)\n\t\t\t\t],\n\t\t\t\t0 // = index; add to top\n\t\t\t);\n\t\t}\n\n\t\treturn fieldset;\n\t};\n\n\t/**\n\t * @param {Array} classes to add\n\t * @return {OO.ui.RadioSelectWidget}\n\t */\n\tuw.LicenseGroup.prototype.createRadioGroup = function ( classes ) {\n\t\tconst options = [];\n\n\t\tthis.config.licenses.forEach( ( licenseName ) => {\n\t\t\tif ( mw.UploadWizard.config.licenses[ licenseName ] === undefined ) {\n\t\t\t\t// unknown license\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst option = new OO.ui.RadioOptionWidget( {\n\t\t\t\tlabel: this.createLabel( licenseName ),\n\t\t\t\tdata: licenseName,\n\t\t\t\tclasses: [ 'mwe-upwiz-license-option-' + licenseName ]\n\t\t\t} );\n\n\t\t\t// when custom text field receives input, we should make sure this option is selected\n\t\t\tif ( this.customInputs[ licenseName ] ) {\n\t\t\t\tthis.customInputs[ licenseName ].$input.on( 'input', () => {\n\t\t\t\t\toption.setSelected( this.customInputs[ licenseName ].getValue() !== '' );\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\toptions.push( option );\n\t\t} );\n\n\t\t// eslint-disable-next-line mediawiki/class-doc\n\t\treturn new OO.ui.RadioSelectWidget( { items: options, classes: classes } );\n\t};\n\n\t/**\n\t * @param {Array} classes to add\n\t * @return {OO.ui.CheckboxMultiselectInputWidget}\n\t */\n\tuw.LicenseGroup.prototype.createCheckboxGroup = function ( classes ) {\n\t\tconst options = [];\n\n\t\tthis.config.licenses.forEach( ( licenseName ) => {\n\t\t\tif ( mw.UploadWizard.config.licenses[ licenseName ] === undefined ) {\n\t\t\t\t// unknown license\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst option = new OO.ui.CheckboxMultioptionWidget( {\n\t\t\t\tlabel: this.createLabel( licenseName ),\n\t\t\t\tdata: licenseName,\n\t\t\t\tclasses: [ 'mwe-upwiz-license-option-' + licenseName ]\n\t\t\t} );\n\n\t\t\t// when custom text field receives input, we should make sure this option is selected\n\t\t\tif ( this.customInputs[ licenseName ] ) {\n\t\t\t\tthis.customInputs[ licenseName ].$input.on( 'input', () => {\n\t\t\t\t\toption.setSelected( this.customInputs[ licenseName ].getValue() !== '' );\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\toptions.push( option );\n\t\t} );\n\n\t\t// eslint-disable-next-line mediawiki/class-doc\n\t\treturn new OO.ui.CheckboxMultiselectWidget( { items: options, classes: classes } );\n\t};\n\n\t/**\n\t * @return {string}\n\t */\n\tuw.LicenseGroup.prototype.getWikiText = function () {\n\t\tconst values = this.getValue();\n\n\t\tconst wikiTexts = Object.keys( values ).map( ( name ) => {\n\t\t\tlet wikiText = this.getLicenceWikiText( name );\n\t\t\tconst value = values[ name ];\n\t\t\tif ( typeof value === 'string' ) {\n\t\t\t\t// `value` is custom input\n\t\t\t\twikiText += '\\n' + value.trim();\n\t\t\t}\n\t\t\treturn wikiText;\n\t\t} );\n\n\t\treturn wikiTexts.join( '' ).trim();\n\t};\n\n\t/**\n\t * Returns a string unique to the group (if defined)\n\t *\n\t * @return {string}\n\t */\n\tuw.LicenseGroup.prototype.getGroup = function () {\n\t\treturn this.config.head || '';\n\t};\n\n\t/**\n\t * @return {string}\n\t */\n\tuw.LicenseGroup.prototype.getData = function () {\n\t\treturn this.getGroup();\n\t};\n\n\t/**\n\t * @return {Object} Map of { licenseName: true }, or { licenseName: \"custom input\" }\n\t */\n\tuw.LicenseGroup.prototype.getValue = function () {\n\t\tconst result = {};\n\n\t\tlet selected, name;\n\t\tif ( this.type === 'radio' ) {\n\t\t\tselected = this.group.findSelectedItem();\n\t\t\tif ( selected ) {\n\t\t\t\tname = selected.getData();\n\t\t\t\tresult[ name ] = !this.customInputs[ name ] || this.customInputs[ name ].getValue();\n\t\t\t}\n\t\t} else if ( this.type === 'checkbox' ) {\n\t\t\tselected = this.group.findSelectedItems();\n\t\t\tselected.forEach( ( item ) => {\n\t\t\t\tname = item.getData();\n\t\t\t\tresult[ name ] = !this.customInputs[ name ] || this.customInputs[ name ].getValue();\n\t\t\t} );\n\t\t}\n\n\t\treturn result;\n\t};\n\n\t/**\n\t * @param {Object} values Map of { licenseName: true }, or { licenseName: \"custom input\" }\n\t */\n\tuw.LicenseGroup.prototype.setValue = function ( values ) {\n\t\tconst selectArray = [];\n\n\t\tObject.keys( values ).forEach( ( name ) => {\n\t\t\tconst value = values[ name ];\n\t\t\tif ( typeof value === 'string' && this.customInputs[ name ] ) {\n\t\t\t\tthis.customInputs[ name ].setValue( value );\n\t\t\t\t// add to list of items to select\n\t\t\t\tselectArray.push( name );\n\t\t\t}\n\n\t\t\t// add to list of items to select\n\t\t\t// (only true/string values should be included in `values`, but might\n\t\t\t// as well play it safe...)\n\t\t\tif ( value === true ) {\n\t\t\t\tselectArray.push( name );\n\t\t\t}\n\t\t} );\n\n\t\tif ( this.type === 'radio' ) {\n\t\t\tthis.group.selectItemByData( selectArray[ 0 ] );\n\t\t} else if ( this.type === 'checkbox' ) {\n\t\t\tthis.group.selectItemsByData( selectArray );\n\t\t}\n\t};\n\n\tuw.LicenseGroup.prototype.validate = function ( thorough ) {\n\t\tconst status = new uw.ValidationStatus();\n\n\t\tif ( thorough !== true ) {\n\t\t\t// `thorough` is the strict checks executed on submit, but we don't want errors\n\t\t\t// to change/display every change event\n\t\t\treturn status.resolve();\n\t\t}\n\n\t\tlet selected, name;\n\t\tif ( this.type === 'radio' ) {\n\t\t\tselected = this.group.findSelectedItem() ? [ this.group.findSelectedItem() ] : [];\n\t\t} else if ( this.type === 'checkbox' ) {\n\t\t\tselected = this.group.findSelectedItems();\n\t\t}\n\n\t\tif ( selected.length === 0 ) {\n\t\t\treturn status\n\t\t\t\t.addError( mw.message( 'mwe-upwiz-deeds-require-selection' ) )\n\t\t\t\t.reject();\n\t\t}\n\n\t\tconst customInputPromises = [];\n\t\tselected.forEach( ( item ) => {\n\t\t\tname = item.getData();\n\t\t\tif ( name in this.customInputFields ) {\n\t\t\t\tcustomInputPromises.push( this.customInputFields[ name ].validate( thorough ) );\n\t\t\t}\n\t\t} );\n\n\t\treturn uw.ValidationStatus.mergePromises( ...customInputPromises ).then(\n\t\t\t// custom input (if any) is fine\n\t\t\t() => status.getErrors().length === 0 ? status.resolve() : status.reject(),\n\t\t\t// there was an error in one of the custom inputs; we'll still want\n\t\t\t// to reject, but those child messages need not be added into this status\n\t\t\t// object, since they'll already be displayed within those child widgets\n\t\t\t() => status.reject()\n\t\t);\n\t};\n\n\t/**\n\t * @private\n\t * @param {string} name\n\t * @return {Object}\n\t */\n\tuw.LicenseGroup.prototype.getLicenseInfo = function ( name ) {\n\t\treturn {\n\t\t\tname: name,\n\t\t\tprops: mw.UploadWizard.config.licenses[ name ]\n\t\t};\n\t};\n\n\t/**\n\t * @private\n\t * @param {string} name\n\t * @return {string[]}\n\t */\n\tuw.LicenseGroup.prototype.getTemplates = function ( name ) {\n\t\tconst licenseInfo = this.getLicenseInfo( name );\n\t\treturn licenseInfo.props.templates === undefined ?\n\t\t\t[ licenseInfo.name ] :\n\t\t\tlicenseInfo.props.templates.slice( 0 );\n\t};\n\n\t/**\n\t * License templates are these abstract ideas like cc-by-sa. In general they map directly to a license template.\n\t * However, configuration for a particular option can add other templates or transform the templates,\n\t * such as wrapping templates in an outer \"self\" template for own-work\n\t *\n\t * @private\n\t * @param {string} name license template name\n\t * @return {string} of wikitext\n\t */\n\tuw.LicenseGroup.prototype.getLicenceWikiText = function ( name ) {\n\t\tlet templates = this.getTemplates( name );\n\n\t\tif ( this.config.prependTemplates !== undefined ) {\n\t\t\tthis.config.prependTemplates.forEach( ( template ) => {\n\t\t\t\ttemplates.unshift( template );\n\t\t\t} );\n\t\t}\n\n\t\tif ( this.config.template !== undefined ) {\n\t\t\ttemplates.unshift( this.config.template );\n\t\t\ttemplates = [ templates.join( '|' ) ];\n\t\t}\n\n\t\tconst wikiTexts = templates.map( ( t ) => '{{' + t + '}}' );\n\t\treturn wikiTexts.join( '' );\n\t};\n\n\t/**\n\t * Get a label for the form element\n\t *\n\t * @private\n\t * @param {string} name license template name\n\t * @return {jQuery}\n\t */\n\tuw.LicenseGroup.prototype.createLabel = function ( name ) {\n\t\tconst licenseInfo = this.getLicenseInfo( name ),\n\t\t\tmessageKey = licenseInfo.props.msg === undefined ?\n\t\t\t\t'[missing msg for ' + licenseInfo.name + ']' :\n\t\t\t\tlicenseInfo.props.msg,\n\t\t\t$icons = $( '<span>' );\n\t\t\t// The URL is optional, but if the message includes it as $2, we surface the fact\n\t\t\t// that it's missing.\n\t\tlet licenseURL = licenseInfo.props.url === undefined ? '#missing license URL' : licenseInfo.props.url;\n\n\t\tif (\n\t\t\tlicenseInfo.props.languageCodePrefix !== undefined &&\n\t\t\tlicenseInfo.props.availableLanguages !== undefined\n\t\t) {\n\t\t\tlet targetLanguageCode = 'en'; // final fallback\n\t\t\tconst fallbackChain = mw.language.getFallbackLanguageChain();\n\t\t\tfor ( let i = 0; i < fallbackChain.length; i++ ) {\n\t\t\t\tif ( licenseInfo.props.availableLanguages.includes( fallbackChain[ i ] ) ) {\n\t\t\t\t\ttargetLanguageCode = fallbackChain[ i ];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tlicenseURL += licenseInfo.props.languageCodePrefix + targetLanguageCode;\n\t\t}\n\t\tconst $licenseLink = $( '<a>' ).attr( { target: '_blank', href: licenseURL } );\n\t\tif ( licenseInfo.props.icons !== undefined ) {\n\t\t\tlicenseInfo.props.icons.forEach( ( icon ) => {\n\t\t\t\t// The following classes are used here:\n\t\t\t\t// * mwe-upwiz-cc-public-domain-icon\n\t\t\t\t// * mwe-upwiz-cc-zero-icon\n\t\t\t\t// * mwe-upwiz-cc-sa-icon\n\t\t\t\t// * mwe-upwiz-cc-by-icon\n\t\t\t\t$icons.append( $( '<span>' ).addClass( 'skin-invert mwe-upwiz-license-icon mwe-upwiz-' + icon + '-icon' ) );\n\t\t\t} );\n\t\t}\n\n\t\tconst $label = $( '<label>' ).msg( messageKey, this.count || 0, $licenseLink, $icons );\n\n\t\tif (\n\t\t\tthis.config.special === 'custom' ||\n\t\t\tlicenseInfo.props.special === 'input'\n\t\t) {\n\t\t\tconst inputField = this.createInput( name, licenseInfo.props.defaultText );\n\n\t\t\t$label.append( inputField.$element );\n\t\t\tif ( licenseInfo.props.msgSpecial !== undefined ) {\n\t\t\t\tinputField.$body.append(\n\t\t\t\t\t$( '<span>' )\n\t\t\t\t\t\t.html( mw.message( licenseInfo.props.msgSpecial, this.count || 0, $licenseLink ).parse() )\n\t\t\t\t\t\t.addClass( 'mwe-upwiz-label-extra' )\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tif ( licenseInfo.props.msgExplain !== undefined ) {\n\t\t\t$label.append(\n\t\t\t\t$( '<span>' )\n\t\t\t\t\t.msg( licenseInfo.props.msgExplain, this.count || 0, $licenseLink )\n\t\t\t\t\t.addClass( 'mwe-upwiz-label-extra mwe-upwiz-label-explainer' )\n\t\t\t);\n\t\t}\n\n\t\tif ( licenseInfo.props.msgWarning !== undefined ) {\n\t\t\t$label.append(\n\t\t\t\t$( '<span>' )\n\t\t\t\t\t.msg( licenseInfo.props.msgWarning, this.count || 0, $licenseLink )\n\t\t\t\t\t.addClass( 'mwe-upwiz-label-extra mwe-upwiz-label-warning' )\n\t\t\t);\n\t\t}\n\n\t\treturn $label.contents();\n\t};\n\n\t/**\n\t * Returns a list of templates used & transcluded in given wikitext\n\t *\n\t * @private\n\t * @param {string} wikitext\n\t * @return {jQuery.Promise} Promise that resolves with an array of template names\n\t */\n\tuw.LicenseGroup.prototype.getUsedTemplates = function ( wikitext ) {\n\t\tif ( wikitext in this.templateCache ) {\n\t\t\treturn $.Deferred().resolve( this.templateCache[ wikitext ] ).promise();\n\t\t}\n\t\treturn this.api.get( {\n\t\t\taction: 'parse',\n\t\t\tpst: true,\n\t\t\tprop: 'templates',\n\t\t\ttitle: 'File:UploadWizard license verification.png',\n\t\t\ttext: wikitext\n\t\t} ).then( ( result ) => {\n\t\t\tconst templates = [];\n\t\t\tfor ( let i = 0; i < result.parse.templates.length; i++ ) {\n\t\t\t\tconst template = result.parse.templates[ i ];\n\t\t\t\t// normalize templates to mw.Title.getPrefixedDb() format\n\t\t\t\tconst title = new mw.Title( template.title, template.ns );\n\t\t\t\ttemplates.push( title.getPrefixedDb() );\n\t\t\t}\n\t\t\t// cache result so we won't have to fire another API request\n\t\t\t// for the same content\n\t\t\tthis.templateCache[ wikitext ] = templates;\n\t\t\treturn templates;\n\t\t} );\n\t};\n\n\t/**\n\t * @private\n\t * @param {string} name license name\n\t * @param {string} [defaultText] Default custom license text\n\t * @return {uw.FieldLayout}\n\t */\n\tuw.LicenseGroup.prototype.createInput = function ( name, defaultText ) {\n\t\tconst input = new OO.ui.TextInputWidget( {\n\t\t\tvalue: defaultText\n\t\t} );\n\n\t\tconst button = new OO.ui.ButtonWidget( {\n\t\t\tlabel: mw.message( 'mwe-upwiz-license-custom-preview' ).text(),\n\t\t\tflags: [ 'progressive' ]\n\t\t} );\n\n\t\tinput.on( 'change', () => {\n\t\t\t// Update displayed errors as the user is typing\n\t\t\tthis.emit( 'change' );\n\t\t} );\n\t\tinput.$element.on( 'mousedown', ( event ) => {\n\t\t\t// T294389 \"Another reason not mentioned above\" license input textarea: Text is unclickable.\n\t\t\t// When used inside other widgets (e.g. RadioSelectWidget or CheckboxMultioptionWidget),\n\t\t\t// those may have event handlers to respond to clicks (e.g. selecting the radio/checkbox)\n\t\t\t// that would steal focus from the input. We do not want that to happen, as it may\n\t\t\t// interfere with operations within the input field (e.g. selecting text), so we'll\n\t\t\t// avoid that by not propagating the event (to RadioSelectWidget/CheckboxMultioptionWidget),\n\t\t\t// but that'll then require those parent widgets to handle such relevant events themselves.\n\t\t\tevent.stopPropagation();\n\t\t} );\n\t\tinput.$element.on( 'keydown', ( event ) => {\n\t\t\tswitch ( event.which ) {\n\t\t\t\tcase OO.ui.Keys.UP:\n\t\t\t\tcase OO.ui.Keys.LEFT:\n\t\t\t\tcase OO.ui.Keys.DOWN:\n\t\t\t\tcase OO.ui.Keys.RIGHT:\n\t\t\t\t\t// Similar to the mouse events issue described above; parent widgets may also\n\t\t\t\t\t// handle some keyboard events (e.g. RadioSelectWidget or CheckboxMultioptionWidget\n\t\t\t\t\t// capture up/left and down/right to navigate to previous/next item)\n\t\t\t\t\t// Once again, this is undesirable, as these keys are likely used for operations\n\t\t\t\t\t// within the input field (moving the cursor), so once again we're preventing\n\t\t\t\t\t// propagation of these events to the parent nodes.\n\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\tbreak;\n\t\t\t\tcase OO.ui.Keys.ENTER:\n\t\t\t\t\t// Hitting enter should not trigger the default button action (submitting a form),\n\t\t\t\t\t// but open the preview dialog instead.\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\tbutton.emit( 'click' );\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t} );\n\n\t\tbutton.on( 'click', () => {\n\t\t\tthis.showPreview( input.getValue() );\n\t\t} );\n\n\t\tuw.ValidatableElement.decorate( input );\n\t\tinput.validate = ( thorough ) => {\n\t\t\tconst status = new uw.ValidationStatus();\n\t\t\tlet promise = $.Deferred().resolve().promise();\n\n\t\t\tif ( thorough !== true ) {\n\t\t\t\treturn status.resolve();\n\t\t\t}\n\n\t\t\tconst wikitext = input.getValue().trim();\n\t\t\tif ( wikitext === '' ) {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-license-wikitext-missing' ) );\n\t\t\t} else if ( wikitext.length < mw.UploadWizard.config.minCustomLicenseLength ) {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-license-wikitext-too-short' ) );\n\t\t\t} else if ( wikitext.length > mw.UploadWizard.config.maxCustomLicenseLength ) {\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-license-wikitext-too-long' ) );\n\t\t\t} else if ( !/\\{\\{(.+?)\\}\\}/g.test( wikitext ) ) {\n\t\t\t\t// if text doesn't contain a template, we don't even\n\t\t\t\t// need to validate it any further...\n\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-license-wikitext-missing-template' ) );\n\t\t\t} else if ( mw.UploadWizard.config.customLicenseTemplate !== false ) {\n\t\t\t\t// now do a thorough test to see if the text actually\n\t\t\t\t// includes a license template\n\t\t\t\tpromise = this.getUsedTemplates( wikitext ).then( ( usedTemplates ) => {\n\t\t\t\t\tif ( !usedTemplates.includes( mw.UploadWizard.config.customLicenseTemplate ) ) {\n\t\t\t\t\t\t// no license template found, add another error\n\t\t\t\t\t\tstatus.addError( mw.message( 'mwe-upwiz-error-license-wikitext-missing-template' ) );\n\t\t\t\t\t}\n\t\t\t\t} );\n\t\t\t}\n\n\t\t\treturn promise.then( () => status.getErrors().length === 0 ? status.resolve() : status.reject() );\n\t\t};\n\n\t\tconst inputField = new uw.FieldLayout( input, {\n\t\t\tclasses: [ 'mwe-upwiz-license-custom', 'mwe-upwiz-label-input' ]\n\t\t} );\n\t\tinputField.$body.append( button.$element );\n\t\tinput.connect( inputField, { change: [ 'emit', 'change' ] } );\n\n\t\tthis.customInputs[ name ] = input;\n\t\tthis.customInputFields[ name ] = inputField;\n\n\t\treturn inputField;\n\t};\n\n\t/**\n\t * Preview wikitext in a popup window\n\t *\n\t * @private\n\t * @param {string} wikiText\n\t */\n\tuw.LicenseGroup.prototype.showPreview = function ( wikiText ) {\n\t\tthis.previewDialog.setLoading( true );\n\t\tthis.windowManager.openWindow( this.previewDialog );\n\n\t\tconst show = ( html ) => {\n\t\t\tthis.previewDialog.setPreview( html );\n\t\t\tthis.windowManager.openWindow( this.previewDialog );\n\t\t};\n\n\t\tconst error = ( code, result ) => {\n\t\t\tconst message = result.errors[ 0 ].html;\n\n\t\t\tshow( $( '<div>' ).append(\n\t\t\t\t$( '<h3>' ).append( code ),\n\t\t\t\t$( '<p>' ).append( message )\n\t\t\t) );\n\t\t};\n\n\t\tthis.api.parse( wikiText, { pst: true } ).done( show ).fail( error );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/uw.LicensePreviewDialog.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/uw.ValidatableElement.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":36,"column":1,"nodeType":"Block","endLine":36,"endColumn":1}],"suppressedMessages":[{"ruleId":"no-unused-vars","severity":2,"message":"'thorough' is defined but never used.","line":39,"column":56,"nodeType":"Identifier","messageId":"unusedVar","endLine":39,"endColumn":64,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\tuw.ValidatableElement = function UWValidatableElement() {};\n\tOO.initClass( uw.ValidatableElement );\n\n\t/**\n\t * Convenience method to attach validatableElement methods to an on-the-fly\n\t * created instance of one of the existing widgets where this class hasn't\n\t * been mixed in.\n\t *\n\t * @param {OO.ui.Widget} widget\n\t */\n\tuw.ValidatableElement.decorate = function ( widget ) {\n\t\t// copy ValidatableElement methods onto widget instance\n\t\tlet key;\n\t\tfor ( key in uw.ValidatableElement.prototype ) {\n\t\t\tif ( key !== 'constructor' && Object.prototype.hasOwnProperty.call( uw.ValidatableElement.prototype, key ) ) {\n\t\t\t\twidget[ key ] = uw.ValidatableElement.prototype[ key ];\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * This method executes the validation. It is expected to return a promise\n\t * that either resolves (on success) or rejects (on failure), with up to\n\t * 3 arguments, in both cases, representing errors, warnings and notices\n\t * respectively.\n\t *\n\t * Notice that it is possible for a check to fail (rejected) without error,\n\t * or for there to be an error without failure (resolved). This can be\n\t * useful e.g. in case there was an error in a nested component, where it\n\t * is displayed in situ: we don't want to show a duplicate error in the\n\t * parent, but still want to report on the overall failure of the validation.\n\t *\n\t * @param {boolean} thorough\n\t * @return {jQuery.Promise<uw.ValidationStatus>}\n\t */\n\t// eslint-disable-next-line no-unused-vars\n\tuw.ValidatableElement.prototype.validate = function ( thorough ) {\n\t\treturn ( new uw.ValidationStatus() ).resolve();\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/uw.ValidationMessageElement.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidatableElement' is undefined.","line":10,"column":1,"nodeType":"Block","endLine":10,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":48,"column":1,"nodeType":"Block","endLine":48,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":60,"column":1,"nodeType":"Block","endLine":60,"endColumn":1},{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":92,"column":10,"nodeType":"CallExpression","endLine":94,"endColumn":61},{"ruleId":"mediawiki/class-doc","severity":1,"message":"All possible CSS classes should be documented. See https://w.wiki/PS2 for details.","line":92,"column":10,"nodeType":"CallExpression","endLine":93,"endColumn":48}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":5,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\t/**\n\t * Element that is able to display validation messages from itself or another widget.\n\t *\n\t * @abstract\n\t * @class\n\t *\n\t * @param {Object} [config]\n\t * @param {uw.ValidatableElement} [config.validatedWidget] Widget to validate\n\t */\n\tuw.ValidationMessageElement = function UWValidationMessageElement( config ) {\n\t\tconfig = config || {};\n\n\t\tthis.validatedWidget = config.validatedWidget || this;\n\t\tthis.$messages = $( '<ul>' );\n\n\t\tthis.errors = [];\n\t\tthis.warnings = [];\n\t\tthis.notices = [];\n\t\tthis.successMessages = []; // unused, but OO.ui.FieldLayout.prototype.updateMessages assumes this exists\n\n\t\tthis.validatedWidget.on( 'change', () => this.validate && this.validate( false ) );\n\n\t\tthis.$messages.addClass( 'oo-ui-fieldLayout-messages' );\n\t\tthis.$element.addClass( 'mwe-upwiz-validationMessageElement' );\n\t};\n\tOO.initClass( uw.ValidationMessageElement );\n\tOO.mixinClass( uw.ValidationMessageElement, uw.ValidatableElement );\n\n\t// Hack: Steal methods from OO.ui.FieldLayout.\n\t// TODO: Upstream ValidationMessageElement to OOUI, make FieldLayout use it.\n\tuw.ValidationMessageElement.prototype.setErrors = OO.ui.FieldLayout.prototype.setErrors;\n\tuw.ValidationMessageElement.prototype.setWarnings = OO.ui.FieldLayout.prototype.setWarnings;\n\tuw.ValidationMessageElement.prototype.setNotices = OO.ui.FieldLayout.prototype.setNotices;\n\tuw.ValidationMessageElement.prototype.updateMessages = OO.ui.FieldLayout.prototype.updateMessages;\n\n\tuw.ValidationMessageElement.prototype.preValidate = function () {\n\t\tif ( this.validatedWidget.pushPending ) {\n\t\t\tthis.validatedWidget.pushPending();\n\t\t}\n\t};\n\n\t/**\n\t * Check the field's widget for errors, warnings & notices and display them in the UI.\n\t *\n\t * @param {boolean} thorough True to perform a thorough validity check. Defaults to false for a fast on-change check.\n\t * @return {jQuery.Promise<uw.ValidationStatus>}\n\t */\n\tuw.ValidationMessageElement.prototype.validate = function ( thorough ) {\n\t\tthorough = thorough || false;\n\n\t\treturn $.Deferred().resolve().promise()\n\t\t\t.then( () => this.preValidate() )\n\t\t\t.then( () => this.validatedWidget.validate( thorough ) )\n\t\t\t.always( ( status ) => this.postValidate( status ) );\n\t};\n\n\t/**\n\t * @param {uw.ValidationStatus} status\n\t */\n\tuw.ValidationMessageElement.prototype.postValidate = function ( status ) {\n\t\t// errors, warnings & notices are arrays of mw.Messages and not strings in this subclass\n\t\tthis.setErrors( status.getErrors() );\n\t\tthis.setWarnings( status.getWarnings() );\n\t\tthis.setNotices( status.getNotices() );\n\n\t\tif ( this.validatedWidget.popPending ) {\n\t\t\tthis.validatedWidget.popPending();\n\t\t}\n\t};\n\n\t/**\n\t * @protected\n\t * @param {string} kind 'error', 'warning' or 'notice'\n\t * @param {mw.Message|Object} error Message, or an object in { key: ..., html: ... } format\n\t * @return {jQuery}\n\t */\n\tuw.ValidationMessageElement.prototype.makeMessage = function ( kind, error ) {\n\t\tlet code, $content;\n\n\t\tif ( error.parseDom ) {\n\t\t\t// mw.Message object\n\t\t\tcode = error.key;\n\t\t\t$content = error.parseDom();\n\t\t} else {\n\t\t\t// { key: ..., html: ... } object (= formatted API error responses)\n\t\t\tcode = error.code;\n\t\t\t$content = $( $.parseHTML( error.html ) );\n\t\t}\n\n\t\treturn OO.ui.FieldLayout.prototype.makeMessage.call( this, kind, $content )\n\t\t\t.addClass( 'mwe-upwiz-fieldLayout-' + kind )\n\t\t\t.addClass( 'mwe-upwiz-fieldLayout-' + kind + '-' + code );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/uw.ValidationStatus.js","messages":[{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":11,"column":1,"nodeType":"Block","endLine":11,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":20,"column":1,"nodeType":"Block","endLine":20,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":29,"column":1,"nodeType":"Block","endLine":29,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":38,"column":1,"nodeType":"Block","endLine":38,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":47,"column":1,"nodeType":"Block","endLine":47,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":56,"column":1,"nodeType":"Block","endLine":56,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":85,"column":1,"nodeType":"Block","endLine":85,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":92,"column":1,"nodeType":"Block","endLine":92,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":99,"column":1,"nodeType":"Block","endLine":99,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":100,"column":1,"nodeType":"Block","endLine":100,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":115,"column":1,"nodeType":"Block","endLine":115,"endColumn":1},{"ruleId":"jsdoc/no-undefined-types","severity":1,"message":"The type 'uw.ValidationStatus' is undefined.","line":116,"column":1,"nodeType":"Block","endLine":116,"endColumn":1}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":12,"fixableErrorCount":0,"fixableWarningCount":0,"source":"( function ( uw ) {\n\n\tuw.ValidationStatus = function UWValidationStatus() {\n\t\tthis.errors = [];\n\t\tthis.warnings = [];\n\t\tthis.notices = [];\n\t};\n\n\t/**\n\t * @param {Array} errors\n\t * @return {uw.ValidationStatus}\n\t */\n\tuw.ValidationStatus.prototype.setErrors = function ( errors ) {\n\t\tthis.errors = errors;\n\t\treturn this;\n\t};\n\n\t/**\n\t * @param {Array} warnings\n\t * @return {uw.ValidationStatus}\n\t */\n\tuw.ValidationStatus.prototype.setWarnings = function ( warnings ) {\n\t\tthis.warnings = warnings;\n\t\treturn this;\n\t};\n\n\t/**\n\t * @param {Array} notices\n\t * @return {uw.ValidationStatus}\n\t */\n\tuw.ValidationStatus.prototype.setNotices = function ( notices ) {\n\t\tthis.notices = notices;\n\t\treturn this;\n\t};\n\n\t/**\n\t * @param {mw.message} error\n\t * @return {uw.ValidationStatus}\n\t */\n\tuw.ValidationStatus.prototype.addError = function ( error ) {\n\t\tthis.errors.push( error );\n\t\treturn this;\n\t};\n\n\t/**\n\t * @param {mw.message} warning\n\t * @return {uw.ValidationStatus}\n\t */\n\tuw.ValidationStatus.prototype.addWarning = function ( warning ) {\n\t\tthis.warnings.push( warning );\n\t\treturn this;\n\t};\n\n\t/**\n\t * @param {mw.message} notice\n\t * @return {uw.ValidationStatus}\n\t */\n\tuw.ValidationStatus.prototype.addNotice = function ( notice ) {\n\t\tthis.notices.push( notice );\n\t\treturn this;\n\t};\n\n\t/**\n\t * @return {Array} errors\n\t */\n\tuw.ValidationStatus.prototype.getErrors = function () {\n\t\treturn this.errors;\n\t};\n\n\t/**\n\t * @return {Array} warnings\n\t */\n\tuw.ValidationStatus.prototype.getWarnings = function () {\n\t\treturn this.warnings;\n\t};\n\n\t/**\n\t * @return {Array} notices\n\t */\n\tuw.ValidationStatus.prototype.getNotices = function () {\n\t\treturn this.notices;\n\t};\n\n\t/**\n\t * @return {jQuery.Promise<uw.ValidationStatus>}\n\t */\n\tuw.ValidationStatus.prototype.resolve = function () {\n\t\treturn $.Deferred().resolve( this ).promise();\n\t};\n\n\t/**\n\t * @return {jQuery.Promise<uw.ValidationStatus>}\n\t */\n\tuw.ValidationStatus.prototype.reject = function () {\n\t\treturn $.Deferred().reject( this ).promise();\n\t};\n\n\t/**\n\t * @param {Array<uw.ValidationStatus>} statuses\n\t * @return {uw.ValidationStatus}\n\t */\n\tuw.ValidationStatus.mergeStatus = function ( ...statuses ) {\n\t\tconst newStatus = new uw.ValidationStatus();\n\n\t\tstatuses.forEach( ( status ) => {\n\t\t\tstatus.getErrors().forEach( ( error ) => newStatus.addError( error ) );\n\t\t\tstatus.getWarnings().forEach( ( warning ) => newStatus.addWarning( warning ) );\n\t\t\tstatus.getNotices().forEach( ( notice ) => newStatus.addNotice( notice ) );\n\t\t} );\n\n\t\treturn newStatus;\n\t};\n\n\t/**\n\t * @param {Array<jQuery.Promise<uw.ValidationStatus>>} promises\n\t * @return {jQuery.Promise<uw.ValidationStatus>}\n\t */\n\tuw.ValidationStatus.mergePromises = function ( ...promises ) {\n\t\treturn this.whenWithoutEarlyReject( ...promises ).then(\n\t\t\t( ...statuses ) => uw.ValidationStatus.mergeStatus( ...statuses ).resolve(),\n\t\t\t( ...statuses ) => uw.ValidationStatus.mergeStatus( ...statuses ).reject()\n\t\t);\n\t};\n\n\tuw.ValidationStatus.whenWithoutEarlyReject = function ( ...promises ) {\n\t\t// $.when will be applied on all promises, but we need to ensure\n\t\t// that they will all actually resolve. If any of them rejects,\n\t\t// it will immediately cause the master promise to reject, leaving\n\t\t// us with incomplete results.\n\t\t// To avoid this, we'll just .then them into promises that always\n\t\t// resolve and keep their success status in an argument - later on,\n\t\t// we'll then combine the results and resolve/reject based on the\n\t\t// success status of all of them\n\t\tconst resolveablePromises = promises.map(\n\t\t\t( promise ) => promise.then(\n\t\t\t\t( ...data ) => $.Deferred().resolve( true, ...data ).promise(),\n\t\t\t\t( ...data ) => $.Deferred().resolve( false, ...data ).promise()\n\t\t\t)\n\t\t);\n\n\t\twhile ( resolveablePromises.length < 2 ) {\n\t\t\t// adding bogus promises to ensure $.when always resolves\n\t\t\t// with an array of multiple results (if there's just 1,\n\t\t\t// it would otherwise have just that one's arguments,\n\t\t\t// instead of a multi-dimensional array)\n\t\t\tresolveablePromises.push( $.Deferred().resolve( true, null ).promise() );\n\t\t}\n\n\t\t// resolveablePromises is an array of promises that each resolve with [<{bool} status>, ...<{Array} params>]\n\t\t// now iterate them all to figure out if we can proceed\n\t\treturn $.when.apply( $, resolveablePromises )\n\t\t\t.then( ( ...args ) => {\n\t\t\t\tlet success = true;\n\t\t\t\tconst data = [];\n\n\t\t\t\t// remove data from bogus promises that may have been added\n\t\t\t\t// for the purpose of making $.when behave consistently\n\t\t\t\targs.slice( 0, promises.length ).forEach( ( arg ) => {\n\t\t\t\t\tsuccess = success && arg[ 0 ];\n\t\t\t\t\tdata.push( arg[ 1 ] );\n\t\t\t\t} );\n\n\t\t\t\tif ( success ) {\n\t\t\t\t\treturn $.Deferred().resolve( ...data ).promise();\n\t\t\t\t}\n\t\t\t\treturn $.Deferred().reject( ...data ).promise();\n\t\t\t} );\n\t};\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/uw.units.js","messages":[],"suppressedMessages":[{"ruleId":"mediawiki/msg-doc","severity":1,"message":"All possible message keys should be documented. See https://w.wiki/4r9a for details.","line":23,"column":11,"nodeType":"CallExpression","endLine":23,"endColumn":73,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/resources/uw/uw.base.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/sql/abstractSchemaChanges/patch-uw_campaigns-cleanup.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/sql/tables.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/.eslintrc.json","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"indent","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"no-extra-parens","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/controller/uw.controller.Deed.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/controller/uw.controller.Details.test.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":127,"column":3,"nodeType":"CallExpression","endLine":127,"endColumn":40}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension DetailsWizard.\n *\n * DetailsWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * DetailsWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with DetailsWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\tQUnit.module( 'mw.uploadWizard.controller.Details', QUnit.newMwEnvironment() );\n\n\tfunction createTestUpload( sandbox, aborted ) {\n\t\tconst stubs = {\n\t\t\tgetSerialized: sandbox.stub(),\n\t\t\tsetSerialized: sandbox.stub(),\n\t\t\tattach: sandbox.stub()\n\t\t};\n\n\t\treturn {\n\t\t\tdeedChooser: { deed: { name: 'cc-by-sa-4.0' } },\n\n\t\t\ton: function () {},\n\n\t\t\tdetails: {\n\t\t\t\tgetSerialized: stubs.getSerialized,\n\t\t\t\tsetSerialized: stubs.setSerialized,\n\t\t\t\tattach: stubs.attach,\n\t\t\t\ton: function () {}\n\t\t\t},\n\n\t\t\tstate: aborted ? 'aborted' : 'stashed',\n\n\t\t\tstubs: stubs\n\t\t};\n\t}\n\n\tQUnit.test( 'Constructor sense-check', ( assert ) => {\n\t\tconst step = new uw.controller.Details( new mw.Api(), {\n\t\t\tmaxSimultaneousConnections: 1\n\t\t} );\n\t\tassert.true( step instanceof uw.controller.Step );\n\t\tassert.true( !!step.ui );\n\t} );\n\n\tQUnit.test( 'load', function ( assert ) {\n\t\tconst step = new uw.controller.Details( new mw.Api(), {\n\t\t\t\tmaxSimultaneousConnections: 1\n\t\t\t} ),\n\t\t\tstepUiStub = this.sandbox.stub( step.ui, 'load' );\n\n\t\tlet testUpload = createTestUpload( this.sandbox );\n\n\t\t// replace createDetails with a stub; UploadWizardDetails needs way too\n\t\t// much setup to actually be able to create it\n\t\tstep.createDetails = this.sandbox.stub();\n\n\t\tstep.load( [ testUpload ] );\n\n\t\tassert.strictEqual( step.createDetails.callCount, 1 );\n\t\tassert.true( stepUiStub.called );\n\n\t\ttestUpload = createTestUpload( this.sandbox, true );\n\t\tstep.load( [ testUpload ] );\n\n\t\tassert.strictEqual( step.createDetails.callCount, 2 );\n\t\tassert.true( stepUiStub.called );\n\n\t\ttestUpload = createTestUpload( this.sandbox );\n\t\tstep.load( [ testUpload, createTestUpload( this.sandbox ) ] );\n\n\t\tassert.strictEqual( step.createDetails.callCount, 4 );\n\t\tassert.true( stepUiStub.called );\n\n\t\ttestUpload = createTestUpload( this.sandbox );\n\t\tstep.load( [ testUpload, createTestUpload( this.sandbox, false, true ) ] );\n\n\t\tassert.strictEqual( step.createDetails.callCount, 6 );\n\t\tassert.true( stepUiStub.called );\n\t} );\n\n\tQUnit.test( 'canTransition', ( assert ) => {\n\t\tconst upload = {},\n\t\t\tstep = new uw.controller.Details( new mw.Api(), {\n\t\t\t\tmaxSimultaneousConnections: 1\n\t\t\t} );\n\n\t\tassert.strictEqual( step.canTransition( upload ), false );\n\t\tupload.state = 'details';\n\t\tassert.strictEqual( step.canTransition( upload ), true );\n\t\tupload.state = 'complete';\n\t\tassert.strictEqual( step.canTransition( upload ), false );\n\t} );\n\n\tQUnit.test( 'transitionAll', function ( assert ) {\n\t\tconst done = assert.async(),\n\t\t\tdonestub = this.sandbox.stub(),\n\t\t\tds = [ $.Deferred(), $.Deferred(), $.Deferred() ],\n\t\t\tps = [ ds[ 0 ].promise(), ds[ 1 ].promise(), ds[ 2 ].promise() ];\n\n\t\tconst tostub = this.sandbox.stub( uw.controller.Details.prototype, 'transitionOne' );\n\t\ttostub.onFirstCall().returns( ps[ 0 ] );\n\t\ttostub.onSecondCall().returns( ps[ 1 ] );\n\t\ttostub.onThirdCall().returns( ps[ 2 ] );\n\n\t\tthis.sandbox.stub( uw.controller.Details.prototype, 'canTransition' ).returns( true );\n\n\t\tconst step = new uw.controller.Details( new mw.Api(), {\n\t\t\tmaxSimultaneousConnections: 3\n\t\t} );\n\n\t\tstep.uploads = [\n\t\t\t{ id: 15 },\n\t\t\tundefined,\n\t\t\t{ id: 21 },\n\t\t\t{ id: 'aoeu' }\n\t\t];\n\n\t\tstep.transitionAll().done( donestub );\n\t\tsetTimeout( () => {\n\t\t\tconst calls = [ tostub.getCall( 0 ), tostub.getCall( 1 ), tostub.getCall( 2 ) ];\n\n\t\t\tassert.strictEqual( calls[ 0 ].args[ 0 ].id, 15 );\n\t\t\tassert.strictEqual( calls[ 1 ].args[ 0 ].id, 21 );\n\n\t\t\tds[ 0 ].resolve();\n\t\t\tds[ 1 ].resolve();\n\t\t\tsetTimeout( () => {\n\t\t\t\tassert.strictEqual( donestub.called, false );\n\n\t\t\t\tds[ 2 ].resolve();\n\t\t\t\tsetTimeout( () => {\n\t\t\t\t\tassert.true( donestub.called );\n\n\t\t\t\t\tdone();\n\t\t\t\t} );\n\t\t\t} );\n\t\t} );\n\t} );\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/controller/uw.controller.Step.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/controller/uw.controller.Thanks.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/controller/uw.controller.Tutorial.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/controller/uw.controller.Upload.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/details/uw.LocationDetailsWidget.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/mw.FlickrChecker.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/mw.UploadWizardLicenseInput.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/mw.UploadWizardUpload.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/mw.fileApi.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/transports/mw.FormDataTransport.test.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":142,"column":3,"nodeType":"CallExpression","endLine":148,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":175,"column":10,"nodeType":"CallExpression","endLine":178,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .done","line":194,"column":10,"nodeType":"CallExpression","endLine":197,"endColumn":6},{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":214,"column":3,"nodeType":"CallExpression","endLine":218,"endColumn":6}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":4,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function () {\n\tQUnit.module( 'mw.FormDataTransport', QUnit.newMwEnvironment() );\n\n\tfunction createTransport( chunkSize, api ) {\n\t\tchunkSize = chunkSize || 0;\n\t\tapi = api || {};\n\n\t\tconst config = {\n\t\t\tuseRetryTimeout: false,\n\t\t\tchunkSize: chunkSize,\n\t\t\tmaxPhpUploadSize: chunkSize\n\t\t};\n\n\t\treturn new mw.FormDataTransport( api, {}, config );\n\t}\n\n\tQUnit.test( 'Constructor sense-check', ( assert ) => {\n\t\tconst transport = createTransport();\n\n\t\tassert.true( !!transport );\n\t} );\n\n\tQUnit.test( 'abort', function ( assert ) {\n\t\tconst transport = createTransport( 0 ),\n\t\t\trequest = $.Deferred().promise( { abort: this.sandbox.stub() } );\n\n\t\ttransport.request = request;\n\n\t\tassert.true( request.abort.notCalled );\n\n\t\ttransport.abort();\n\n\t\tassert.true( request.abort.called );\n\t\tassert.true( transport.aborted );\n\t} );\n\n\tQUnit.test( 'createParams', ( assert ) => {\n\t\tconst transport = createTransport( 10 ),\n\t\t\tparams = transport.createParams( 'foobar.jpg', 0 );\n\n\t\tassert.true( !!params );\n\n\t\tassert.strictEqual( params.filename, 'foobar.jpg' );\n\t\tassert.strictEqual( params.offset, 0 );\n\t} );\n\n\tQUnit.test( 'post', function ( assert ) {\n\t\tconst stub = this.sandbox.stub(),\n\t\t\t// post() works on a promise and binds .then, so we have to make\n\t\t\t// sure it actually is a promise, but also that it calls our stub\n\t\t\ttransport = createTransport( 10, { post: function () {\n\t\t\t\tstub();\n\t\t\t\treturn $.Deferred().resolve();\n\t\t\t} } );\n\n\t\tthis.sandbox.useFakeServer();\n\n\t\tassert.true( stub.notCalled );\n\n\t\ttransport.post( {} );\n\n\t\tassert.true( stub.called );\n\t} );\n\n\tQUnit.test( 'upload', function ( assert ) {\n\t\tconst transport = createTransport( 10, new mw.Api() ),\n\t\t\tfakeFile = {\n\t\t\t\tname: 'test file for fdt.jpg',\n\t\t\t\tsize: 5\n\t\t\t};\n\n\t\tthis.sandbox.useFakeServer();\n\n\t\ttransport.upload( fakeFile, 'test file for fdt.jpg' );\n\n\t\tassert.strictEqual( this.sandbox.server.requests.length, 1 );\n\t\tconst request = this.sandbox.server.requests[ 0 ];\n\t\tassert.strictEqual( request.method, 'POST' );\n\t\tassert.strictEqual( request.url, mw.util.wikiScript( 'api' ) );\n\t\tassert.true( request.async );\n\n\t\ttransport.abort();\n\t} );\n\n\tQUnit.test( 'uploadChunk', function ( assert ) {\n\t\tconst transport = createTransport( 10, new mw.Api() ),\n\t\t\tfakeFile = {\n\t\t\t\tname: 'test file for fdt.jpg',\n\t\t\t\tsize: 20,\n\t\t\t\tslice: function ( offset ) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tname: 'test file for fdt.jpg',\n\t\t\t\t\t\toffset: offset,\n\t\t\t\t\t\tsize: 10\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t};\n\n\t\tthis.sandbox.useFakeServer();\n\n\t\ttransport.uploadChunk( fakeFile, 0 );\n\n\t\tassert.strictEqual( this.sandbox.server.requests.length, 1 );\n\t\tconst request = this.sandbox.server.requests[ 0 ];\n\t\tassert.strictEqual( request.method, 'POST' );\n\t\tassert.strictEqual( request.url, mw.util.wikiScript( 'api' ) );\n\t\tassert.true( request.async );\n\n\t\ttransport.abort();\n\t} );\n\n\t// test invalid server response (in missing 'stage' param)\n\tQUnit.test( 'checkStatus invalid API response', function ( assert ) {\n\t\tconst done = assert.async(),\n\t\t\ttransport = createTransport( 10, new mw.Api() ),\n\t\t\ttstub = this.sandbox.stub(),\n\t\t\tpoststub = this.sandbox.stub( transport.api, 'post' ),\n\t\t\tpostd = $.Deferred();\n\n\t\t// prepare a bogus invalid API result\n\t\tpoststub.returns( postd.promise() );\n\t\tpostd.resolve( { upload: { result: 'Poll' } } );\n\n\t\t// call tstub upon checkStatus failure, and verify it got called correctly\n\t\ttransport.checkStatus().fail( tstub, () => {\n\t\t\tassert.true( tstub.calledWith( 'server-error', { errors: [ {\n\t\t\t\tcode: 'server-error',\n\t\t\t\thtml: mw.message( 'api-clientside-error-invalidresponse' ).parse()\n\t\t\t} ] } ) );\n\t\t\tdone();\n\t\t} );\n\t} );\n\n\t// test retry after server responds upload is still incomplete\n\tQUnit.test( 'checkStatus retry', function ( assert ) {\n\t\tconst transport = createTransport( 10, new mw.Api() ),\n\t\t\tusstub = this.sandbox.stub(),\n\t\t\tpoststub = this.sandbox.stub( transport.api, 'post' ),\n\t\t\tpostd = $.Deferred(),\n\t\t\tpostd2 = $.Deferred();\n\n\t\ttransport.on( 'update-stage', usstub );\n\n\t\t// prepare a first API call that responds with 'Poll' (upload\n\t\t// concatenation is not yet complete) followed by a second call that\n\t\t// marks the upload successful\n\t\tpoststub\n\t\t\t.onFirstCall().returns( postd.promise() )\n\t\t\t.onSecondCall().returns( postd2.promise() );\n\n\t\t// resolve 3 API calls, where server first responds upload is not yet\n\t\t// assembled, and second says it's published\n\t\tpostd.resolve( { upload: { result: 'Poll', stage: 'queued' } } );\n\t\tpostd2.resolve( { upload: { result: 'Success' } } );\n\n\t\t// confirm that, once second API call was successful, status resolves,\n\t\t// 2 API calls have gone out & the failed call updates stage accordingly\n\t\treturn transport.checkStatus().done( () => {\n\t\t\tassert.true( poststub.calledTwice );\n\t\t\tassert.true( usstub.firstCall.calledWith( 'queued' ) );\n\t\t} );\n\t} );\n\n\tQUnit.test( 'checkStatus success', function ( assert ) {\n\t\tconst transport = createTransport( 10, new mw.Api() ),\n\t\t\ttstub = this.sandbox.stub(),\n\t\t\tusstub = this.sandbox.stub(),\n\t\t\tpoststub = this.sandbox.stub( transport.api, 'post' ),\n\t\t\tpostd = $.Deferred();\n\n\t\ttransport.on( 'update-stage', usstub );\n\n\t\t// prepare a bogus valid API result\n\t\tpoststub.returns( postd.promise() );\n\t\tpostd.resolve( 'testing' );\n\n\t\treturn transport.checkStatus().done( tstub, () => {\n\t\t\tassert.true( tstub.calledWith( 'testing' ) );\n\t\t\tassert.false( usstub.called );\n\t\t} );\n\t} );\n\n\tQUnit.test( 'checkStatus error API response', function ( assert ) {\n\t\tconst done = assert.async(),\n\t\t\ttransport = createTransport( 10, new mw.Api() ),\n\t\t\ttstub = this.sandbox.stub(),\n\t\t\tusstub = this.sandbox.stub(),\n\t\t\tpoststub = this.sandbox.stub( transport.api, 'post' ),\n\t\t\tpostd = $.Deferred();\n\n\t\ttransport.on( 'update-stage', usstub );\n\n\t\t// prepare an error API response\n\t\tpoststub.returns( postd.promise() );\n\t\tpostd.reject( 'testing', { error: 'testing' } );\n\n\t\ttransport.checkStatus().fail( tstub, () => {\n\t\t\tassert.true( tstub.calledWith( 'testing', { error: 'testing' } ) );\n\t\t\tassert.false( usstub.called );\n\t\t\tdone();\n\t\t} );\n\t} );\n\n}() );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/uw.ConcurrentQueue.test.js","messages":[{"ruleId":"no-jquery/no-done-fail","severity":1,"message":"Prefer .then to .fail","line":54,"column":5,"nodeType":"CallExpression","endLine":54,"endColumn":87}],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"/*\n * This file is part of the MediaWiki extension UploadWizard.\n *\n * UploadWizard is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 2 of the License, or\n * (at your option) any later version.\n *\n * UploadWizard is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with UploadWizard. If not, see <http://www.gnu.org/licenses/>.\n */\n\n( function ( uw ) {\n\tQUnit.module( 'mw.uploadWizard.ConcurrentQueue', QUnit.newMwEnvironment() );\n\n\t// This is a bogus action that will be executed for every item added to the\n\t// queue. We just need to make sure that the action doesn't complete\n\t// immediately, or the order some methods are called in could be slightly\n\t// different (e.g. when adding a new item after one has just completed will\n\t// trigger the next one to execute, which would terminate immediately,\n\t// instead of giving time for a second new thingy to be added)\n\tfunction queueAction() {\n\t\tconst deferred = $.Deferred();\n\t\tsetTimeout( deferred.resolve, 10 );\n\t\treturn deferred.promise();\n\t}\n\n\t// Asserts that the given stub functions were called in the given order.\n\t// SinonJS's assert.callOrder doesn't allow to check individual calls.\n\tfunction assertCalledInOrder() {\n\t\t// Map stubs to specific calls\n\t\tconst calls = Array.prototype.map.call( arguments, ( spy ) => {\n\t\t\tif ( !spy.assertCallsInOrderLastCall ) {\n\t\t\t\tspy.assertCallsInOrderLastCall = 0;\n\t\t\t}\n\t\t\treturn spy.getCall( spy.assertCallsInOrderLastCall++ );\n\t\t} );\n\t\tlet nextSpyCall;\n\t\t// Assert stuff\n\t\tfor ( let i = 0; i < calls.length - 1; i++ ) {\n\t\t\tconst currSpyCall = calls[ i ];\n\t\t\tnextSpyCall = calls[ i + 1 ];\n\t\t\tif ( currSpyCall ) {\n\t\t\t\tQUnit.assert.true(\n\t\t\t\t\tcurrSpyCall.callId < ( nextSpyCall ? nextSpyCall.callId : -1 ),\n\t\t\t\t\t'Call ' + ( i + 1 ) + ' (callId ' + currSpyCall.callId + ') is in the right order'\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tQUnit.assert.fail( 'Call ' + ( i + 1 ) + ' (never called) is in the right order' );\n\t\t\t}\n\t\t}\n\t\tQUnit.assert.true(\n\t\t\t!!nextSpyCall,\n\t\t\t'Call ' + calls.length + ' is in the right order'\n\t\t);\n\t}\n\n\tQUnit.test( 'Basic behavior', ( assert ) => {\n\t\tconst done = assert.async();\n\t\tconst action = sinon.spy( queueAction );\n\t\tconst queue = new uw.ConcurrentQueue( {\n\t\t\tcount: 3,\n\t\t\taction: action\n\t\t} );\n\n\t\tqueue.on( 'progress', () => {\n\t\t\tQUnit.assert.true( queue.running.length <= 3, 'No more than 3 items are executing' );\n\t\t} );\n\n\t\tqueue.on( 'complete', () => {\n\t\t\t// All items executed\n\t\t\tsinon.assert.callCount( action, 5 );\n\t\t\t// All items executed in the expected order\n\t\t\tsinon.assert.calledWith( action.getCall( 0 ), 'a' );\n\t\t\tsinon.assert.calledWith( action.getCall( 1 ), 'b' );\n\t\t\tsinon.assert.calledWith( action.getCall( 2 ), 'c' );\n\t\t\tsinon.assert.calledWith( action.getCall( 3 ), 'd' );\n\t\t\tsinon.assert.calledWith( action.getCall( 4 ), 'e' );\n\n\t\t\tdone();\n\t\t} );\n\n\t\t[ 'a', 'b', 'c', 'd', 'e' ].forEach( ( v ) => {\n\t\t\tqueue.addItem( v );\n\t\t} );\n\n\t\tqueue.startExecuting();\n\t} );\n\n\tQUnit.test( 'Event emitting', ( assert ) => {\n\t\tconst done = assert.async();\n\t\tconst changeHandler = sinon.stub();\n\t\tconst progressHandler = sinon.stub();\n\t\tconst completeHandler = sinon.stub();\n\t\tconst queue = new uw.ConcurrentQueue( {\n\t\t\tcount: 3,\n\t\t\taction: queueAction\n\t\t} );\n\n\t\tqueue.connect( null, {\n\t\t\tchange: changeHandler,\n\t\t\tprogress: progressHandler,\n\t\t\tcomplete: completeHandler\n\t\t} );\n\n\t\tqueue.on( 'complete', () => {\n\t\t\tsinon.assert.callCount( changeHandler, 3 );\n\t\t\tsinon.assert.callCount( progressHandler, 3 );\n\t\t\tsinon.assert.callCount( completeHandler, 1 );\n\n\t\t\tassertCalledInOrder(\n\t\t\t\tchangeHandler, // Added 'a'\n\t\t\t\tchangeHandler, // Added 'b'\n\t\t\t\tchangeHandler, // Added 'c'\n\t\t\t\tprogressHandler, // Finished 'a', 'b' or 'c'\n\t\t\t\tprogressHandler, // Finished 'a', 'b' or 'c'\n\t\t\t\tprogressHandler, // Finished 'a', 'b' or 'c'\n\t\t\t\tcompleteHandler\n\t\t\t);\n\n\t\t\tdone();\n\t\t} );\n\n\t\tqueue.addItem( 'a' );\n\t\tqueue.addItem( 'b' );\n\t\tqueue.addItem( 'c' );\n\t\tqueue.startExecuting();\n\t} );\n\n\tQUnit.test( 'Restarting a completed queue', ( assert ) => {\n\t\tconst done = assert.async();\n\t\tconst queue = new uw.ConcurrentQueue( {\n\t\t\tcount: 3,\n\t\t\taction: queueAction\n\t\t} );\n\n\t\tqueue.addItem( 'a' );\n\t\tqueue.addItem( 'b' );\n\t\tqueue.addItem( 'c' );\n\n\t\tqueue.once( 'complete', () => {\n\t\t\tQUnit.assert.equal( queue.completed, true );\n\t\t\tqueue.addItem( 'd' );\n\t\t\tqueue.addItem( 'e' );\n\n\t\t\tqueue.once( 'complete', () => {\n\t\t\t\tQUnit.assert.equal( queue.completed, true );\n\t\t\t\tdone();\n\t\t\t} );\n\n\t\t\tqueue.startExecuting();\n\t\t} );\n\n\t\tqueue.startExecuting();\n\t} );\n\n\tQUnit.test( 'Empty queue completes', ( assert ) => {\n\t\tconst done = assert.async();\n\t\tconst queue = new uw.ConcurrentQueue( {\n\t\t\tcount: 3,\n\t\t\taction: queueAction\n\t\t} );\n\n\t\tqueue.on( 'complete', () => {\n\t\t\tQUnit.assert.equal( queue.completed, true );\n\n\t\t\tdone();\n\t\t} );\n\n\t\tqueue.startExecuting();\n\t} );\n\n\tQUnit.test( 'Adding new items while queue running', ( assert ) => {\n\t\tconst done = assert.async();\n\t\tconst changeHandler = sinon.stub();\n\t\tconst progressHandler = sinon.stub();\n\t\tconst completeHandler = sinon.stub();\n\t\tconst queue = new uw.ConcurrentQueue( {\n\t\t\tcount: 2,\n\t\t\taction: queueAction\n\t\t} );\n\n\t\tqueue.connect( null, {\n\t\t\tchange: changeHandler,\n\t\t\tprogress: progressHandler,\n\t\t\tcomplete: completeHandler\n\t\t} );\n\n\t\tqueue.on( 'complete', () => {\n\t\t\tsinon.assert.callCount( changeHandler, 6 );\n\t\t\tsinon.assert.callCount( progressHandler, 6 );\n\t\t\tsinon.assert.callCount( completeHandler, 1 );\n\n\t\t\tassertCalledInOrder(\n\t\t\t\tchangeHandler, // Added 'a'\n\t\t\t\tchangeHandler, // Added 'b'\n\t\t\t\tchangeHandler, // Added 'c'\n\t\t\t\tprogressHandler, // Finished 'a' or 'b'\n\t\t\t\tchangeHandler, // Added 'd'\n\t\t\t\tchangeHandler, // Added 'e'\n\t\t\t\tprogressHandler, // Finished 'a', 'b' or 'c'\n\t\t\t\tprogressHandler, // Finished 'a', 'b', 'c' or 'd'\n\t\t\t\tprogressHandler, // Finished 'a', 'b', 'c', 'd' or 'e'\n\t\t\t\tprogressHandler, // Finished 'a', 'b', 'c', 'd' or 'e'\n\t\t\t\tchangeHandler, // Added 'f'\n\t\t\t\tprogressHandler, // Finished f'\n\t\t\t\tcompleteHandler\n\t\t\t);\n\n\t\t\tdone();\n\t\t} );\n\n\t\tqueue.addItem( 'a' );\n\t\tqueue.addItem( 'b' );\n\t\tqueue.addItem( 'c' );\n\t\tqueue.once( 'progress', () => {\n\t\t\tqueue.addItem( 'd' );\n\t\t\tqueue.addItem( 'e' );\n\t\t} );\n\t\tqueue.on( 'progress', () => {\n\t\t\tif ( queue.done.length === 5 ) {\n\t\t\t\tqueue.addItem( 'f' );\n\t\t\t}\n\t\t} );\n\t\tqueue.startExecuting();\n\t} );\n\n\tQUnit.test( 'Deleting items while queue running', ( assert ) => {\n\t\tconst done = assert.async();\n\t\tconst changeHandler = sinon.stub();\n\t\tconst progressHandler = sinon.stub();\n\t\tconst completeHandler = sinon.stub();\n\t\tconst queue = new uw.ConcurrentQueue( {\n\t\t\tcount: 2,\n\t\t\taction: queueAction\n\t\t} );\n\n\t\tqueue.connect( null, {\n\t\t\tchange: changeHandler,\n\t\t\tprogress: progressHandler,\n\t\t\tcomplete: completeHandler\n\t\t} );\n\n\t\tqueue.on( 'complete', () => {\n\t\t\tsinon.assert.callCount( changeHandler, 8 );\n\t\t\tsinon.assert.callCount( progressHandler, 4 );\n\t\t\tsinon.assert.callCount( completeHandler, 1 );\n\n\t\t\tassertCalledInOrder(\n\t\t\t\tchangeHandler, // Added 'a'\n\t\t\t\tchangeHandler, // Added 'b'\n\t\t\t\tchangeHandler, // Added 'c'\n\t\t\t\tchangeHandler, // Added 'd'\n\t\t\t\tchangeHandler, // Added 'e'\n\t\t\t\tchangeHandler, // Added 'f'\n\t\t\t\tprogressHandler, // Finished 'a' or 'b'\n\t\t\t\tchangeHandler, // Removed first of the queued (not executing), which is 'd'\n\t\t\t\tprogressHandler, // Finished 'a', 'b' or 'c'\n\t\t\t\tchangeHandler, // Removed the last one queued (not executing), which is 'f'\n\t\t\t\tprogressHandler, // Finished 'a', 'b', 'c' or 'e'\n\t\t\t\tprogressHandler, // Finished 'a', 'b', 'c' or 'e'\n\t\t\t\tcompleteHandler\n\t\t\t);\n\n\t\t\tdone();\n\t\t} );\n\n\t\tqueue.addItem( 'a' );\n\t\tqueue.addItem( 'b' );\n\t\tqueue.addItem( 'c' );\n\t\tqueue.addItem( 'd' );\n\t\tqueue.addItem( 'e' );\n\t\tqueue.addItem( 'f' );\n\t\tqueue.once( 'progress', () => {\n\t\t\tqueue.removeItem( queue.queued[ 0 ] );\n\n\t\t\tqueue.once( 'progress', () => {\n\t\t\t\tqueue.removeItem( queue.queued[ 0 ] );\n\t\t\t} );\n\t\t} );\n\t\tqueue.startExecuting();\n\t} );\n\n\tQUnit.test( 'Deleting currently running item', ( assert ) => {\n\t\tconst done = assert.async();\n\t\tconst action = sinon.spy( queueAction );\n\t\tconst changeHandler = sinon.stub();\n\t\tconst progressHandler = sinon.stub();\n\t\tconst completeHandler = sinon.stub();\n\t\tconst queue = new uw.ConcurrentQueue( {\n\t\t\tcount: 2,\n\t\t\taction: action\n\t\t} );\n\n\t\tqueue.connect( null, {\n\t\t\tchange: changeHandler,\n\t\t\tprogress: progressHandler,\n\t\t\tcomplete: completeHandler\n\t\t} );\n\n\t\tqueue.on( 'complete', () => {\n\t\t\t// Every item in the queue was executed...\n\t\t\tsinon.assert.callCount( action, 4 );\n\n\t\t\tsinon.assert.callCount( changeHandler, 5 );\n\t\t\t// ...but the one we removed wasn't registered as finished\n\t\t\tsinon.assert.callCount( progressHandler, 3 );\n\t\t\tsinon.assert.callCount( completeHandler, 1 );\n\n\t\t\tassertCalledInOrder(\n\t\t\t\tchangeHandler, // Added 'a'\n\t\t\t\tchangeHandler, // Added 'b'\n\t\t\t\tchangeHandler, // Added 'c'\n\t\t\t\tchangeHandler, // Added 'd'\n\t\t\t\taction, // Started 'a'\n\t\t\t\taction, // Started 'b'\n\t\t\t\tprogressHandler, // Finished 'a' or 'b'\n\t\t\t\tchangeHandler, // Removed first of the executing, which is 'a' or 'b'\n\t\t\t\taction, // Started 'c'\n\t\t\t\taction, // Started 'd' - note how two threads are running still\n\t\t\t\tprogressHandler, // Finished 'c' or 'd'\n\t\t\t\tprogressHandler, // Finished 'c' or 'd'\n\t\t\t\tcompleteHandler\n\t\t\t);\n\n\t\t\tdone();\n\t\t} );\n\n\t\tqueue.addItem( 'a' );\n\t\tqueue.addItem( 'b' );\n\t\tqueue.addItem( 'c' );\n\t\tqueue.addItem( 'd' );\n\t\tqueue.once( 'progress', () => {\n\t\t\tqueue.removeItem( queue.running[ 0 ] );\n\t\t} );\n\t\tqueue.startExecuting();\n\t} );\n\n\tQUnit.test( 'Adding a new item when almost done', ( assert ) => {\n\t\tconst done = assert.async();\n\t\t// This test seems extra flaky and was occasionally failing, double the delays\n\t\tconst action = sinon.spy( queueAction );\n\t\tconst changeHandler = sinon.stub();\n\t\tconst progressHandler = sinon.stub();\n\t\tconst completeHandler = sinon.stub();\n\t\tconst queue = new uw.ConcurrentQueue( {\n\t\t\tcount: 2,\n\t\t\taction: action\n\t\t} );\n\n\t\tqueue.connect( null, {\n\t\t\tchange: changeHandler,\n\t\t\tprogress: progressHandler,\n\t\t\tcomplete: completeHandler\n\t\t} );\n\n\t\tqueue.on( 'complete', () => {\n\t\t\tsinon.assert.callCount( action, 5 );\n\t\t\tsinon.assert.callCount( changeHandler, 5 );\n\t\t\tsinon.assert.callCount( progressHandler, 5 );\n\t\t\tsinon.assert.callCount( completeHandler, 1 );\n\n\t\t\tassertCalledInOrder(\n\t\t\t\tchangeHandler, // Added 'a'\n\t\t\t\tchangeHandler, // Added 'b'\n\t\t\t\tchangeHandler, // Added 'c'\n\t\t\t\tchangeHandler, // Added 'd'\n\t\t\t\taction, // Started 'a'\n\t\t\t\taction, // Started 'b'\n\t\t\t\tprogressHandler, // Finished 'a' or 'b'\n\t\t\t\taction, // Started 'c'\n\t\t\t\tprogressHandler, // Finished 'a', 'b' or 'c'\n\t\t\t\taction, // Started 'd'\n\t\t\t\tprogressHandler, // Finished 'a', 'b', 'c' or 'd'\n\t\t\t\tchangeHandler, // Added 'e'\n\t\t\t\taction, // Started 'e' -- this starts a new thread\n\t\t\t\tprogressHandler, // Finished 'a', 'b', 'c', 'd' or 'e'\n\t\t\t\tprogressHandler, // Finished 'a', 'b', 'c', 'd' or 'e'\n\t\t\t\tcompleteHandler\n\t\t\t);\n\n\t\t\tdone();\n\t\t} );\n\n\t\tqueue.addItem( 'a' );\n\t\tqueue.addItem( 'b' );\n\t\tqueue.addItem( 'c' );\n\t\tqueue.addItem( 'd' );\n\t\tconst onProgress = function () {\n\t\t\tif ( queue.done.length === 3 ) {\n\t\t\t\tqueue.addItem( 'e' );\n\t\t\t\tqueue.off( 'progress', onProgress );\n\t\t\t}\n\t\t};\n\t\tqueue.on( 'progress', onProgress );\n\t\tqueue.startExecuting();\n\t} );\n\n}( mw.uploadWizard ) );\n","usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/uw.CopyMetadataWidget.test.js","messages":[],"suppressedMessages":[],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]},{"filePath":"/src/repo/tests/qunit/uw.TitleDetailsWidget.test.js","messages":[],"suppressedMessages":[{"ruleId":"camelcase","severity":2,"message":"Identifier 'user_talk' is not in camel case.","line":67,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":67,"endColumn":14,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'wikipedia_talk' is not in camel case.","line":69,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":69,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'file_talk' is not in camel case.","line":71,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":71,"endColumn":14,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'mediawiki_talk' is not in camel case.","line":73,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":73,"endColumn":19,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'template_talk' is not in camel case.","line":75,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":75,"endColumn":18,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'help_talk' is not in camel case.","line":77,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":77,"endColumn":14,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'category_talk' is not in camel case.","line":79,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":79,"endColumn":18,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'image_talk' is not in camel case.","line":81,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":81,"endColumn":15,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'project_talk' is not in camel case.","line":83,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":83,"endColumn":17,"suppressions":[{"kind":"directive","justification":""}]},{"ruleId":"camelcase","severity":2,"message":"Identifier 'antarctic_waterfowl' is not in camel case.","line":86,"column":5,"nodeType":"Identifier","messageId":"notCamelCase","endLine":86,"endColumn":24,"suppressions":[{"kind":"directive","justification":""}]}],"errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":[{"ruleId":"arrow-parens","replacedBy":[]},{"ruleId":"arrow-spacing","replacedBy":[]},{"ruleId":"lines-between-class-members","replacedBy":[]},{"ruleId":"no-new-require","replacedBy":[]},{"ruleId":"template-curly-spacing","replacedBy":[]},{"ruleId":"implicit-arrow-linebreak","replacedBy":[]},{"ruleId":"array-bracket-spacing","replacedBy":[]},{"ruleId":"block-spacing","replacedBy":[]},{"ruleId":"brace-style","replacedBy":[]},{"ruleId":"comma-dangle","replacedBy":[]},{"ruleId":"comma-spacing","replacedBy":[]},{"ruleId":"comma-style","replacedBy":[]},{"ruleId":"computed-property-spacing","replacedBy":[]},{"ruleId":"dot-location","replacedBy":[]},{"ruleId":"eol-last","replacedBy":[]},{"ruleId":"func-call-spacing","replacedBy":[]},{"ruleId":"indent","replacedBy":[]},{"ruleId":"key-spacing","replacedBy":[]},{"ruleId":"keyword-spacing","replacedBy":[]},{"ruleId":"linebreak-style","replacedBy":[]},{"ruleId":"max-statements-per-line","replacedBy":[]},{"ruleId":"new-parens","replacedBy":[]},{"ruleId":"no-floating-decimal","replacedBy":[]},{"ruleId":"no-multi-spaces","replacedBy":[]},{"ruleId":"no-multiple-empty-lines","replacedBy":[]},{"ruleId":"no-new-object","replacedBy":["no-object-constructor"]},{"ruleId":"no-tabs","replacedBy":[]},{"ruleId":"no-trailing-spaces","replacedBy":[]},{"ruleId":"no-whitespace-before-property","replacedBy":[]},{"ruleId":"object-curly-spacing","replacedBy":[]},{"ruleId":"operator-linebreak","replacedBy":[]},{"ruleId":"quote-props","replacedBy":[]},{"ruleId":"quotes","replacedBy":[]},{"ruleId":"semi","replacedBy":[]},{"ruleId":"semi-spacing","replacedBy":[]},{"ruleId":"semi-style","replacedBy":[]},{"ruleId":"space-before-blocks","replacedBy":[]},{"ruleId":"space-before-function-paren","replacedBy":[]},{"ruleId":"space-in-parens","replacedBy":[]},{"ruleId":"space-infix-ops","replacedBy":[]},{"ruleId":"space-unary-ops","replacedBy":[]},{"ruleId":"spaced-comment","replacedBy":[]},{"ruleId":"switch-colon-spacing","replacedBy":[]},{"ruleId":"wrap-iife","replacedBy":[]},{"ruleId":"no-extra-semi","replacedBy":[]},{"ruleId":"no-mixed-spaces-and-tabs","replacedBy":[]}]}]
--- end ---
$ /usr/bin/npm ci
--- stderr ---
npm WARN deprecated @humanwhocodes/config-array@0.13.0: Use @eslint/config-array instead
npm WARN deprecated @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead
npm WARN deprecated eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options.
--- stdout ---
added 484 packages, and audited 485 packages in 5s
107 packages are looking for funding
run `npm fund` for details
1 moderate severity vulnerability
To address all issues, run:
npm audit fix
Run `npm audit` for details.
--- end ---
$ /usr/bin/npm test
--- stdout ---
> test
> grunt test
Running "eslint:all" (eslint) task
/src/repo/resources/controller/uw.controller.Deed.js
23:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
47:3 warning Prefer .then to .done no-jquery/no-done-fail
94:22 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
293:10 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
305:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/resources/controller/uw.controller.Details.js
23:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
104:3 warning ES2025 'Iterator.prototype.some' method is forbidden es-x/no-iterator-prototype-some
164:3 warning Prefer .then to .done no-jquery/no-done-fail
176:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/resources/controller/uw.controller.Step.js
25:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
76:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
82:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
95:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
105:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
/src/repo/resources/controller/uw.controller.Thanks.js
23:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
/src/repo/resources/controller/uw.controller.Tutorial.js
24:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
61:3 warning Prefer .then to .done no-jquery/no-done-fail
61:3 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/resources/controller/uw.controller.Upload.js
24:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
/src/repo/resources/deed/uw.deed.Abstract.js
48:14 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
83:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
160:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
173:1 warning The type 'uw.PatentDialog' is undefined jsdoc/no-undefined-types
190:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/resources/deed/uw.deed.OwnWork.js
478:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
683:1 warning The type 'uw.PatentDialog' is undefined jsdoc/no-undefined-types
/src/repo/resources/deed/uw.deed.ThirdParty.js
139:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
266:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/details/uw.CampaignDetailsWidget.js
6:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
/src/repo/resources/details/uw.CategoriesDetailsWidget.js
130:40 warning ES2025 'Iterator.prototype.some' method is forbidden es-x/no-iterator-prototype-some
213:34 warning Prefer .then to .fail no-jquery/no-done-fail
242:36 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/resources/details/uw.DateDetailsWidget.js
6:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
16:31 warning OO.ui.ButtonWidget has no label. Even icon-only buttons should set a label with invisibleLabel set to true mediawiki/no-unlabeled-buttonwidget
164:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
171:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
179:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
187:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
204:25 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/details/uw.LanguageDropdownWidget.js
14:53 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
/src/repo/resources/details/uw.LocationDetailsWidget.js
6:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
67:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/details/uw.MultipleLanguageInputWidget.js
7:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
134:10 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
181:10 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/details/uw.OtherDetailsWidget.js
7:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
/src/repo/resources/details/uw.SingleLanguageInputWidget.js
6:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
45:23 warning OO.ui.ButtonWidget has no label. Even icon-only buttons should set a label with invisibleLabel set to true mediawiki/no-unlabeled-buttonwidget
/src/repo/resources/details/uw.StatementWidget.js
70:1 warning The type 'dataValues.DataValue' is undefined jsdoc/no-undefined-types
105:1 warning The type 'datamodel.StatementList' is undefined jsdoc/no-undefined-types
108:22 warning ES2025 'Iterator.prototype.toArray' method is forbidden es-x/no-iterator-prototype-toarray
117:1 warning The type 'datamodel.StatementList' is undefined jsdoc/no-undefined-types
131:1 warning The type 'dataValues.DataValue' is undefined jsdoc/no-undefined-types
132:1 warning The type 'datamodel.Statement' is undefined jsdoc/no-undefined-types
145:1 warning The type 'datamodel.StatementList' is undefined jsdoc/no-undefined-types
/src/repo/resources/details/uw.TitleDetailsWidget.js
10:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
112:1 warning Missing JSDoc @param "thorough" type jsdoc/require-param-type
113:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
161:30 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
207:10 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
215:27 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
217:21 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/details/uw.UlsWidget.js
30:3 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
99:1 warning The type 'uw.UlsWidget' is undefined jsdoc/no-undefined-types
/src/repo/resources/mw.DestinationChecker.js
14:3 warning Found more than one @return declaration jsdoc/require-returns
14:3 warning Found more than one @return declaration jsdoc/require-returns-check
35:3 warning Found more than one @return declaration jsdoc/require-returns
35:3 warning Found more than one @return declaration jsdoc/require-returns-check
80:3 warning Found more than one @return declaration jsdoc/require-returns
80:3 warning Found more than one @return declaration jsdoc/require-returns-check
/src/repo/resources/mw.FlickrChecker.js
4:1 warning Missing JSDoc @param "ui" type jsdoc/require-param-type
5:1 warning Missing JSDoc @param "selectButton" type jsdoc/require-param-type
212:1 warning The type 'getPhotos' is undefined jsdoc/no-undefined-types
226:17 warning 'data' is already declared in the upper scope on line 219 column 15 no-shadow
246:1 warning The type 'getCollection' is undefined jsdoc/no-undefined-types
304:45 warning 'data' is already declared in the upper scope on line 293 column 15 no-shadow
314:1 warning The type 'getPhotos' is undefined jsdoc/no-undefined-types
331:1 warning The type 'getPhotos' is undefined jsdoc/no-undefined-types
375:10 warning Prefer .then to .fail no-jquery/no-done-fail
468:5 warning Prefer .then to .done no-jquery/no-done-fail
519:10 warning Prefer .then to .fail no-jquery/no-done-fail
581:4 warning Prefer .then to .done no-jquery/no-done-fail
582:46 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
678:4 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
696:39 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
/src/repo/resources/mw.UploadWizard.js
4:1 warning Missing JSDoc @param "uw" type jsdoc/require-param-type
9:1 warning Missing JSDoc @param "config" type jsdoc/require-param-type
112:16 warning 'steps' is already declared in the upper scope on line 86 column 10 no-shadow
/src/repo/resources/mw.UploadWizardDeedChooser.js
32:42 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
40:17 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
44:21 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/mw.UploadWizardDetails.js
498:5 warning Prefer .then to .done no-jquery/no-done-fail
555:9 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
560:14 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
665:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
678:1 warning Missing JSDoc @param "thorough" type jsdoc/require-param-type
715:4 warning Prefer .then to .done no-jquery/no-done-fail
844:20 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
935:3 warning JSDoc @return declaration present but return expression not available in function jsdoc/require-returns-check
1017:9 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
1018:45 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
/src/repo/resources/mw.UploadWizardLicenseInput.js
50:20 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
61:41 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
69:49 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
123:24 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
287:28 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
/src/repo/resources/mw.UploadWizardUpload.js
24:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
224:16 warning ES2015 'Uint8Array' is forbidden es-x/no-typed-arrays
380:3 warning Prefer .then to .done no-jquery/no-done-fail
380:3 warning Prefer .then to .fail no-jquery/no-done-fail
445:3 warning Prefer .then to .done no-jquery/no-done-fail
445:3 warning Prefer .then to .fail no-jquery/no-done-fail
766:3 warning Prefer .then to .done no-jquery/no-done-fail
766:3 warning Prefer .then to .fail no-jquery/no-done-fail
774:6 warning Prefer .then to .done no-jquery/no-done-fail
777:7 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/mw.UploadWizardUploadInterface.js
110:3 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
215:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/transports/mw.FormDataTransport.js
166:5 warning Prefer .then to .done no-jquery/no-done-fail
167:6 warning Prefer .then to .done no-jquery/no-done-fail
167:6 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/resources/ui/steps/uw.ui.Deed.js
23:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
51:3 warning Prefer .then to .done no-jquery/no-done-fail
86:5 warning Prefer .then to .done no-jquery/no-done-fail
108:6 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/ui/steps/uw.ui.Details.js
23:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
108:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/ui/steps/uw.ui.Thanks.js
23:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
150:3 warning Prefer .then to .done no-jquery/no-done-fail
180:4 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
/src/repo/resources/ui/steps/uw.ui.Tutorial.js
50:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
126:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/ui/steps/uw.ui.Upload.js
23:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
263:39 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
268:33 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
334:6 warning Prefer .then to .done no-jquery/no-done-fail
344:3 warning Prefer .then to .done no-jquery/no-done-fail
528:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/ui/uw.ui.Step.js
111:3 warning Prefer .then to .done no-jquery/no-done-fail
120:3 warning Prefer .then to .done no-jquery/no-done-fail
166:12 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
180:7 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
197:5 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/ui/uw.ui.Wizard.js
128:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
141:25 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
166:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
167:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
/src/repo/resources/uw.LicenseGroup.js
148:48 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
181:56 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
423:18 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
435:14 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
443:5 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
451:5 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
496:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
619:3 warning Prefer .then to .done no-jquery/no-done-fail
619:3 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/resources/uw.ValidatableElement.js
36:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/resources/uw.ValidationMessageElement.js
10:1 warning The type 'uw.ValidatableElement' is undefined jsdoc/no-undefined-types
48:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
60:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
92:10 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
92:10 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
/src/repo/resources/uw.ValidationStatus.js
11:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
20:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
29:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
38:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
47:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
56:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
85:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
92:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
99:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
100:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
115:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
116:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/tests/qunit/controller/uw.controller.Details.test.js
127:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/tests/qunit/transports/mw.FormDataTransport.test.js
142:3 warning Prefer .then to .fail no-jquery/no-done-fail
175:10 warning Prefer .then to .done no-jquery/no-done-fail
194:10 warning Prefer .then to .done no-jquery/no-done-fail
214:3 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/tests/qunit/uw.ConcurrentQueue.test.js
54:5 warning Prefer .then to .fail no-jquery/no-done-fail
✖ 180 problems (0 errors, 180 warnings)
Running "stylelint:all" (stylelint) task
>> Linted 18 files without errors
Running "banana:UploadWizard" (banana) task
>> 2 message directories checked.
Done.
--- end ---
$ /usr/bin/npm audit --json
--- stdout ---
{
"auditReportVersion": 2,
"vulnerabilities": {
"js-yaml": {
"name": "js-yaml",
"severity": "moderate",
"isDirect": false,
"via": [
{
"source": 1109801,
"name": "js-yaml",
"dependency": "js-yaml",
"title": "js-yaml has prototype pollution in merge (<<)",
"url": "https://github.com/advisories/GHSA-mh29-5h37-fv8m",
"severity": "moderate",
"cwe": [
"CWE-1321"
],
"cvss": {
"score": 5.3,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"
},
"range": "<3.14.2"
},
{
"source": 1109802,
"name": "js-yaml",
"dependency": "js-yaml",
"title": "js-yaml has prototype pollution in merge (<<)",
"url": "https://github.com/advisories/GHSA-mh29-5h37-fv8m",
"severity": "moderate",
"cwe": [
"CWE-1321"
],
"cvss": {
"score": 5.3,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"
},
"range": ">=4.0.0 <4.1.1"
}
],
"effects": [],
"range": "<3.14.2 || >=4.0.0 <4.1.1",
"nodes": [
"node_modules/@eslint/eslintrc/node_modules/js-yaml",
"node_modules/cosmiconfig/node_modules/js-yaml",
"node_modules/eslint/node_modules/js-yaml",
"node_modules/js-yaml"
],
"fixAvailable": true
}
},
"metadata": {
"vulnerabilities": {
"info": 0,
"low": 0,
"moderate": 1,
"high": 0,
"critical": 0,
"total": 1
},
"dependencies": {
"prod": 1,
"dev": 484,
"optional": 0,
"peer": 1,
"peerOptional": 0,
"total": 484
}
}
}
--- end ---
Attempting to npm audit fix
$ /usr/bin/npm audit fix --dry-run --only=dev --json
--- stderr ---
npm WARN invalid config only="dev" set in command line options
npm WARN invalid config Must be one of: null, prod, production
--- stdout ---
{
"added": 0,
"removed": 0,
"changed": 4,
"audited": 485,
"funding": 107,
"audit": {
"auditReportVersion": 2,
"vulnerabilities": {
"js-yaml": {
"name": "js-yaml",
"severity": "moderate",
"isDirect": false,
"via": [
{
"source": 1109801,
"name": "js-yaml",
"dependency": "js-yaml",
"title": "js-yaml has prototype pollution in merge (<<)",
"url": "https://github.com/advisories/GHSA-mh29-5h37-fv8m",
"severity": "moderate",
"cwe": [
"CWE-1321"
],
"cvss": {
"score": 5.3,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"
},
"range": "<3.14.2"
},
{
"source": 1109802,
"name": "js-yaml",
"dependency": "js-yaml",
"title": "js-yaml has prototype pollution in merge (<<)",
"url": "https://github.com/advisories/GHSA-mh29-5h37-fv8m",
"severity": "moderate",
"cwe": [
"CWE-1321"
],
"cvss": {
"score": 5.3,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"
},
"range": ">=4.0.0 <4.1.1"
}
],
"effects": [],
"range": "<3.14.2 || >=4.0.0 <4.1.1",
"nodes": [
"",
"",
"",
""
],
"fixAvailable": true
}
},
"metadata": {
"vulnerabilities": {
"info": 0,
"low": 0,
"moderate": 1,
"high": 0,
"critical": 0,
"total": 1
},
"dependencies": {
"prod": 1,
"dev": 484,
"optional": 0,
"peer": 1,
"peerOptional": 0,
"total": 484
}
}
}
}
--- end ---
{"added": 0, "removed": 0, "changed": 4, "audited": 485, "funding": 107, "audit": {"auditReportVersion": 2, "vulnerabilities": {"js-yaml": {"name": "js-yaml", "severity": "moderate", "isDirect": false, "via": [{"source": 1109801, "name": "js-yaml", "dependency": "js-yaml", "title": "js-yaml has prototype pollution in merge (<<)", "url": "https://github.com/advisories/GHSA-mh29-5h37-fv8m", "severity": "moderate", "cwe": ["CWE-1321"], "cvss": {"score": 5.3, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"}, "range": "<3.14.2"}, {"source": 1109802, "name": "js-yaml", "dependency": "js-yaml", "title": "js-yaml has prototype pollution in merge (<<)", "url": "https://github.com/advisories/GHSA-mh29-5h37-fv8m", "severity": "moderate", "cwe": ["CWE-1321"], "cvss": {"score": 5.3, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"}, "range": ">=4.0.0 <4.1.1"}], "effects": [], "range": "<3.14.2 || >=4.0.0 <4.1.1", "nodes": ["", "", "", ""], "fixAvailable": true}}, "metadata": {"vulnerabilities": {"info": 0, "low": 0, "moderate": 1, "high": 0, "critical": 0, "total": 1}, "dependencies": {"prod": 1, "dev": 484, "optional": 0, "peer": 1, "peerOptional": 0, "total": 484}}}}
$ /usr/bin/npm audit fix --only=dev
--- stderr ---
npm WARN invalid config only="dev" set in command line options
npm WARN invalid config Must be one of: null, prod, production
--- stdout ---
up to date, audited 485 packages in 2s
107 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
--- end ---
Verifying that tests still pass
$ /usr/bin/npm ci
--- stderr ---
npm WARN deprecated @humanwhocodes/config-array@0.13.0: Use @eslint/config-array instead
npm WARN deprecated @humanwhocodes/object-schema@2.0.3: Use @eslint/object-schema instead
npm WARN deprecated eslint@8.57.1: This version is no longer supported. Please see https://eslint.org/version-support for other options.
--- stdout ---
added 484 packages, and audited 485 packages in 6s
107 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
--- end ---
$ /usr/bin/npm test
--- stdout ---
> test
> grunt test
Running "eslint:all" (eslint) task
/src/repo/resources/controller/uw.controller.Deed.js
23:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
47:3 warning Prefer .then to .done no-jquery/no-done-fail
94:22 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
293:10 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
305:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/resources/controller/uw.controller.Details.js
23:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
104:3 warning ES2025 'Iterator.prototype.some' method is forbidden es-x/no-iterator-prototype-some
164:3 warning Prefer .then to .done no-jquery/no-done-fail
176:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/resources/controller/uw.controller.Step.js
25:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
76:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
82:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
95:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
105:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
/src/repo/resources/controller/uw.controller.Thanks.js
23:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
/src/repo/resources/controller/uw.controller.Tutorial.js
24:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
61:3 warning Prefer .then to .done no-jquery/no-done-fail
61:3 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/resources/controller/uw.controller.Upload.js
24:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
/src/repo/resources/deed/uw.deed.Abstract.js
48:14 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
83:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
160:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
173:1 warning The type 'uw.PatentDialog' is undefined jsdoc/no-undefined-types
190:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/resources/deed/uw.deed.OwnWork.js
478:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
683:1 warning The type 'uw.PatentDialog' is undefined jsdoc/no-undefined-types
/src/repo/resources/deed/uw.deed.ThirdParty.js
139:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
266:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/details/uw.CampaignDetailsWidget.js
6:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
/src/repo/resources/details/uw.CategoriesDetailsWidget.js
130:40 warning ES2025 'Iterator.prototype.some' method is forbidden es-x/no-iterator-prototype-some
213:34 warning Prefer .then to .fail no-jquery/no-done-fail
242:36 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/resources/details/uw.DateDetailsWidget.js
6:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
16:31 warning OO.ui.ButtonWidget has no label. Even icon-only buttons should set a label with invisibleLabel set to true mediawiki/no-unlabeled-buttonwidget
164:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
171:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
179:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
187:6 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
204:25 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/details/uw.LanguageDropdownWidget.js
14:53 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
/src/repo/resources/details/uw.LocationDetailsWidget.js
6:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
67:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/details/uw.MultipleLanguageInputWidget.js
7:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
134:10 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
181:10 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/details/uw.OtherDetailsWidget.js
7:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
/src/repo/resources/details/uw.SingleLanguageInputWidget.js
6:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
45:23 warning OO.ui.ButtonWidget has no label. Even icon-only buttons should set a label with invisibleLabel set to true mediawiki/no-unlabeled-buttonwidget
/src/repo/resources/details/uw.StatementWidget.js
70:1 warning The type 'dataValues.DataValue' is undefined jsdoc/no-undefined-types
105:1 warning The type 'datamodel.StatementList' is undefined jsdoc/no-undefined-types
108:22 warning ES2025 'Iterator.prototype.toArray' method is forbidden es-x/no-iterator-prototype-toarray
117:1 warning The type 'datamodel.StatementList' is undefined jsdoc/no-undefined-types
131:1 warning The type 'dataValues.DataValue' is undefined jsdoc/no-undefined-types
132:1 warning The type 'datamodel.Statement' is undefined jsdoc/no-undefined-types
145:1 warning The type 'datamodel.StatementList' is undefined jsdoc/no-undefined-types
/src/repo/resources/details/uw.TitleDetailsWidget.js
10:1 warning The type 'uw.DetailsWidget' is undefined jsdoc/no-undefined-types
112:1 warning Missing JSDoc @param "thorough" type jsdoc/require-param-type
113:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
161:30 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
207:10 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
215:27 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
217:21 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/details/uw.UlsWidget.js
30:3 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
99:1 warning The type 'uw.UlsWidget' is undefined jsdoc/no-undefined-types
/src/repo/resources/mw.DestinationChecker.js
14:3 warning Found more than one @return declaration jsdoc/require-returns
14:3 warning Found more than one @return declaration jsdoc/require-returns-check
35:3 warning Found more than one @return declaration jsdoc/require-returns
35:3 warning Found more than one @return declaration jsdoc/require-returns-check
80:3 warning Found more than one @return declaration jsdoc/require-returns
80:3 warning Found more than one @return declaration jsdoc/require-returns-check
/src/repo/resources/mw.FlickrChecker.js
4:1 warning Missing JSDoc @param "ui" type jsdoc/require-param-type
5:1 warning Missing JSDoc @param "selectButton" type jsdoc/require-param-type
212:1 warning The type 'getPhotos' is undefined jsdoc/no-undefined-types
226:17 warning 'data' is already declared in the upper scope on line 219 column 15 no-shadow
246:1 warning The type 'getCollection' is undefined jsdoc/no-undefined-types
304:45 warning 'data' is already declared in the upper scope on line 293 column 15 no-shadow
314:1 warning The type 'getPhotos' is undefined jsdoc/no-undefined-types
331:1 warning The type 'getPhotos' is undefined jsdoc/no-undefined-types
375:10 warning Prefer .then to .fail no-jquery/no-done-fail
468:5 warning Prefer .then to .done no-jquery/no-done-fail
519:10 warning Prefer .then to .fail no-jquery/no-done-fail
581:4 warning Prefer .then to .done no-jquery/no-done-fail
582:46 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
678:4 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
696:39 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
/src/repo/resources/mw.UploadWizard.js
4:1 warning Missing JSDoc @param "uw" type jsdoc/require-param-type
9:1 warning Missing JSDoc @param "config" type jsdoc/require-param-type
112:16 warning 'steps' is already declared in the upper scope on line 86 column 10 no-shadow
/src/repo/resources/mw.UploadWizardDeedChooser.js
32:42 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
40:17 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
44:21 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/mw.UploadWizardDetails.js
498:5 warning Prefer .then to .done no-jquery/no-done-fail
555:9 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
560:14 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
665:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
678:1 warning Missing JSDoc @param "thorough" type jsdoc/require-param-type
715:4 warning Prefer .then to .done no-jquery/no-done-fail
844:20 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
935:3 warning JSDoc @return declaration present but return expression not available in function jsdoc/require-returns-check
1017:9 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
1018:45 warning ES2019 'Symbol.prototype.description' property is forbidden es-x/no-symbol-prototype-description
/src/repo/resources/mw.UploadWizardLicenseInput.js
50:20 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
61:41 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
69:49 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
123:24 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
287:28 warning ES2025 'Iterator.prototype.reduce' method is forbidden es-x/no-iterator-prototype-reduce
/src/repo/resources/mw.UploadWizardUpload.js
24:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
224:16 warning ES2015 'Uint8Array' is forbidden es-x/no-typed-arrays
380:3 warning Prefer .then to .done no-jquery/no-done-fail
380:3 warning Prefer .then to .fail no-jquery/no-done-fail
445:3 warning Prefer .then to .done no-jquery/no-done-fail
445:3 warning Prefer .then to .fail no-jquery/no-done-fail
766:3 warning Prefer .then to .done no-jquery/no-done-fail
766:3 warning Prefer .then to .fail no-jquery/no-done-fail
774:6 warning Prefer .then to .done no-jquery/no-done-fail
777:7 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/mw.UploadWizardUploadInterface.js
110:3 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
215:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/transports/mw.FormDataTransport.js
166:5 warning Prefer .then to .done no-jquery/no-done-fail
167:6 warning Prefer .then to .done no-jquery/no-done-fail
167:6 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/resources/ui/steps/uw.ui.Deed.js
23:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
51:3 warning Prefer .then to .done no-jquery/no-done-fail
86:5 warning Prefer .then to .done no-jquery/no-done-fail
108:6 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/ui/steps/uw.ui.Details.js
23:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
108:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/ui/steps/uw.ui.Thanks.js
23:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
150:3 warning Prefer .then to .done no-jquery/no-done-fail
180:4 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
/src/repo/resources/ui/steps/uw.ui.Tutorial.js
50:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
126:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/ui/steps/uw.ui.Upload.js
23:1 warning The type 'uw.ui.Step' is undefined jsdoc/no-undefined-types
263:39 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
268:33 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
334:6 warning Prefer .then to .done no-jquery/no-done-fail
344:3 warning Prefer .then to .done no-jquery/no-done-fail
528:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/resources/ui/uw.ui.Step.js
111:3 warning Prefer .then to .done no-jquery/no-done-fail
120:3 warning Prefer .then to .done no-jquery/no-done-fail
166:12 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
180:7 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
197:5 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
/src/repo/resources/ui/uw.ui.Wizard.js
128:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
141:25 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
166:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
167:1 warning The type 'uw.controller.Step' is undefined jsdoc/no-undefined-types
/src/repo/resources/uw.LicenseGroup.js
148:48 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
181:56 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
423:18 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
435:14 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
443:5 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
451:5 warning All possible message keys should be documented. See https://w.wiki/4r9a for details mediawiki/msg-doc
496:1 warning The type 'uw.FieldLayout' is undefined jsdoc/no-undefined-types
619:3 warning Prefer .then to .done no-jquery/no-done-fail
619:3 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/resources/uw.ValidatableElement.js
36:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/resources/uw.ValidationMessageElement.js
10:1 warning The type 'uw.ValidatableElement' is undefined jsdoc/no-undefined-types
48:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
60:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
92:10 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
92:10 warning All possible CSS classes should be documented. See https://w.wiki/PS2 for details mediawiki/class-doc
/src/repo/resources/uw.ValidationStatus.js
11:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
20:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
29:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
38:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
47:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
56:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
85:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
92:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
99:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
100:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
115:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
116:1 warning The type 'uw.ValidationStatus' is undefined jsdoc/no-undefined-types
/src/repo/tests/qunit/controller/uw.controller.Details.test.js
127:3 warning Prefer .then to .done no-jquery/no-done-fail
/src/repo/tests/qunit/transports/mw.FormDataTransport.test.js
142:3 warning Prefer .then to .fail no-jquery/no-done-fail
175:10 warning Prefer .then to .done no-jquery/no-done-fail
194:10 warning Prefer .then to .done no-jquery/no-done-fail
214:3 warning Prefer .then to .fail no-jquery/no-done-fail
/src/repo/tests/qunit/uw.ConcurrentQueue.test.js
54:5 warning Prefer .then to .fail no-jquery/no-done-fail
✖ 180 problems (0 errors, 180 warnings)
Running "stylelint:all" (stylelint) task
>> Linted 18 files without errors
Running "banana:UploadWizard" (banana) task
>> 2 message directories checked.
Done.
--- end ---
{"1109801": {"source": 1109801, "name": "js-yaml", "dependency": "js-yaml", "title": "js-yaml has prototype pollution in merge (<<)", "url": "https://github.com/advisories/GHSA-mh29-5h37-fv8m", "severity": "moderate", "cwe": ["CWE-1321"], "cvss": {"score": 5.3, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"}, "range": "<3.14.2"}, "1109802": {"source": 1109802, "name": "js-yaml", "dependency": "js-yaml", "title": "js-yaml has prototype pollution in merge (<<)", "url": "https://github.com/advisories/GHSA-mh29-5h37-fv8m", "severity": "moderate", "cwe": ["CWE-1321"], "cvss": {"score": 5.3, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"}, "range": ">=4.0.0 <4.1.1"}}
Upgrading n:js-yaml from 3.14.1, 4.1.0 -> 3.14.2, 4.1.1
$ package-lock-lint package-lock.json
--- stdout ---
Checking package-lock.json
--- end ---
build: Updating npm dependencies
* eslint-config-wikimedia: 0.32.1 → 0.32.2
* js-yaml: 3.14.1, 4.1.0 → 3.14.2, 4.1.1
* https://github.com/advisories/GHSA-mh29-5h37-fv8m
$ git add .
--- stdout ---
--- end ---
$ git commit -F /tmp/tmppig_rm9r
--- stdout ---
[master f0c6c54] build: Updating npm dependencies
2 files changed, 32 insertions(+), 32 deletions(-)
--- end ---
$ git format-patch HEAD~1 --stdout
--- stdout ---
From f0c6c54c98623e394760b1476804c4fd6074c993 Mon Sep 17 00:00:00 2001
From: libraryupgrader <tools.libraryupgrader@tools.wmflabs.org>
Date: Tue, 18 Nov 2025 18:52:13 +0000
Subject: [PATCH] build: Updating npm dependencies
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* eslint-config-wikimedia: 0.32.1 → 0.32.2
* js-yaml: 3.14.1, 4.1.0 → 3.14.2, 4.1.1
* https://github.com/advisories/GHSA-mh29-5h37-fv8m
Change-Id: I1bad3f404b4f0eb1f17139277c6b665cc6d2fa08
---
package-lock.json | 62 +++++++++++++++++++++++------------------------
package.json | 2 +-
2 files changed, 32 insertions(+), 32 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 00d6548..5671c6f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,7 @@
"": {
"name": "UploadWizard",
"devDependencies": {
- "eslint-config-wikimedia": "0.32.1",
+ "eslint-config-wikimedia": "0.32.2",
"grunt": "1.6.1",
"grunt-banana-checker": "0.13.0",
"grunt-contrib-watch": "1.1.0",
@@ -294,9 +294,9 @@
"dev": true
},
"node_modules/@eslint/eslintrc/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
@@ -1339,9 +1339,9 @@
"dev": true
},
"node_modules/cosmiconfig/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
@@ -1783,9 +1783,9 @@
}
},
"node_modules/eslint-config-wikimedia": {
- "version": "0.32.1",
- "resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.1.tgz",
- "integrity": "sha512-gPvhyVFNlpKFOcJfoVTNlzg3A0b6qjhAbjjBIJ9xp5m+om0oqix5gkqIIEav5BaGxdDxYNmrY4ge3DAPP3u/lg==",
+ "version": "0.32.2",
+ "resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.2.tgz",
+ "integrity": "sha512-vAGz50AJPk23qQ701sL4tAgaF8FEAkP/E3kgojSTVrGgmDqjnRvq8z3EItDNI/EAkb5Ys15WPPFsoBH8YhTdSg==",
"dev": true,
"dependencies": {
"@stylistic/eslint-plugin": "^3.1.0",
@@ -2262,9 +2262,9 @@
"dev": true
},
"node_modules/eslint/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"dependencies": {
"argparse": "^2.0.1"
@@ -3389,9 +3389,9 @@
"dev": true
},
"node_modules/js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
"dependencies": {
"argparse": "^1.0.7",
@@ -6015,9 +6015,9 @@
"dev": true
},
"js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"requires": {
"argparse": "^2.0.1"
@@ -6732,9 +6732,9 @@
"dev": true
},
"js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"requires": {
"argparse": "^2.0.1"
@@ -7067,9 +7067,9 @@
"dev": true
},
"js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"requires": {
"argparse": "^2.0.1"
@@ -7096,9 +7096,9 @@
}
},
"eslint-config-wikimedia": {
- "version": "0.32.1",
- "resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.1.tgz",
- "integrity": "sha512-gPvhyVFNlpKFOcJfoVTNlzg3A0b6qjhAbjjBIJ9xp5m+om0oqix5gkqIIEav5BaGxdDxYNmrY4ge3DAPP3u/lg==",
+ "version": "0.32.2",
+ "resolved": "https://registry.npmjs.org/eslint-config-wikimedia/-/eslint-config-wikimedia-0.32.2.tgz",
+ "integrity": "sha512-vAGz50AJPk23qQ701sL4tAgaF8FEAkP/E3kgojSTVrGgmDqjnRvq8z3EItDNI/EAkb5Ys15WPPFsoBH8YhTdSg==",
"dev": true,
"requires": {
"@stylistic/eslint-plugin": "^3.1.0",
@@ -8237,9 +8237,9 @@
"dev": true
},
"js-yaml": {
- "version": "3.14.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
- "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"dev": true,
"requires": {
"argparse": "^1.0.7",
diff --git a/package.json b/package.json
index 6baa68b..2792669 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"test": "grunt test"
},
"devDependencies": {
- "eslint-config-wikimedia": "0.32.1",
+ "eslint-config-wikimedia": "0.32.2",
"grunt": "1.6.1",
"grunt-banana-checker": "0.13.0",
"grunt-contrib-watch": "1.1.0",
--
2.47.3
--- end ---