diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000000..ac636f1e14 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,68 @@ +const { browser: browserGlobals, node: nodeGlobals, jquery: jqueryGlobals } = (await import('globals')).default; + +// File patterns to ignore +const IGNORED_FILES = [ + 'phpBB/assets/javascript/cropper.js', + 'phpBB/assets/javascript/hermite.js', + 'phpBB/assets/javascript/jquery-cropper.js', + 'phpBB/ext/**/*.js', + 'phpBB/**/*.min.js', + 'phpBB/vendor/**/*.js', + 'phpBB/vendor-ext/**/*.js', + 'phpBB/phpbb/**/*.js', + 'phpBB/tests/**/*.js', +]; + +// ESLint rule configurations +const FORMATTING_RULES = { + 'quotes': ['error', 'single'], + 'comma-dangle': ['error', 'always-multiline'], + 'block-spacing': 'error', + 'array-bracket-spacing': ['error', 'always'], + 'object-curly-spacing': ['error', 'always'], + 'space-before-function-paren': ['error', 'never'], + 'space-in-parens': 'off', +}; + +const CODE_QUALITY_RULES = { + 'semi': ['error', 'always'], + 'eqeqeq': ['error', 'always'], + 'curly': ['error', 'multi-line'], + 'no-var': 'error', + 'prefer-const': 'error', + 'no-console': 'off', + 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], +}; + +const DISABLED_STYLE_RULES = { + 'multiline-comment-style': 'off', + 'computed-property-spacing': 'off', + 'capitalized-comments': 'off', + 'no-lonely-if': 'off', +}; + +const mainConfig = { + files: ['**/*.js', '**/*.js.twig'], + linterOptions: { + reportUnusedDisableDirectives: false, + }, + languageOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + globals: { + ...browserGlobals, + ...nodeGlobals, + ...jqueryGlobals, + }, + }, + rules: { + ...FORMATTING_RULES, + ...CODE_QUALITY_RULES, + ...DISABLED_STYLE_RULES, + }, +}; + +export default [ + { ignores: IGNORED_FILES }, + mainConfig, +]; diff --git a/package-lock.json b/package-lock.json index 102d21cd0a..cbc41696cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,8 @@ "devDependencies": { "autoprefixer": "^10.4.4", "cssnano": "^5.1.7", - "eslint": "^8.13.0", - "eslint-config-xo": "^0.40.0", + "eslint": "^9.28.0", + "globals": "^16.2.0", "gulp": "^5.0.0", "gulp-concat": "^2.6.1", "gulp-postcss": "^9.0.1", @@ -34,9 +34,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, "engines": { "node": ">=6.9.0" @@ -127,24 +127,135 @@ "node": ">=4" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", - "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.3.1", - "globals": "^13.9.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", + "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "dev": true, + "dependencies": { + "@eslint/core": "^0.14.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@gulpjs/messages": { @@ -168,25 +279,66 @@ "node": ">=10.13.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", - "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -232,6 +384,18 @@ "node": ">=10.13.0" } }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "node_modules/@types/minimist": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", @@ -264,9 +428,9 @@ } }, "node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -666,9 +830,9 @@ } }, "node_modules/browserslist": { - "version": "4.20.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz", - "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", "dev": true, "funding": [ { @@ -678,14 +842,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001317", - "electron-to-chromium": "^1.4.84", - "escalade": "^3.1.1", - "node-releases": "^2.0.2", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -734,18 +901,27 @@ "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, - "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -802,9 +978,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001332", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001332.tgz", - "integrity": "sha512-10T30NYOEQtN6C11YGg411yebhvpnC6Z102+B95eAsN0oB6KUs01ivE8u+G6FMIRtIrVlYXhL+LUwQ3/hXwDWw==", + "version": "1.0.30001720", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz", + "integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==", "dev": true, "funding": [ { @@ -814,6 +990,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, @@ -984,12 +1164,6 @@ "source-map": "^0.6.1" } }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true - }, "node_modules/connected-domain": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/connected-domain/-/connected-domain-1.0.0.tgz", @@ -1090,9 +1264,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -1314,29 +1488,11 @@ } }, "node_modules/deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1379,18 +1535,6 @@ "node": ">=8" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -1446,6 +1590,20 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/each-props": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/each-props/-/each-props-3.0.0.tgz", @@ -1476,9 +1634,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.111", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.111.tgz", - "integrity": "sha512-/s3+fwhKf1YK4k7btOImOzCQLpUjS6MaPf0ODTNuT4eTM1Bg4itBpLkydhOzJmpmH6Z9eXFyuuK5czsmzRzwtw==", + "version": "1.5.161", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz", + "integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==", "dev": true }, "node_modules/emoji-regex": { @@ -1525,14 +1683,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -1547,10 +1701,22 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, "engines": { "node": ">=6" @@ -1576,50 +1742,86 @@ } }, "node_modules/eslint": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.13.0.tgz", - "integrity": "sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==", + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", + "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.2.1", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.28.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -1627,91 +1829,137 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-config-xo": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/eslint-config-xo/-/eslint-config-xo-0.40.0.tgz", - "integrity": "sha512-msI1O0JGxeK2bbExg3U6EGaWKcjhOFzEjwzObywG/DC5GSNZTOyJT+b2l9MZGBeZsVdxfIGwdXTNeWXl8cN9iw==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "dependencies": { - "confusing-browser-globals": "1.0.11" + "flat-cache": "^4.0.0" }, "engines": { - "node": ">=12" + "node": ">=16.0.0" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" - }, - "peerDependencies": { - "eslint": ">=8.6.0" } }, - "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "node_modules/eslint/node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=16" } }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^2.0.0" + "p-locate": "^5.0.0" }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/espree": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", - "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, "dependencies": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.3.0" + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -1785,11 +2033,10 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, - "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -1810,7 +2057,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -1825,6 +2072,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/debug": { @@ -1909,16 +2160,16 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -1945,7 +2196,7 @@ "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "node_modules/fastest-levenshtein": { @@ -2091,9 +2342,9 @@ } }, "node_modules/flatted": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", - "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true }, "node_modules/for-in": { @@ -2191,12 +2442,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -2207,17 +2452,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, - "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -2226,6 +2475,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stdin": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", @@ -2325,15 +2587,12 @@ } }, "node_modules/globals": { - "version": "13.13.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", - "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz", + "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2358,13 +2617,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2508,18 +2766,6 @@ "node": ">=6" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2529,38 +2775,11 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -2680,9 +2899,9 @@ ] }, "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "engines": { "node": ">= 4" @@ -2803,12 +3022,15 @@ } }, "node_modules/is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2995,6 +3217,12 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -3056,6 +3284,15 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/known-css-properties": { "version": "0.24.0", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.24.0.tgz", @@ -3237,6 +3474,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mathml-tag-names": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", @@ -3455,9 +3701,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -3545,9 +3791,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.3.tgz", - "integrity": "sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true }, "node_modules/normalize-package-data": { @@ -3626,11 +3872,10 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3688,9 +3933,9 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "dependencies": { "deep-is": "^0.1.3", @@ -3698,7 +3943,7 @@ "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -3867,11 +4112,10 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", - "dev": true, - "license": "MIT" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true }, "node_modules/path-type": { "version": "4.0.0", @@ -3883,9 +4127,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, "node_modules/picomatch": { @@ -4694,18 +4938,6 @@ "node": ">=8" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -4749,13 +4981,20 @@ } }, "node_modules/resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, "dependencies": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4876,13 +5115,10 @@ "dev": true }, "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -4977,24 +5213,6 @@ "node": ">= 0.8.0" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -5024,16 +5242,69 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, - "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -5455,6 +5726,18 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/sver": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", @@ -5557,12 +5840,6 @@ "streamx": "^2.12.5" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, "node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -5638,18 +5915,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -5716,6 +5981,36 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -6036,6 +6331,18 @@ "engines": { "node": ">=10" } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } }, "dependencies": { @@ -6049,9 +6356,9 @@ } }, "@babel/helper-validator-identifier": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.5.tgz", - "integrity": "sha512-5lsetuxCLilmVGyiLEfoHBRX8UCFD+1m2x3Rj97WrW3V7H3u4RWRXA4evMjImCsin2J2YT0QaVDGf+z8ondbAg==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true }, "@babel/highlight": { @@ -6123,21 +6430,92 @@ } } }, + "@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.4.3" + } + }, + "@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true + }, + "@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "requires": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + } + }, + "@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true + }, + "@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.15" + } + }, "@eslint/eslintrc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", - "integrity": "sha512-bxvbYnBPN1Gibwyp6NrpnFzA3YtRL3BBAyEAFVIpNTm2Rn4Vy87GA5M4aSn3InRrlsbX5N0GW7XIx+U4SAEKdQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.3.1", - "globals": "^13.9.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true + } + } + }, + "@eslint/js": { + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", + "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "dev": true + }, + "@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true + }, + "@eslint/plugin-kit": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "dev": true, + "requires": { + "@eslint/core": "^0.14.0", + "levn": "^0.4.1" } }, "@gulpjs/messages": { @@ -6155,21 +6533,40 @@ "is-negated-glob": "^1.0.0" } }, - "@humanwhocodes/config-array": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", - "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true + }, + "@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "dependencies": { + "@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true + } } }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true }, "@nodelib/fs.scandir": { @@ -6204,6 +6601,18 @@ "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", "dev": true }, + "@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "@types/minimist": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", @@ -6233,9 +6642,9 @@ } }, "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true }, "acorn-jsx": { @@ -6523,16 +6932,15 @@ } }, "browserslist": { - "version": "4.20.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz", - "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001317", - "electron-to-chromium": "^1.4.84", - "escalade": "^3.1.1", - "node-releases": "^2.0.2", - "picocolors": "^1.0.0" + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" } }, "buffer": { @@ -6557,17 +6965,24 @@ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true }, - "call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "requires": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" } }, "callsites": { @@ -6606,9 +7021,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001332", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001332.tgz", - "integrity": "sha512-10T30NYOEQtN6C11YGg411yebhvpnC6Z102+B95eAsN0oB6KUs01ivE8u+G6FMIRtIrVlYXhL+LUwQ3/hXwDWw==", + "version": "1.0.30001720", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001720.tgz", + "integrity": "sha512-Ec/2yV2nNPwb4DnTANEV99ZWwm3ZWfdlfkQbWSDDt+PsXEVYwlhPH8tdMaPunYTKKmz7AnHi2oNEi1GcmKCD8g==", "dev": true }, "chalk": { @@ -6745,12 +7160,6 @@ "source-map": "^0.6.1" } }, - "confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true - }, "connected-domain": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/connected-domain/-/connected-domain-1.0.0.tgz", @@ -6822,9 +7231,9 @@ } }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -6978,22 +7387,11 @@ } }, "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -7021,15 +7419,6 @@ "path-type": "^4.0.0" } }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, "dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -7067,6 +7456,17 @@ "domhandler": "^4.2.0" } }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, "each-props": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/each-props/-/each-props-3.0.0.tgz", @@ -7093,9 +7493,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.4.111", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.111.tgz", - "integrity": "sha512-/s3+fwhKf1YK4k7btOImOzCQLpUjS6MaPf0ODTNuT4eTM1Bg4itBpLkydhOzJmpmH6Z9eXFyuuK5czsmzRzwtw==", + "version": "1.5.161", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.161.tgz", + "integrity": "sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==", "dev": true }, "emoji-regex": { @@ -7135,13 +7535,10 @@ } }, "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.4" - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true }, "es-errors": { "version": "1.3.0", @@ -7149,10 +7546,19 @@ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0" + } + }, "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true }, "escape-html": { @@ -7168,105 +7574,151 @@ "dev": true }, "eslint": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.13.0.tgz", - "integrity": "sha512-D+Xei61eInqauAyTJ6C0q6x9mx7kTUC1KZ0m0LSEexR0V+e94K12LmWX076ZIsldwfQ2RONdaJe0re0TRGQbRQ==", + "version": "9.28.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", + "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.2.1", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.28.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.1", - "esquery": "^1.4.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.6.0", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - } - }, - "eslint-config-xo": { - "version": "0.40.0", - "resolved": "https://registry.npmjs.org/eslint-config-xo/-/eslint-config-xo-0.40.0.tgz", - "integrity": "sha512-msI1O0JGxeK2bbExg3U6EGaWKcjhOFzEjwzObywG/DC5GSNZTOyJT+b2l9MZGBeZsVdxfIGwdXTNeWXl8cN9iw==", - "dev": true, - "requires": { - "confusing-browser-globals": "1.0.11" + "optionator": "^0.9.3" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true + }, + "file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "requires": { + "flat-cache": "^4.0.0" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "requires": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + } } }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + }, + "espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, "requires": { - "eslint-visitor-keys": "^2.0.0" + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" }, "dependencies": { "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true } } }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", - "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", - "dev": true, - "requires": { - "acorn": "^8.7.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.3.0" - } - }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -7318,9 +7770,9 @@ } }, "express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, "requires": { "accepts": "~1.3.8", @@ -7342,7 +7794,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -7420,16 +7872,16 @@ "dev": true }, "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "dependencies": { "glob-parent": { @@ -7452,7 +7904,7 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, "fastest-levenshtein": { @@ -7572,9 +8024,9 @@ } }, "flatted": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", - "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true }, "for-in": { @@ -7639,12 +8091,6 @@ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -7652,16 +8098,31 @@ "dev": true }, "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" } }, "get-stdin": { @@ -7741,13 +8202,10 @@ } }, "globals": { - "version": "13.13.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", - "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz", + "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==", + "dev": true }, "globjoin": { "version": "0.1.4", @@ -7765,13 +8223,10 @@ } }, "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.3" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true }, "graceful-fs": { "version": "4.2.11", @@ -7877,40 +8332,16 @@ "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", "dev": true }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0" - } - }, - "has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true - }, "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true }, "hasown": { @@ -7984,9 +8415,9 @@ "dev": true }, "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true }, "import-fresh": { @@ -8077,12 +8508,12 @@ } }, "is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "requires": { - "has": "^1.0.3" + "hasown": "^2.0.2" } }, "is-extendable": { @@ -8219,6 +8650,12 @@ "argparse": "^2.0.1" } }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -8276,6 +8713,15 @@ "safe-buffer": "^5.0.1" } }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, "known-css-properties": { "version": "0.24.0", "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.24.0.tgz", @@ -8427,6 +8873,12 @@ "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", "dev": true }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true + }, "mathml-tag-names": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", @@ -8584,9 +9036,9 @@ "dev": true }, "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true }, "natural-compare": { @@ -8649,9 +9101,9 @@ } }, "node-releases": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.3.tgz", - "integrity": "sha512-maHFz6OLqYxz+VQyCAtA3PTX4UP/53pa05fyDNc9CwjvJ0yEh6+xBwKsgCxMNhS8taUKBFYxfuiaD9U/55iFaw==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true }, "normalize-package-data": { @@ -8709,9 +9161,9 @@ } }, "object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true }, "object.defaults": { @@ -8754,9 +9206,9 @@ } }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "requires": { "deep-is": "^0.1.3", @@ -8764,7 +9216,7 @@ "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" } }, "p-limit": { @@ -8881,9 +9333,9 @@ "dev": true }, "path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true }, "path-type": { @@ -8893,9 +9345,9 @@ "dev": true }, "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, "picomatch": { @@ -9423,12 +9875,6 @@ "strip-indent": "^3.0.0" } }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -9460,13 +9906,14 @@ "dev": true }, "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "resolve-dir": { @@ -9547,13 +9994,10 @@ "dev": true }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true }, "semver-greatest-satisfied-range": { "version": "2.0.0", @@ -9628,20 +10072,6 @@ "send": "0.19.0" } }, - "set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - } - }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -9664,15 +10094,51 @@ "dev": true }, "side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, "requires": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" } }, "signal-exit": { @@ -9999,6 +10465,12 @@ "supports-color": "^7.0.0" } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, "sver": { "version": "1.8.4", "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", @@ -10089,12 +10561,6 @@ "streamx": "^2.12.5" } }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -10150,12 +10616,6 @@ "prelude-ls": "^1.2.1" } }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -10207,6 +10667,16 @@ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true }, + "update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "requires": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + } + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -10464,6 +10934,12 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/package.json b/package.json index 4543bc9797..3a3121230c 100644 --- a/package.json +++ b/package.json @@ -6,65 +6,6 @@ "directories": { "doc": "docs" }, - "eslintConfig": { - "extends": "xo", - "ignorePatterns": [ - "phpBB/adm/style/admin.js", - "phpBB/adm/style/ajax.js", - "phpBB/adm/style/permissions.js", - "phpBB/adm/style/tooltip.js", - "phpBB/assets/javascript/core.js", - "phpBB/assets/javascript/cropper.js", - "phpBB/assets/javascript/editor.js", - "phpBB/assets/javascript/hermite.js", - "phpBB/assets/javascript/installer.js", - "phpBB/assets/javascript/jquery-cropper.js", - "phpBB/assets/javascript/plupload.js", - "phpBB/ext/**/*.js", - "phpBB/styles/prosilver/template/ajax.js", - "phpBB/styles/prosilver/template/forum_fn.js", - "phpBB/**/*.min.js", - "phpBB/vendor/**/*.js", - "phpBB/vendor-ext/**/*.js", - "phpBB/phpbb/**/*.js", - "phpBB/tests/**/*.js" - ], - "rules": { - "quotes": [ - "error", - "single" - ], - "comma-dangle": [ - "error", - "always-multiline" - ], - "block-spacing": "error", - "array-bracket-spacing": [ - "error", - "always" - ], - "multiline-comment-style": "off", - "computed-property-spacing": "off", - "space-before-function-paren": [ - "error", - "never" - ], - "space-in-parens": "off", - "capitalized-comments": "off", - "object-curly-spacing": [ - "error", - "always" - ], - "no-lonely-if": "off", - "unicorn/prefer-module": "off" - }, - "env": { - "es6": true, - "browser": true, - "node": true, - "jquery": true - } - }, "browserslist": [ "> 1%", "not ie 11", @@ -101,8 +42,8 @@ "devDependencies": { "autoprefixer": "^10.4.4", "cssnano": "^5.1.7", - "eslint": "^8.13.0", - "eslint-config-xo": "^0.40.0", + "eslint": "^9.28.0", + "globals": "^16.2.0", "gulp": "^5.0.0", "gulp-concat": "^2.6.1", "gulp-postcss": "^9.0.1", diff --git a/phpBB/adm/style/admin.js b/phpBB/adm/style/admin.js index 2655fa6d90..a62e819716 100644 --- a/phpBB/adm/style/admin.js +++ b/phpBB/adm/style/admin.js @@ -7,19 +7,16 @@ /** * Parse document block */ -function parse_document(container) -{ - var test = document.createElement('div'), - oldBrowser = (typeof test.style.borderRadius == 'undefined'); - +function parseDocument(container) { + const test = document.createElement('div'); test.remove(); /** * Navigation */ container.find('#menu').each(function() { - var menu = $(this), - blocks = menu.children('.menu-block'); + const menu = $(this); + const blocks = menu.children('.menu-block'); if (!blocks.length) { return; @@ -27,10 +24,11 @@ function parse_document(container) // Set onclick event blocks.children('a.header').click(function() { - var parent = $(this).parent(); + const parent = $(this).parent(); if (!parent.hasClass('active')) { parent.siblings().removeClass('active'); } + parent.toggleClass('active'); }); @@ -47,23 +45,22 @@ function parse_document(container) * Responsive tables */ container.find('table').not('.not-responsive').each(function() { - var $this = $(this), - th = $this.find('thead > tr > th'), - columns = th.length, - headers = [], - totalHeaders = 0, - i, headersLength; + const $this = $(this); + const th = $this.find('thead > tr > th'); + const headers = []; + let totalHeaders = 0; + let i; // Find columns $this.find('colgroup:first').children().each(function(i) { - var column = $(this); + const column = $(this); $this.find('td:nth-child(' + (i + 1) + ')').addClass(column.prop('className')); }); // Styles table if ($this.hasClass('styles')) { $this.find('td:first-child[style]').each(function() { - var style = $(this).attr('style'); + const style = $(this).attr('style'); if (style.length) { $(this).parent('tr').attr('style', style.toLowerCase().replace('padding', 'margin')).addClass('responsive-style-row'); } @@ -71,21 +68,24 @@ function parse_document(container) } // Find each header - if (!$this.data('no-responsive-header')) - { + if (!$this.data('no-responsive-header')) { th.each(function(column) { - var cell = $(this), - colspan = parseInt(cell.attr('colspan')), - dfn = cell.attr('data-dfn'), - text = dfn ? dfn : $.trim(cell.text()); + const cell = $(this); + let colspan = parseInt(cell.attr('colspan'), 10); + const dfn = cell.attr('data-dfn'); + let text = dfn ? dfn : $.trim(cell.text()); + + if (text === ' ') { + text = ''; + } - if (text == ' ') text = ''; colspan = isNaN(colspan) || colspan < 1 ? 1 : colspan; - for (i=0; i $this.addClass('responsive'); @@ -104,19 +104,19 @@ function parse_document(container) } $this.find('tbody > tr').each(function() { - var row = $(this), - cells = row.children('td'), - column = 0; + const row = $(this); + const cells = row.children('td'); + let column = 0; - if (cells.length == 1) { + if (cells.length === 1) { row.addClass('big-column'); return; } cells.each(function() { - var cell = $(this), - colspan = parseInt(cell.attr('colspan')), - text = $.trim(cell.text()); + const cell = $(this); + let colspan = parseInt(cell.attr('colspan'), 10); + const text = $.trim(cell.text()); if (headersLength <= column) { return; @@ -124,10 +124,9 @@ function parse_document(container) if ((text.length && text !== '-') || cell.children().length) { if (headers[column].length) { - cell.prepend($("").css('display', 'none').text(headers[column])); + cell.prepend($('').css('display', 'none').text(headers[column])); } - } - else { + } else { cell.addClass('empty'); } @@ -144,9 +143,8 @@ function parse_document(container) * Hide empty responsive tables */ container.find('table.responsive > tbody').each(function() { - var items = $(this).children('tr'); - if (!items.length) - { + const items = $(this).children('tr'); + if (!items.length) { $(this).parent('table:first').addClass('responsive-hide'); } }); @@ -155,8 +153,8 @@ function parse_document(container) * Fieldsets with empty */ container.find('fieldset dt > span:last-child').each(function() { - var $this = $(this); - if ($this.html() == ' ') { + const $this = $(this); + if ($this.html() === ' ') { $this.addClass('responsive-hide'); } }); @@ -166,7 +164,7 @@ function parse_document(container) */ container.find('#sitename_short').each(function() { const $this = this; - const maxLength = $this.maxLength; + const { maxLength } = $this; $this.maxLength = maxLength * 2; $this.addEventListener('input', () => { const inputChars = Array.from($this.value); @@ -180,25 +178,25 @@ function parse_document(container) * Responsive tabs */ container.find('#tabs').not('[data-skip-responsive]').each(function() { - var $this = $(this), - $body = $('body'), - ul = $this.children(), - tabs = ul.children().not('[data-skip-responsive]'), - links = tabs.children('a'), - item = ul.append('').find('li.responsive-tab'), - menu = item.find('.dropdown-contents'), - maxHeight = 0, - lastWidth = false, - responsive = false; + const $this = $(this); + const $body = $('body'); + const ul = $this.children(); + const tabs = ul.children().not('[data-skip-responsive]'); + const links = tabs.children('a'); + const item = ul.append('').find('li.responsive-tab'); + const menu = item.find('.dropdown-contents'); + let maxHeight = 0; + let lastWidth = false; + let responsive = false; links.each(function() { - var link = $(this); + const link = $(this); maxHeight = Math.max(maxHeight, Math.max(link.outerHeight(true), link.parent().outerHeight(true))); - }) + }); function check() { - var width = $body.width(), - height = $this.height(); + const width = $body.width(); + let height = $this.height(); if (!arguments.length && (!responsive || width <= lastWidth) && height <= maxHeight) { return; @@ -214,6 +212,7 @@ function parse_document(container) if (item.hasClass('dropdown-visible')) { phpbb.toggleDropdown.call(item.find('a.responsive-tab-link').get(0)); } + return; } @@ -221,23 +220,29 @@ function parse_document(container) item.show(); menu.html(''); - var availableTabs = tabs.filter(':not(.activetab, .responsive-tab)'), - total = availableTabs.length, - i, tab; + const availableTabs = tabs.filter(':not(.activetab, .responsive-tab)'); + const total = availableTabs.length; + let i; + let tab; - for (i = total - 1; i >= 0; i --) { + for (i = total - 1; i >= 0; i--) { tab = availableTabs.eq(i); menu.prepend(tab.clone(true).removeClass('tab')); tab.hide(); if ($this.height() <= maxHeight) { - menu.find('a').click(function() { check(true); }); + menu.find('a').click(() => { + check(true); + }); return; } } - menu.find('a').click(function() { check(true); }); + + menu.find('a').click(() => { + check(true); + }); } - phpbb.registerDropdown(item.find('a.responsive-tab-link'), item.find('.dropdown'), {visibleClass: 'activetab', verticalDirection: 'down'}); + phpbb.registerDropdown(item.find('a.responsive-tab-link'), item.find('.dropdown'), { visibleClass: 'activetab', verticalDirection: 'down' }); check(true); $(window).resize(check); @@ -248,7 +253,7 @@ function parse_document(container) * Run onload functions */ (function($) { - $(document).ready(function() { + $(document).ready(() => { // Swap .nojs and .hasjs $('body.nojs').toggleClass('nojs hasjs'); @@ -257,13 +262,13 @@ function parse_document(container) $('#' + this.getAttribute('data-focus')).focus(); }); - parse_document($('body')); + parseDocument($('body')); $('#questionnaire-form').css('display', 'none'); - var $triggerConfiglist = $('#trigger-configlist'); + const $triggerConfiglist = $('#trigger-configlist'); - $triggerConfiglist.on('click', function () { - var $configlist = $('#configlist'); + $triggerConfiglist.on('click', function() { + const $configlist = $('#configlist'); $configlist.closest('.send-stats-data-row').toggleClass('send-stats-data-hidden'); $configlist.closest('.send-stats-row').find('.send-stats-data-row:first-child').toggleClass('send-stats-data-only-row'); $(this).find('i').toggleClass('fa-angle-down fa-angle-up'); @@ -272,8 +277,8 @@ function parse_document(container) $('#configlist').closest('.send-stats-data-row').addClass('send-stats-data-hidden'); // Do not underline actions icons on hover (could not be done via CSS) - $('.actions a:has(i.acp-icon)').mouseover(function () { - $(this).css("text-decoration", "none"); + $('.actions a:has(i.acp-icon)').mouseover(function() { + $(this).css('text-decoration', 'none'); }); // Live update BBCode font icon preview @@ -296,11 +301,11 @@ function parse_document(container) const pageIconFont = document.getElementById('bbcode_font_icon'); if (pageIconFont) { - pageIconFont.addEventListener('keyup', function () { + pageIconFont.addEventListener('keyup', function() { updateIconClass(this.nextElementSibling, this.value); }); - pageIconFont.addEventListener('blur', function () { + pageIconFont.addEventListener('blur', function() { updateIconClass(this.nextElementSibling, this.value); }); } diff --git a/phpBB/adm/style/ajax.js b/phpBB/adm/style/ajax.js index 2c364bcd86..1f0a2fa623 100644 --- a/phpBB/adm/style/ajax.js +++ b/phpBB/adm/style/ajax.js @@ -1,424 +1,421 @@ /* global phpbb, statsData */ -(function($) { // Avoid conflicts with other libraries +(function($) { // Avoid conflicts with other libraries + 'use strict'; -'use strict'; + phpbb.prepareSendStats = function() { + const $form = $('#acp_help_phpbb'); + const $dark = $('#darkenwrapper'); + let $loadingIndicator; + $form.on('submit', function(event) { + const $this = $(this); + const currentTime = Math.floor(new Date().getTime() / 1000); + const statsTime = parseInt($this.find('input[name=help_send_statistics_time]').val(), 10); -phpbb.prepareSendStats = function () { - var $form = $('#acp_help_phpbb'); - var $dark = $('#darkenwrapper'); - var $loadingIndicator; + event.preventDefault(); + $this.unbind('submit'); - $form.on('submit', function (event) { - var $this = $(this), - currentTime = Math.floor(new Date().getTime() / 1000), - statsTime = parseInt($this.find('input[name=help_send_statistics_time]').val(), 10); - - event.preventDefault(); - $this.unbind('submit'); - - // Skip ajax request if form is submitted too early or send stats - // checkbox is not checked - if (!$this.find('input[name=help_send_statistics]').is(':checked') || - statsTime > currentTime) { - $form.find('input[type=submit]').click(); - setTimeout(function () { + // Skip ajax request if form is submitted too early or send stats + // checkbox is not checked + if (!$this.find('input[name=help_send_statistics]').is(':checked') + || statsTime > currentTime) { $form.find('input[type=submit]').click(); - }, 300); + setTimeout(() => { + $form.find('input[type=submit]').click(); + }, 300); + return; + } + + /** + * Handler for AJAX errors + */ + function errorHandler(jqXHR, textStatus, errorThrown) { + if (typeof console !== 'undefined' && console.log) { + console.log('AJAX error. status: ' + textStatus + ', message: ' + errorThrown); + } + + phpbb.clearLoadingTimeout(); + let errorText = ''; + + if (typeof errorThrown === 'string' && errorThrown.length > 0) { + errorText = errorThrown; + } else { + errorText = $dark.attr('data-ajax-error-text-' + textStatus); + if (typeof errorText !== 'string' || !errorText.length) { + errorText = $dark.attr('data-ajax-error-text'); + } + } + + phpbb.alert($dark.attr('data-ajax-error-title'), errorText); + } + + /** + * This is a private function used to handle the callbacks, refreshes + * and alert. It calls the callback, refreshes the page if necessary, and + * displays an alert to the user and removes it after an amount of time. + * + * It cannot be called from outside this function, and is purely here to + * avoid repetition of code. + * + * @param {object} res The object sent back by the server. + */ + function returnHandler(res) { + phpbb.clearLoadingTimeout(); + + // If a confirmation is not required, display an alert and call the + // callbacks. + $dark.fadeOut(phpbb.alertTime); + + if ($loadingIndicator) { + $loadingIndicator.fadeOut(phpbb.alertTime); + } + + const $sendStatisticsSuccess = $('', { + type: 'hidden', + name: 'send_statistics_response', + value: JSON.stringify(res), + }); + $sendStatisticsSuccess.appendTo('p.submit-buttons'); + + // Finish actual form submission + $form.find('input[type=submit]').click(); + } + + $loadingIndicator = phpbb.loadingIndicator(); + + $.ajax({ + url: $this.attr('data-ajax-action').replace('&', '&'), + type: 'POST', + data: statsData, + success: returnHandler, + error: errorHandler, + cache: false, + }).always(() => { + if ($loadingIndicator && $loadingIndicator.is(':visible')) { + $loadingIndicator.fadeOut(phpbb.alertTime); + } + }); + }); + }; + + /** + * The following callbacks are for reording items. row_down + * is triggered when an item is moved down, and row_up is triggered when + * an item is moved up. It moves the row up or down, and deactivates / + * activates any up / down icons that require it (the ones at the top or bottom). + */ + phpbb.addAjaxCallback('row_down', function(res) { + if (typeof res.success === 'undefined' || !res.success) { return; } - /** - * Handler for AJAX errors - */ - function errorHandler(jqXHR, textStatus, errorThrown) { - if (typeof console !== 'undefined' && console.log) { - console.log('AJAX error. status: ' + textStatus + ', message: ' + errorThrown); - } - phpbb.clearLoadingTimeout(); - var errorText = ''; + const $firstTr = $(this).parents('tr'); + const $secondTr = $firstTr.next(); - if (typeof errorThrown === 'string' && errorThrown.length > 0) { - errorText = errorThrown; - } else { - errorText = $dark.attr('data-ajax-error-text-' + textStatus); - if (typeof errorText !== 'string' || !errorText.length) { - errorText = $dark.attr('data-ajax-error-text'); - } - } - phpbb.alert($dark.attr('data-ajax-error-title'), errorText); + $firstTr.insertAfter($secondTr); + }); + + phpbb.addAjaxCallback('row_up', function(res) { + if (typeof res.success === 'undefined' || !res.success) { + return; } - /** - * This is a private function used to handle the callbacks, refreshes - * and alert. It calls the callback, refreshes the page if necessary, and - * displays an alert to the user and removes it after an amount of time. - * - * It cannot be called from outside this function, and is purely here to - * avoid repetition of code. - * - * @param {object} res The object sent back by the server. - */ - function returnHandler(res) { - phpbb.clearLoadingTimeout(); + const $secondTr = $(this).parents('tr'); + const $firstTr = $secondTr.prev(); - // If a confirmation is not required, display an alert and call the - // callbacks. - $dark.fadeOut(phpbb.alertTime); + $secondTr.insertBefore($firstTr); + }); - if ($loadingIndicator) { - $loadingIndicator.fadeOut(phpbb.alertTime); - } + /** + * This callback replaces activate links with deactivate links and vice versa. + * It does this by replacing the text, and replacing all instances of "activate" + * in the href with "deactivate", and vice versa. + */ + phpbb.addAjaxCallback('activate_deactivate', function(res) { + const $this = $(this); + let newHref = $this.attr('href'); - var $sendStatisticsSuccess = $('', { - type: 'hidden', - name: 'send_statistics_response', - value: JSON.stringify(res) - }); - $sendStatisticsSuccess.appendTo('p.submit-buttons'); + $this.text(res.text); - // Finish actual form submission - $form.find('input[type=submit]').click(); + if (newHref.indexOf('deactivate') === -1) { + newHref = newHref.replace('activate', 'deactivate'); + } else { + newHref = newHref.replace('deactivate', 'activate'); } - $loadingIndicator = phpbb.loadingIndicator(); + $this.attr('href', newHref); + }); - $.ajax({ - url: $this.attr('data-ajax-action').replace('&', '&'), - type: 'POST', - data: statsData, - success: returnHandler, - error: errorHandler, - cache: false - }).always(function() { - if ($loadingIndicator && $loadingIndicator.is(':visible')) { - $loadingIndicator.fadeOut(phpbb.alertTime); + /** + * The removes the parent row of the link or form that triggered the callback, + * and is good for stuff like the removal of forums. + */ + phpbb.addAjaxCallback('row_delete', function(res) { + if (res.SUCCESS !== false) { + $(this).parents('tr').remove(); + } + }); + + /** + * This callback generates the VAPID keys for the web push notification service. + */ + phpbb.addAjaxCallback('generate_vapid_keys', () => { + /** + * Generate VAPID keypair with public and private key string + * + * @returns {Promise<{privateKey: string, publicKey: string}|null>} + */ + async function generateVAPIDKeys() { + try { + // Generate a new key pair using the Subtle Crypto API + const keyPair = await crypto.subtle.generateKey( + { + name: 'ECDH', + namedCurve: 'P-256', + }, + true, + [ 'deriveKey', 'deriveBits' ], + ); + + const privateKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey); + const privateKeyString = privateKeyJwk.d; + + const publicKeyBuffer = await crypto.subtle.exportKey('raw', keyPair.publicKey); + const publicKeyString = phpbb.base64UrlEncode(phpbb.rawKeyToBase64(publicKeyBuffer)); + + return { + privateKey: privateKeyString, + publicKey: publicKeyString, + }; + } catch (error) { + console.error('Error generating keys with SubtleCrypto:', error); + return null; } + } + + generateVAPIDKeys().then(keyPair => { + if (!keyPair) { + return; + } + + const publicKeyInput = document.querySelector('#webpush_vapid_public'); + const privateKeyInput = document.querySelector('#webpush_vapid_private'); + publicKeyInput.value = keyPair.publicKey; + privateKeyInput.value = keyPair.privateKey; }); }); -}; - -/** - * The following callbacks are for reording items. row_down - * is triggered when an item is moved down, and row_up is triggered when - * an item is moved up. It moves the row up or down, and deactivates / - * activates any up / down icons that require it (the ones at the top or bottom). - */ -phpbb.addAjaxCallback('row_down', function(res) { - if (typeof res.success === 'undefined' || !res.success) { - return; - } - - var $firstTr = $(this).parents('tr'), - $secondTr = $firstTr.next(); - - $firstTr.insertAfter($secondTr); -}); - -phpbb.addAjaxCallback('row_up', function(res) { - if (typeof res.success === 'undefined' || !res.success) { - return; - } - - var $secondTr = $(this).parents('tr'), - $firstTr = $secondTr.prev(); - - $secondTr.insertBefore($firstTr); -}); - -/** - * This callback replaces activate links with deactivate links and vice versa. - * It does this by replacing the text, and replacing all instances of "activate" - * in the href with "deactivate", and vice versa. - */ -phpbb.addAjaxCallback('activate_deactivate', function(res) { - var $this = $(this), - newHref = $this.attr('href'); - - $this.text(res.text); - - if (newHref.indexOf('deactivate') !== -1) { - newHref = newHref.replace('deactivate', 'activate'); - } else { - newHref = newHref.replace('activate', 'deactivate'); - } - - $this.attr('href', newHref); -}); - -/** - * The removes the parent row of the link or form that triggered the callback, - * and is good for stuff like the removal of forums. - */ -phpbb.addAjaxCallback('row_delete', function(res) { - if (res.SUCCESS !== false) { - $(this).parents('tr').remove(); - } -}); - -/** - * This callback generates the VAPID keys for the web push notification service. - */ -phpbb.addAjaxCallback('generate_vapid_keys', () => { /** - * Generate VAPID keypair with public and private key string - * - * @returns {Promise<{privateKey: string, publicKey: string}|null>} - */ - async function generateVAPIDKeys() { - try { - // Generate a new key pair using the Subtle Crypto API - const keyPair = await crypto.subtle.generateKey( - { - name: 'ECDH', - namedCurve: 'P-256', - }, - true, - ['deriveKey', 'deriveBits'] - ); + * Handler for submitting permissions form in chunks + * This call will submit permissions forms in chunks of 5 fieldsets. + */ + function submitPermissions() { + const $form = $('form#set-permissions'); + let fieldsetList = $form.find('fieldset[id^=perm]'); + const formDataSets = []; + let dataSetIndex = 0; + const $submitAllButton = $form.find('input[type=submit][name^=action]')[0]; + const $submitButton = $form.find('input[type=submit][data-clicked=true]')[0]; - const privateKeyJwk = await crypto.subtle.exportKey('jwk', keyPair.privateKey); - const privateKeyString = privateKeyJwk.d; + // Set proper start values for handling refresh of page + let permissionSubmitSize = 0; + let permissionRequestCount = 0; + const forumIds = []; + let permissionSubmitFailed = false; + let clearIndicator = true; - const publicKeyBuffer = await crypto.subtle.exportKey('raw', keyPair.publicKey); - const publicKeyString = phpbb.base64UrlEncode(phpbb.rawKeyToBase64(publicKeyBuffer)); - - return { - privateKey: privateKeyString, - publicKey: publicKeyString - }; - } catch (error) { - console.error('Error generating keys with SubtleCrypto:', error); - return null; - } - } - - generateVAPIDKeys().then(keyPair => { - if (!keyPair) { - return; - } - const publicKeyInput = document.querySelector('#webpush_vapid_public'); - const privateKeyInput = document.querySelector('#webpush_vapid_private'); - publicKeyInput.value = keyPair.publicKey; - privateKeyInput.value = keyPair.privateKey; - }) -}) - -/** - * Handler for submitting permissions form in chunks - * This call will submit permissions forms in chunks of 5 fieldsets. - */ -function submitPermissions() { - var $form = $('form#set-permissions'), - fieldsetList = $form.find('fieldset[id^=perm]'), - formDataSets = [], - dataSetIndex = 0, - $submitAllButton = $form.find('input[type=submit][name^=action]')[0], - $submitButton = $form.find('input[type=submit][data-clicked=true]')[0]; - - // Set proper start values for handling refresh of page - var permissionSubmitSize = 0, - permissionRequestCount = 0, - forumIds = [], - permissionSubmitFailed = false, - clearIndicator = true, - $loadingIndicator; - - if ($submitAllButton !== $submitButton) { - fieldsetList = $form.find('fieldset#' + $submitButton.closest('fieldset.permissions').id); - } - - $.each(fieldsetList, function (key, value) { - dataSetIndex = Math.floor(key / 5); - var $fieldset = $('fieldset#' + value.id); - if (key % 5 === 0) { - formDataSets[dataSetIndex] = $fieldset.find('select:visible, input:not([data-name])').serialize(); - } else { - formDataSets[dataSetIndex] += '&' + $fieldset.find('select:visible, input:not([data-name])').serialize(); + if ($submitAllButton !== $submitButton) { + fieldsetList = $form.find('fieldset#' + $submitButton.closest('fieldset.permissions').id); } - // Find proper role value - var roleInput = $fieldset.find('input[name^=role][data-name]'); - if (roleInput.val()) { - formDataSets[dataSetIndex] += '&' + roleInput.attr('name') + '=' + roleInput.val(); - } else { - formDataSets[dataSetIndex] += '&' + roleInput.attr('name') + '=' + - $fieldset.find('select[name="' + roleInput.attr('name') + '"]').val(); - } - }); + $.each(fieldsetList, (key, value) => { + dataSetIndex = Math.floor(key / 5); + const $fieldset = $('fieldset#' + value.id); + if (key % 5 === 0) { + formDataSets[dataSetIndex] = $fieldset.find('select:visible, input:not([data-name])').serialize(); + } else { + formDataSets[dataSetIndex] += '&' + $fieldset.find('select:visible, input:not([data-name])').serialize(); + } - permissionSubmitSize = formDataSets.length; + // Find proper role value + const roleInput = $fieldset.find('input[name^=role][data-name]'); + if (roleInput.val()) { + formDataSets[dataSetIndex] += '&' + roleInput.attr('name') + '=' + roleInput.val(); + } else { + formDataSets[dataSetIndex] += '&' + roleInput.attr('name') + '=' + + $fieldset.find('select[name="' + roleInput.attr('name') + '"]').val(); + } + }); - // Add each forum ID to forum ID list to preserve selected forums - $.each($form.find('input[type=hidden][name^=forum_id]'), function (key, value) { - if (value.name.match(/^forum_id\[([0-9]+)\]$/)) { - forumIds.push(value.value); - } - }); + permissionSubmitSize = formDataSets.length; - $loadingIndicator = phpbb.loadingIndicator(); + // Add each forum ID to forum ID list to preserve selected forums + $.each($form.find('input[type=hidden][name^=forum_id]'), (key, value) => { + if (value.name.match(/^forum_id\[([0-9]+)\]$/)) { + forumIds.push(value.value); + } + }); - /** - * Handler for submitted permissions form chunk - * - * @param {object} res Object returned by AJAX call - */ - function handlePermissionReturn(res) { - permissionRequestCount++; - var $dark = $('#darkenwrapper'); + const $loadingIndicator = phpbb.loadingIndicator(); - if (res.S_USER_WARNING) { - phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT); - permissionSubmitFailed = true; - } else if (!permissionSubmitFailed && res.S_USER_NOTICE) { - // Display success message at the end of submitting the form - if (permissionRequestCount >= permissionSubmitSize) { - clearIndicator = true; + /** + * Handler for submitted permissions form chunk + * + * @param {object} res Object returned by AJAX call + */ + function handlePermissionReturn(res) { + permissionRequestCount++; + const $dark = $('#darkenwrapper'); - var $alert = phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT); - var $alertBoxLink = $alert.find('p.alert_text > a'); + if (res.S_USER_WARNING) { + phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT); + permissionSubmitFailed = true; + } else if (!permissionSubmitFailed && res.S_USER_NOTICE) { + // Display success message at the end of submitting the form + if (permissionRequestCount >= permissionSubmitSize) { + clearIndicator = true; - // Create form to submit instead of normal "Back to previous page" link - if ($alertBoxLink) { - // Remove forum_id[] from URL - $alertBoxLink.attr('href', $alertBoxLink.attr('href').replace(/(&forum_id\[\]=[0-9]+)/g, '')); - const $previousPageForm = $('
').attr({ - action: $alertBoxLink.attr('href'), - method: 'post' - }); + const $alert = phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT); + const $alertBoxLink = $alert.find('p.alert_text > a'); - $.each(forumIds, function (key, value) { - $previousPageForm.append($('').attr({ - type: 'text', - name: 'forum_id[]', - value: value - })); - }); - - $alertBoxLink.on('click', function (e) { - $('body').append($previousPageForm); - e.preventDefault(); - $previousPageForm.submit(); - }); - } - - // Do not allow closing alert - $dark.off('click'); - $alert.find('.alert_close').hide(); - - if (typeof res.REFRESH_DATA !== 'undefined') { - setTimeout(function () { - // Create forum to submit using POST. This will prevent - // exceeding the maximum length of URLs - const $form = $('').attr({ - action: res.REFRESH_DATA.url.replace(/(&forum_id\[\]=[0-9]+)/g, ''), - method: 'post' + // Create form to submit instead of normal "Back to previous page" link + if ($alertBoxLink) { + // Remove forum_id[] from URL + $alertBoxLink.attr('href', $alertBoxLink.attr('href').replace(/(&forum_id\[\]=[0-9]+)/g, '')); + const $previousPageForm = $('').attr({ + action: $alertBoxLink.attr('href'), + method: 'post', }); - $.each(forumIds, function (key, value) { - $form.append($('').attr({ + $.each(forumIds, (key, value) => { + $previousPageForm.append($('').attr({ type: 'text', name: 'forum_id[]', - value: value + value, })); }); - $('body').append($form); - - // Hide the alert even if we refresh the page, in case the user - // presses the back button. - $dark.fadeOut(phpbb.alertTime, function () { - if (typeof $alert !== 'undefined') { - $alert.hide(); - } + $alertBoxLink.on('click', e => { + $('body').append($previousPageForm); + e.preventDefault(); + $previousPageForm.submit(); }); + } - // Submit form - $form.submit(); - }, res.REFRESH_DATA.time * 1000); // Server specifies time in seconds + // Do not allow closing alert + $dark.off('click'); + $alert.find('.alert_close').hide(); + + if (typeof res.REFRESH_DATA !== 'undefined') { + setTimeout(() => { + // Create forum to submit using POST. This will prevent + // exceeding the maximum length of URLs + const $form = $('').attr({ + action: res.REFRESH_DATA.url.replace(/(&forum_id\[\]=[0-9]+)/g, ''), + method: 'post', + }); + + $.each(forumIds, (key, value) => { + $form.append($('').attr({ + type: 'text', + name: 'forum_id[]', + value, + })); + }); + + $('body').append($form); + + // Hide the alert even if we refresh the page, in case the user + // presses the back button. + $dark.fadeOut(phpbb.alertTime, () => { + if (typeof $alert !== 'undefined') { + $alert.hide(); + } + }); + + // Submit form + $form.submit(); + }, res.REFRESH_DATA.time * 1000); // Server specifies time in seconds + } + } else { + // Still more forms to submit, so do not clear indicator + clearIndicator = false; + } + } + + if (clearIndicator) { + phpbb.clearLoadingTimeout(); + + if ($loadingIndicator) { + $loadingIndicator.fadeOut(phpbb.alertTime); } - } else { - // Still more forms to submit, so do not clear indicator - clearIndicator = false; } } - if (clearIndicator) { - phpbb.clearLoadingTimeout(); - - if ($loadingIndicator) { - $loadingIndicator.fadeOut(phpbb.alertTime); - } - } + // Create AJAX request for each form data set + $.each(formDataSets, (key, formData) => { + $.ajax({ + url: $form.action, + type: 'POST', + data: formData + '&' + $submitButton.name + '=' + encodeURIComponent($submitButton.value) + + '&creation_time=' + $form.find('input[type=hidden][name=creation_time]')[0].value + + '&form_token=' + $form.find('input[type=hidden][name=form_token]')[0].value + + '&' + $form.children('input[type=hidden]').serialize() + + '&' + $form.find('input[type=checkbox][name^=inherit]').serialize(), + success: handlePermissionReturn, + error: handlePermissionReturn, + }); + }); } - // Create AJAX request for each form data set - $.each(formDataSets, function (key, formData) { - $.ajax({ - url: $form.action, - type: 'POST', - data: formData + '&' + $submitButton.name + '=' + encodeURIComponent($submitButton.value) + - '&creation_time=' + $form.find('input[type=hidden][name=creation_time]')[0].value + - '&form_token=' + $form.find('input[type=hidden][name=form_token]')[0].value + - '&' + $form.children('input[type=hidden]').serialize() + - '&' + $form.find('input[type=checkbox][name^=inherit]').serialize(), - success: handlePermissionReturn, - error: handlePermissionReturn - }); + $('[data-ajax]').each(function() { + const $this = $(this); + const ajax = $this.attr('data-ajax'); + + if (ajax !== 'false') { + const fn = (ajax === 'true') ? null : ajax; + phpbb.ajaxify({ + selector: this, + refresh: $this.attr('data-refresh') !== undefined, + callback: fn, + }); + } }); -} -$('[data-ajax]').each(function() { - var $this = $(this), - ajax = $this.attr('data-ajax'); + /** + * Automatically resize textarea + */ + $(() => { + phpbb.resizeTextArea($('textarea:not(.no-auto-resize)'), { minHeight: 75 }); - if (ajax !== 'false') { - var fn = (ajax !== 'true') ? ajax : null; - phpbb.ajaxify({ - selector: this, - refresh: $this.attr('data-refresh') !== undefined, - callback: fn - }); - } -}); - -/** -* Automatically resize textarea -*/ -$(function() { - phpbb.resizeTextArea($('textarea:not(.no-auto-resize)'), {minHeight: 75}); - - var $setPermissionsForm = $('form#set-permissions'); - if ($setPermissionsForm.length) { - $setPermissionsForm.on('submit', function (e) { - submitPermissions(); - e.preventDefault(); - }); - $setPermissionsForm.find('input[type=submit]').click(function() { - $('input[type=submit]', $(this).parents($('form#set-permissions'))).removeAttr('data-clicked'); - $(this).attr('data-clicked', true); - }); - } - - // Handle date option changes - const dateoptionSelect = document.getElementById('dateoptions'); - if (dateoptionSelect) { - dateoptionSelect.addEventListener('change', function() { - const dateoptionInput = document.getElementById(this.getAttribute('data-dateoption')); - if (this.value === 'custom') { - dateoptionInput.value = this.getAttribute('data-dateoption-default'); - } else { - dateoptionInput.value = this.value; - } - }) - } - - if ($('#acp_help_phpbb')) { - phpbb.prepareSendStats(); - } -}); + const $setPermissionsForm = $('form#set-permissions'); + if ($setPermissionsForm.length) { + $setPermissionsForm.on('submit', e => { + submitPermissions(); + e.preventDefault(); + }); + $setPermissionsForm.find('input[type=submit]').click(function() { + $('input[type=submit]', $(this).parents($('form#set-permissions'))).removeAttr('data-clicked'); + $(this).attr('data-clicked', true); + }); + } + // Handle date option changes + const dateoptionSelect = document.getElementById('dateoptions'); + if (dateoptionSelect) { + dateoptionSelect.addEventListener('change', function() { + const dateoptionInput = document.getElementById(this.getAttribute('data-dateoption')); + if (this.value === 'custom') { + dateoptionInput.value = this.getAttribute('data-dateoption-default'); + } else { + dateoptionInput.value = this.value; + } + }); + } + if ($('#acp_help_phpbb')) { + phpbb.prepareSendStats(); + } + }); })(jQuery); // Avoid conflicts with other libraries diff --git a/phpBB/adm/style/permissions.js b/phpBB/adm/style/permissions.js index 04b3fc5c5f..a1371ff7d2 100644 --- a/phpBB/adm/style/permissions.js +++ b/phpBB/adm/style/permissions.js @@ -1,24 +1,26 @@ /* global phpbb */ +/* eslint camelcase: 0 */ +/* eslint no-undef: 0 */ +/* eslint no-unused-vars: 0 */ /** * Hide and show all checkboxes * status = true (show boxes), false (hide boxes) */ function display_checkboxes(status) { - var form = document.getElementById('set-permissions'); - var cb = document.getElementsByTagName('input'); - var display; + const form = document.getElementById('set-permissions'); + const cb = document.getElementsByTagName('input'); + let display; - //show if (status) { + // show display = 'inline'; - } - //hide - else { + } else { + // hide display = 'none'; } - for (var i = 0; i < cb.length; i++ ) { + for (let i = 0; i < cb.length; i++ ) { if (cb[i].className === 'permissions-checkbox') { cb[i].style.display = display; } @@ -31,10 +33,10 @@ function display_checkboxes(status) { * value = 0 (hidden) till 10 (fully visible) */ function set_opacity(e, value) { - e.style.opacity = value/10; + e.style.opacity = value / 10; - //IE opacity currently turned off, because of its astronomical stupidity - //e.style.filter = 'alpha(opacity=' + value*10 + ')'; + // IE opacity currently turned off, because of its astronomical stupidity + // e.style.filter = 'alpha(opacity=' + value*10 + ')'; } /** @@ -42,8 +44,8 @@ function set_opacity(e, value) { * block_id = id of the element that needs to be toggled */ function toggle_opacity(block_id) { - var cb = document.getElementById('checkbox' + block_id); - var fs = document.getElementById('perm' + block_id); + const cb = document.getElementById('checkbox' + block_id); + const fs = document.getElementById('perm' + block_id); if (cb.checked) { set_opacity(fs, 5); @@ -58,25 +60,25 @@ function toggle_opacity(block_id) { * except_id = id of the element not to hide */ function reset_opacity(status, except_id) { - var perm = document.getElementById('set-permissions'); - var fs = perm.getElementsByTagName('fieldset'); - var opacity = 5; + const perm = document.getElementById('set-permissions'); + const fs = perm.getElementsByTagName('fieldset'); + let opacity = 5; if (status) { opacity = 10; } - for (var i = 0; i < fs.length; i++ ) { + for (let i = 0; i < fs.length; i++ ) { if (fs[i].className !== 'quick') { set_opacity(fs[i], opacity); } } - if (typeof(except_id) !== 'undefined') { + if (typeof (except_id) !== 'undefined') { set_opacity(document.getElementById('perm' + except_id), 10); } - //reset checkboxes too + // reset checkboxes too marklist('set-permissions', 'inherit', !status); } @@ -86,13 +88,14 @@ function reset_opacity(status, except_id) { * rb = array of radiobuttons */ function get_radio_status(index, rb) { - for (var i = index; i < rb.length; i = i + 3 ) { + for (let i = index; i < rb.length; i += 3 ) { if (rb[i].checked !== true) { if (i > index) { - //at least one is true, but not all (custom) + // at least one is true, but not all (custom) return 2; } - //first one is not true + + // first one is not true return 0; } } @@ -108,18 +111,18 @@ function get_radio_status(index, rb) { * quick = If no calculation needed, this contains the colour */ function set_colours(id, init, quick) { - var table = document.getElementById('table' + id); - var tab = document.getElementById('tab' + id); + const table = document.getElementById('table' + id); + const tab = document.getElementById('tab' + id); - if (typeof(quick) !== 'undefined') { + if (typeof (quick) !== 'undefined') { tab.className = 'permissions-preset-' + quick + ' activetab'; return; } - var rb = table.getElementsByTagName('input'); - var colour = 'custom'; + const rb = table.getElementsByTagName('input'); + let colour = 'custom'; - var status = get_radio_status(0, rb); + let status = get_radio_status(0, rb); if (status === 1) { colour = 'yes'; @@ -151,17 +154,17 @@ function set_colours(id, init, quick) { * block_id = block that is opened */ function init_colours(block_id) { - var block = document.getElementById('advanced' + block_id); - var panels = block.getElementsByTagName('div'); - var tab = document.getElementById('tab' + id); + const block = document.getElementById('advanced' + block_id); + const panels = block.getElementsByTagName('div'); + const tab = document.getElementById('tab' + id); - for (var i = 0; i < panels.length; i++) { + for (let i = 0; i < panels.length; i++) { if (panels[i].className === 'permissions-panel') { set_colours(panels[i].id.replace(/options/, ''), true); } } - tab.className = tab.className + ' activetab'; + tab.className += ' activetab'; } /** @@ -170,13 +173,14 @@ function init_colours(block_id) { * adv = we are opening advanced permissions * view = called from view permissions */ +// eslint-disable-next-line max-params function swap_options(pmask, fmask, cat, adv, view) { id = pmask + fmask + cat; active_option = active_pmask + active_fmask + active_cat; - var old_tab = document.getElementById('tab' + active_option); - var new_tab = document.getElementById('tab' + id); - var adv_block = document.getElementById('advanced' + pmask + fmask); + const old_tab = document.getElementById('tab' + active_option); + const new_tab = document.getElementById('tab' + id); + const adv_block = document.getElementById('advanced' + pmask + fmask); if (adv_block.style.display === 'block' && adv === true) { phpbb.toggleDisplay('advanced' + pmask + fmask, -1); @@ -196,14 +200,14 @@ function swap_options(pmask, fmask, cat, adv, view) { display_checkboxes(true); reset_opacity(1); } else if (adv) { - //Checkbox might have been clicked, but we need full visibility + // Checkbox might have been clicked, but we need full visibility display_checkboxes(true); reset_opacity(1); } // set active tab - old_tab.className = old_tab.className.replace(/\ activetab/g, ''); - new_tab.className = new_tab.className + ' activetab'; + old_tab.className = old_tab.className.replace(/ activetab/g, ''); + new_tab.className += ' activetab'; if (id === active_option && adv !== true) { return; @@ -211,7 +215,7 @@ function swap_options(pmask, fmask, cat, adv, view) { phpbb.toggleDisplay('options' + active_option, -1); - //hiding and showing the checkbox + // hiding and showing the checkbox if (document.getElementById('checkbox' + active_pmask + active_fmask)) { phpbb.toggleDisplay('checkbox' + pmask + fmask, -1); @@ -227,6 +231,7 @@ function swap_options(pmask, fmask, cat, adv, view) { if (!view) { phpbb.toggleDisplay('advanced' + pmask + fmask, 1); } + phpbb.toggleDisplay('options' + id, 1); active_pmask = pmask; @@ -239,32 +244,32 @@ function swap_options(pmask, fmask, cat, adv, view) { * id = table ID container, s = status ['y'/'u'/'n'] */ function mark_options(id, s) { - var t = document.getElementById(id); + const t = document.getElementById(id); if (!t) { return; } - var rb = t.getElementsByTagName('input'); + const rb = t.getElementsByTagName('input'); - for (var r = 0; r < rb.length; r++) { - if (rb[r].id.substr(rb[r].id.length-1) === s) { + for (let r = 0; r < rb.length; r++) { + if (rb[r].id.substr(rb[r].id.length - 1) === s) { rb[r].checked = true; } } } function mark_one_option(id, field_name, s) { - var t = document.getElementById(id); + const t = document.getElementById(id); if (!t) { return; } - var rb = t.getElementsByTagName('input'); + const rb = t.getElementsByTagName('input'); - for (var r = 0; r < rb.length; r++) { - if (rb[r].id.substr(rb[r].id.length-field_name.length-3, field_name.length) === field_name && rb[r].id.substr(rb[r].id.length-1) === s) { + for (let r = 0; r < rb.length; r++) { + if (rb[r].id.substr(rb[r].id.length - field_name.length - 3, field_name.length) === field_name && rb[r].id.substr(rb[r].id.length - 1) === s) { rb[r].checked = true; } } @@ -280,21 +285,21 @@ function mark_one_option(id, field_name, s) { * @returns {void} */ function reset_role(id) { - var t = document.getElementById(id); + const t = document.getElementById(id); if (!t) { return; } // Before resetting the role dropdown, try and match any permission role - var parent = t.parentNode, - roleId = match_role_settings(id.replace('role', 'perm')), - text = no_role_assigned, - index = 0; + const parent = t.parentNode; + const roleId = match_role_settings(id.replace('role', 'perm')); + let text = no_role_assigned; + let index = 0; // If a role permissions was matched, grab that option's value and index if (roleId) { - for (var i = 0; i < t.options.length; i++) { + for (let i = 0; i < t.options.length; i++) { if (parseInt(t.options[i].value, 10) === roleId) { text = t.options[i].text; index = i; @@ -316,7 +321,7 @@ function reset_role(id) { * Load role and set options accordingly */ function set_role_settings(role_id, target_id) { - var settings = role_options[role_id]; + const settings = role_options[role_id]; if (!settings) { return; @@ -325,8 +330,10 @@ function set_role_settings(role_id, target_id) { // Mark all options to no (unset) first... mark_options(target_id, 'u'); - for (var r in settings) { - mark_one_option(target_id, r, (settings[r] === 1) ? 'y' : 'n'); + for (const r in settings) { + if (Object.prototype.hasOwnProperty.call(settings, r)) { + mark_one_option(target_id, r, (settings[r] === 1) ? 'y' : 'n'); + } } } @@ -337,13 +344,13 @@ function set_role_settings(role_id, target_id) { * @return {number} The permission role identifier */ function match_role_settings(id) { - var fieldset = document.getElementById(id), - radios = fieldset.getElementsByTagName('input'), - set = {}; + const fieldset = document.getElementById(id); + const radios = fieldset.getElementsByTagName('input'); + let set = {}; // Iterate over all the radio buttons - for (var i = 0; i < radios.length; i++) { - var matches = radios[i].id.match(/setting\[\d+]\[\d+]\[([a-z_]+)]/); + for (let i = 0; i < radios.length; i++) { + const matches = radios[i].id.match(/setting\[\d+]\[\d+]\[([a-z_]+)]/); // Make sure the name attribute matches, the radio is checked and it is not the "No" (-1) value. if (matches !== null && radios[i].checked && radios[i].value !== '-1') { @@ -355,8 +362,7 @@ function match_role_settings(id) { set = sort_and_stringify(set); // Iterate over the available role options and return the first match - for (var r in role_options) - { + for (const r in role_options) { if (sort_and_stringify(role_options[r]) === set) { return parseInt(r, 10); } @@ -372,7 +378,7 @@ function match_role_settings(id) { * @return {string} The sorted object as a string */ function sort_and_stringify(obj) { - return JSON.stringify(Object.keys(obj).sort().reduce(function (result, key) { + return JSON.stringify(Object.keys(obj).sort().reduce((result, key) => { result[key] = obj[key]; return result; }, {})); diff --git a/phpBB/adm/style/tooltip.js b/phpBB/adm/style/tooltip.js index 562bc99dc3..51aaaafd8c 100644 --- a/phpBB/adm/style/tooltip.js +++ b/phpBB/adm/style/tooltip.js @@ -13,213 +13,209 @@ phpBB Development Team: */ (function($) { // Avoid conflicts with other libraries + 'use strict'; -'use strict'; + const tooltips = []; -var tooltips = []; + /** + * Enable tooltip replacements for selects + * @param {string} id ID tag of select + * @param {string} headline Text that should appear on top of tooltip + * @param {string} [subId] Sub ID that should only be using tooltips (optional) + */ + phpbb.enableTooltipsSelect = function(id, headline, subId) { + let $links; -/** - * Enable tooltip replacements for selects - * @param {string} id ID tag of select - * @param {string} headline Text that should appear on top of tooltip - * @param {string} [subId] Sub ID that should only be using tooltips (optional) -*/ -phpbb.enableTooltipsSelect = function (id, headline, subId) { - var $links, hold; + const hold = $('', { + id: '_tooltip_container', + css: { + position: 'absolute', + }, + }); - hold = $('', { - id: '_tooltip_container', - css: { - position: 'absolute' + $('body').append(hold); + + if (id) { + $links = $('.roles-options li', '#' + id); + } else { + $links = $('.roles-options li'); } - }); - $('body').append(hold); + $links.each(function() { + const $this = $(this); - if (!id) { - $links = $('.roles-options li'); - } else { - $links = $('.roles-options li', '#' + id); - } - - $links.each(function () { - var $this = $(this); - - if (subId) { - if ($this.parent().attr('id').substr(0, subId.length) === subId) { + if (subId) { + if ($this.parent().attr('id').substr(0, subId.length) === subId) { + phpbb.prepareTooltips($this, headline); + } + } else { phpbb.prepareTooltips($this, headline); } + }); + }; + + /** + * Prepare elements to replace + * + * @param {jQuery} $element Element to prepare for tooltips + * @param {string} headText Text heading to display + */ + phpbb.prepareTooltips = function($element, headText) { + const text = $element.attr('data-title'); + + if (text === null || text.length === 0) { + return; + } + + const $title = $('', { + class: 'top', + css: { + display: 'block', + }, + }) + .append(document.createTextNode(headText)); + + const $desc = $('', { + class: 'bottom', + html: text, + css: { + display: 'block', + }, + }); + + const $tooltip = $('', { + class: 'tooltip', + css: { + display: 'block', + }, + }) + .append($title) + .append($desc); + + tooltips[$element.attr('data-id')] = $tooltip; + $element.on('mouseover', phpbb.showTooltip); + $element.on('mouseout', phpbb.hideTooltip); + }; + + /** + * Show tooltip + * + * @param {object} $element Element passed by .on() + */ + phpbb.showTooltip = function($element) { + const $this = $($element.target); + $('#_tooltip_container').append(tooltips[$this.attr('data-id')]); + phpbb.positionTooltip($this); + }; + + /** + * Hide tooltip + */ + phpbb.hideTooltip = function() { + const d = document.getElementById('_tooltip_container'); + if (d.childNodes.length > 0) { + d.removeChild(d.firstChild); + } + }; + + /** + * Correct positioning of tooltip container + * + * @param {jQuery} $element Tooltip element that should be positioned + */ + phpbb.positionTooltip = function($element) { + $element = $element.parent(); + const offset = $element.offset(); + + if ($('body').hasClass('rtl')) { + $('#_tooltip_container').css({ + top: offset.top + 30, + left: offset.left + 255, + }); } else { - phpbb.prepareTooltips($this, headline); - } - }); -}; - -/** - * Prepare elements to replace - * - * @param {jQuery} $element Element to prepare for tooltips - * @param {string} headText Text heading to display -*/ -phpbb.prepareTooltips = function ($element, headText) { - var $tooltip, text, $desc, $title; - - text = $element.attr('data-title'); - - if (text === null || text.length === 0) { - return; - } - - $title = $('', { - class: 'top', - css: { - display: 'block' - } - }) - .append(document.createTextNode(headText)); - - $desc = $('', { - class: 'bottom', - html: text, - css: { - display: 'block' - } - }); - - $tooltip = $('', { - class: 'tooltip', - css: { - display: 'block' - } - }) - .append($title) - .append($desc); - - tooltips[$element.attr('data-id')] = $tooltip; - $element.on('mouseover', phpbb.showTooltip); - $element.on('mouseout', phpbb.hideTooltip); -}; - -/** - * Show tooltip - * - * @param {object} $element Element passed by .on() -*/ -phpbb.showTooltip = function ($element) { - var $this = $($element.target); - $('#_tooltip_container').append(tooltips[$this.attr('data-id')]); - phpbb.positionTooltip($this); -}; - -/** - * Hide tooltip -*/ -phpbb.hideTooltip = function () { - var d = document.getElementById('_tooltip_container'); - if (d.childNodes.length > 0) { - d.removeChild(d.firstChild); - } -}; - -/** - * Correct positioning of tooltip container - * - * @param {jQuery} $element Tooltip element that should be positioned -*/ -phpbb.positionTooltip = function ($element) { - var offset; - - $element = $element.parent(); - offset = $element.offset(); - - if ($('body').hasClass('rtl')) { - $('#_tooltip_container').css({ - top: offset.top + 30, - left: offset.left + 255 - }); - } else { - $('#_tooltip_container').css({ - top: offset.top + 30, - left: offset.left - 205 - }); - } -}; - -/** - * Prepare roles drop down select - */ -phpbb.prepareRolesDropdown = function () { - var $options = $('.roles-options li'); - - // Display span and hide select - $('.roles-options > span').css('display', 'block'); - $('.roles-options > select').hide(); - $('.roles-options > input[type=hidden]').each(function () { - var $this = $(this); - - if ($this.attr('data-name') && !$this.attr('name')) { - $this.attr('name', $this.attr('data-name')); - } - }); - - // Prepare highlighting of select options and settings update - $options.each(function () { - var $this = $(this); - var $rolesOptions = $this.closest('.roles-options'); - var $span = $rolesOptions.children('span'); - - // Correctly show selected option - if (typeof $this.attr('data-selected') !== 'undefined') { - $rolesOptions - .children('span') - .text($this.text()) - .attr('data-default', $this.text()) - .attr('data-default-val', $this.attr('data-id')); - - // Save default text of drop down if there is no default set yet - if (typeof $span.attr('data-default') === 'undefined') { - $span.attr('data-default', $span.text()); - } - - // Prepare resetting drop down on form reset - $this.closest('form').on('reset', function () { - $span.text($span.attr('data-default')); - $rolesOptions.children('input[type=hidden]') - .val($span.attr('data-default-val')); + $('#_tooltip_container').css({ + top: offset.top + 30, + left: offset.left - 205, }); } + }; - $this.on('mouseover', function () { - var $this = $(this); - $options.removeClass('roles-highlight'); - $this.addClass('roles-highlight'); - }).on('click', function () { - var $this = $(this); - var $rolesOptions = $this.closest('.roles-options'); + /** + * Prepare roles drop down select + */ + phpbb.prepareRolesDropdown = function() { + const $options = $('.roles-options li'); - // Update settings - set_role_settings($this.attr('data-id'), $this.attr('data-target-id')); - init_colours($this.attr('data-target-id').replace('advanced', '')); + // Display span and hide select + $('.roles-options > span').css('display', 'block'); + $('.roles-options > select').hide(); + $('.roles-options > input[type=hidden]').each(function() { + const $this = $(this); - // Set selected setting - $rolesOptions.children('span') - .text($this.text()); - $rolesOptions.children('input[type=hidden]') - .val($this.attr('data-id')); - - // Trigger hiding of selection options - $('body').trigger('click'); + if ($this.attr('data-name') && !$this.attr('name')) { + $this.attr('name', $this.attr('data-name')); + } }); + + // Prepare highlighting of select options and settings update + $options.each(function() { + const $this = $(this); + const $rolesOptions = $this.closest('.roles-options'); + const $span = $rolesOptions.children('span'); + + // Correctly show selected option + if (typeof $this.attr('data-selected') !== 'undefined') { + $rolesOptions + .children('span') + .text($this.text()) + .attr('data-default', $this.text()) + .attr('data-default-val', $this.attr('data-id')); + + // Save default text of drop down if there is no default set yet + if (typeof $span.attr('data-default') === 'undefined') { + $span.attr('data-default', $span.text()); + } + + // Prepare resetting drop down on form reset + $this.closest('form').on('reset', () => { + $span.text($span.attr('data-default')); + $rolesOptions.children('input[type=hidden]') + .val($span.attr('data-default-val')); + }); + } + + $this.on('mouseover', function() { + const $this = $(this); + $options.removeClass('roles-highlight'); + $this.addClass('roles-highlight'); + }).on('click', function() { + const $this = $(this); + const $rolesOptions = $this.closest('.roles-options'); + + // Update settings + // eslint-disable-next-line no-undef + set_role_settings($this.attr('data-id'), $this.attr('data-target-id')); + // eslint-disable-next-line no-undef + init_colours($this.attr('data-target-id').replace('advanced', '')); + + // Set selected setting + $rolesOptions.children('span') + .text($this.text()); + $rolesOptions.children('input[type=hidden]') + .val($this.attr('data-id')); + + // Trigger hiding of selection options + $('body').trigger('click'); + }); + }); + }; + + // Run onload functions for RolesDropdown and tooltips + $(() => { + // Enable tooltips + phpbb.enableTooltipsSelect('set-permissions', $('#set-permissions').attr('data-role-description'), 'role'); + + // Prepare dropdown + phpbb.prepareRolesDropdown(); }); -}; - -// Run onload functions for RolesDropdown and tooltips -$(function() { - // Enable tooltips - phpbb.enableTooltipsSelect('set-permissions', $('#set-permissions').attr('data-role-description'), 'role'); - - // Prepare dropdown - phpbb.prepareRolesDropdown(); -}); - })(jQuery); // Avoid conflicts with other libraries diff --git a/phpBB/assets/javascript/core.js b/phpBB/assets/javascript/core.js index c0b225214d..95e66254c8 100644 --- a/phpBB/assets/javascript/core.js +++ b/phpBB/assets/javascript/core.js @@ -1,757 +1,767 @@ /* global bbfontstyle */ -var phpbb = {}; +const phpbb = {}; phpbb.alertTime = 100; -(function($) { // Avoid conflicts with other libraries +(function($) { // Avoid conflicts with other libraries + 'use strict'; -'use strict'; + // define a couple constants for keydown functions. + const keymap = { + TAB: 9, + ENTER: 13, + ESC: 27, + ARROW_UP: 38, + ARROW_DOWN: 40, + }; -// define a couple constants for keydown functions. -var keymap = { - TAB: 9, - ENTER: 13, - ESC: 27, - ARROW_UP: 38, - ARROW_DOWN: 40 -}; + const $dark = $('#darkenwrapper'); + let $loadingIndicator; + let phpbbAlertTimer = null; -var $dark = $('#darkenwrapper'); -var $loadingIndicator; -var phpbbAlertTimer = null; + phpbb.isTouch = (window && typeof window.ontouchstart !== 'undefined'); -phpbb.isTouch = (window && typeof window.ontouchstart !== 'undefined'); + // Add ajax pre-filter to prevent cross-domain script execution + $.ajaxPrefilter(s => { + if (s.crossDomain) { + s.contents.script = false; + } + }); -// Add ajax pre-filter to prevent cross-domain script execution -$.ajaxPrefilter(function(s) { - if (s.crossDomain) { - s.contents.script = false; - } -}); + /** + * Display a loading screen + * + * @returns {object} Returns loadingIndicator. + */ + phpbb.loadingIndicator = function() { + if (!$loadingIndicator) { + $loadingIndicator = $('#loading_indicator'); + } -/** - * Display a loading screen - * - * @returns {object} Returns loadingIndicator. - */ -phpbb.loadingIndicator = function() { - if (!$loadingIndicator) { - $loadingIndicator = $('#loading_indicator'); - } + if (!$loadingIndicator.is(':visible')) { + $loadingIndicator.fadeIn(phpbb.alertTime); + // Wait 60 seconds and display an error if nothing has been returned by then. + phpbb.clearLoadingTimeout(); + phpbbAlertTimer = setTimeout(() => { + phpbb.showTimeoutMessage(); + }, 60000); + } - if (!$loadingIndicator.is(':visible')) { - $loadingIndicator.fadeIn(phpbb.alertTime); - // Wait 60 seconds and display an error if nothing has been returned by then. - phpbb.clearLoadingTimeout(); - phpbbAlertTimer = setTimeout(function() { - phpbb.showTimeoutMessage(); - }, 60000); - } + return $loadingIndicator; + }; - return $loadingIndicator; -}; + /** + * Show timeout message + */ + phpbb.showTimeoutMessage = function() { + const $alert = $('#phpbb_alert'); -/** - * Show timeout message - */ -phpbb.showTimeoutMessage = function () { - var $alert = $('#phpbb_alert'); + if ($loadingIndicator.is(':visible')) { + phpbb.alert($alert.attr('data-l-err'), $alert.attr('data-l-timeout-processing-req')); + } + }; - if ($loadingIndicator.is(':visible')) { - phpbb.alert($alert.attr('data-l-err'), $alert.attr('data-l-timeout-processing-req')); - } -}; + /** + * Clear loading alert timeout + */ + phpbb.clearLoadingTimeout = function() { + if (phpbbAlertTimer !== null) { + clearTimeout(phpbbAlertTimer); + phpbbAlertTimer = null; + } + }; -/** - * Clear loading alert timeout -*/ -phpbb.clearLoadingTimeout = function() { - if (phpbbAlertTimer !== null) { - clearTimeout(phpbbAlertTimer); - phpbbAlertTimer = null; - } -}; + /** + * Close popup alert after a specified delay + * + * @param {int} delay Delay in ms until darkenwrapper's click event is triggered + */ + phpbb.closeDarkenWrapper = function(delay) { + phpbbAlertTimer = setTimeout(() => { + $('#darkenwrapper').trigger('click'); + }, delay); + }; + /** + * Display a simple alert similar to JSs native alert(). + * + * You can only call one alert or confirm box at any one time. + * + * @param {string} title Title of the message, eg "Information" (HTML). + * @param {string} msg Message to display (HTML). + * + * @returns {object} Returns the div created. + */ + phpbb.alert = function(title, msg) { + const $alert = $('#phpbb_alert'); + $alert.find('.alert_title').html(title); + $alert.find('.alert_text').html(msg); -/** -* Close popup alert after a specified delay -* -* @param {int} delay Delay in ms until darkenwrapper's click event is triggered -*/ -phpbb.closeDarkenWrapper = function(delay) { - phpbbAlertTimer = setTimeout(function() { - $('#darkenwrapper').trigger('click'); - }, delay); -}; + $(document).on('keydown.phpbb.alert', e => { + if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) { + phpbb.alert.close($alert, true); + e.preventDefault(); + e.stopPropagation(); + } + }); + phpbb.alert.open($alert); -/** - * Display a simple alert similar to JSs native alert(). - * - * You can only call one alert or confirm box at any one time. - * - * @param {string} title Title of the message, eg "Information" (HTML). - * @param {string} msg Message to display (HTML). - * - * @returns {object} Returns the div created. - */ -phpbb.alert = function(title, msg) { - var $alert = $('#phpbb_alert'); - $alert.find('.alert_title').html(title); - $alert.find('.alert_text').html(msg); + return $alert; + }; - $(document).on('keydown.phpbb.alert', function(e) { - if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) { + /** + * Handler for opening an alert box. + * + * @param {jQuery} $alert jQuery object. + */ + phpbb.alert.open = function($alert) { + if (!$dark.is(':visible')) { + $dark.fadeIn(phpbb.alertTime); + } + + if ($loadingIndicator && $loadingIndicator.is(':visible')) { + $loadingIndicator.fadeOut(phpbb.alertTime, () => { + $dark.append($alert); + $alert.fadeIn(phpbb.alertTime); + }); + } else if ($dark.is(':visible')) { + $dark.append($alert); + $alert.fadeIn(phpbb.alertTime); + } else { + $dark.append($alert); + $alert.show(); + $dark.fadeIn(phpbb.alertTime); + } + + $alert.on('click', e => { + e.stopPropagation(); + }); + + $dark.one('click', e => { phpbb.alert.close($alert, true); e.preventDefault(); e.stopPropagation(); - } - }); - phpbb.alert.open($alert); - - return $alert; -}; - -/** -* Handler for opening an alert box. -* -* @param {jQuery} $alert jQuery object. -*/ -phpbb.alert.open = function($alert) { - if (!$dark.is(':visible')) { - $dark.fadeIn(phpbb.alertTime); - } - - if ($loadingIndicator && $loadingIndicator.is(':visible')) { - $loadingIndicator.fadeOut(phpbb.alertTime, function() { - $dark.append($alert); - $alert.fadeIn(phpbb.alertTime); }); - } else if ($dark.is(':visible')) { - $dark.append($alert); - $alert.fadeIn(phpbb.alertTime); - } else { - $dark.append($alert); - $alert.show(); - $dark.fadeIn(phpbb.alertTime); - } - $alert.on('click', function(e) { - e.stopPropagation(); - }); + $alert.find('.alert_close').one('click', e => { + phpbb.alert.close($alert, true); + e.preventDefault(); + }); + }; - $dark.one('click', function(e) { - phpbb.alert.close($alert, true); - e.preventDefault(); - e.stopPropagation(); - }); + /** + * Handler for closing an alert box. + * + * @param {jQuery} $alert jQuery object. + * @param {bool} fadedark Whether to remove dark background. + */ + phpbb.alert.close = function($alert, fadedark) { + const $fade = (fadedark) ? $dark : $alert; - $alert.find('.alert_close').one('click', function(e) { - phpbb.alert.close($alert, true); - e.preventDefault(); - }); -}; + $fade.fadeOut(phpbb.alertTime, () => { + $alert.hide(); + }); -/** -* Handler for closing an alert box. -* -* @param {jQuery} $alert jQuery object. -* @param {bool} fadedark Whether to remove dark background. -*/ -phpbb.alert.close = function($alert, fadedark) { - var $fade = (fadedark) ? $dark : $alert; + $alert.find('.alert_close').off('click'); + $(document).off('keydown.phpbb.alert'); + }; - $fade.fadeOut(phpbb.alertTime, function() { - $alert.hide(); - }); + /** + * Display a simple yes / no box to the user. + * + * You can only call one alert or confirm box at any one time. + * + * @param {string} msg Message to display (HTML). + * @param {function} callback Callback. Bool param, whether the user pressed + * yes or no (or whatever their language is). + * @param {bool} fadedark Remove the dark background when done? Defaults + * to yes. + * + * @returns {object} Returns the div created. + */ + phpbb.confirm = function(msg, callback, fadedark) { + const $confirmDiv = $('#phpbb_confirm'); + $confirmDiv.find('.alert_text').html(msg); + fadedark = typeof fadedark === 'undefined' ? true : fadedark; - $alert.find('.alert_close').off('click'); - $(document).off('keydown.phpbb.alert'); -}; + $(document).on('keydown.phpbb.alert', e => { + if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) { + const name = (e.keyCode === keymap.ENTER) ? 'confirm' : 'cancel'; -/** - * Display a simple yes / no box to the user. - * - * You can only call one alert or confirm box at any one time. - * - * @param {string} msg Message to display (HTML). - * @param {function} callback Callback. Bool param, whether the user pressed - * yes or no (or whatever their language is). - * @param {bool} fadedark Remove the dark background when done? Defaults - * to yes. - * - * @returns {object} Returns the div created. - */ -phpbb.confirm = function(msg, callback, fadedark) { - var $confirmDiv = $('#phpbb_confirm'); - $confirmDiv.find('.alert_text').html(msg); - fadedark = typeof fadedark !== 'undefined' ? fadedark : true; + $('input[name="' + name + '"]').trigger('click'); + e.preventDefault(); + e.stopPropagation(); + } + }); - $(document).on('keydown.phpbb.alert', function(e) { - if (e.keyCode === keymap.ENTER || e.keyCode === keymap.ESC) { - var name = (e.keyCode === keymap.ENTER) ? 'confirm' : 'cancel'; + $confirmDiv.find('input[type="button"]').one('click.phpbb.confirmbox', function(e) { + const confirmed = this.name === 'confirm'; + + callback(confirmed); + $confirmDiv.find('input[type="button"]').off('click.phpbb.confirmbox'); + phpbb.alert.close($confirmDiv, fadedark || !confirmed); - $('input[name="' + name + '"]').trigger('click'); e.preventDefault(); e.stopPropagation(); - } - }); + }); - $confirmDiv.find('input[type="button"]').one('click.phpbb.confirmbox', function(e) { - var confirmed = this.name === 'confirm'; + phpbb.alert.open($confirmDiv); - callback(confirmed); - $confirmDiv.find('input[type="button"]').off('click.phpbb.confirmbox'); - phpbb.alert.close($confirmDiv, fadedark || !confirmed); + return $confirmDiv; + }; - e.preventDefault(); - e.stopPropagation(); - }); + /** + * Turn a querystring into an array. + * + * @argument {string} string The querystring to parse. + * @returns {object} The object created. + */ + phpbb.parseQuerystring = function(string) { + const params = {}; + let i; + let split; - phpbb.alert.open($confirmDiv); - - return $confirmDiv; -}; - -/** - * Turn a querystring into an array. - * - * @argument {string} string The querystring to parse. - * @returns {object} The object created. - */ -phpbb.parseQuerystring = function(string) { - var params = {}, i, split; - - string = string.split('&'); - for (i = 0; i < string.length; i++) { - split = string[i].split('='); - params[split[0]] = decodeURIComponent(split[1]); - } - return params; -}; - - -/** - * Makes a link use AJAX instead of loading an entire page. - * - * This function will work for links (both standard links and links which - * invoke confirm_box) and forms. It will be called automatically for links - * and forms with the data-ajax attribute set, and will call the necessary - * callback. - * - * For more info, view the following page on the phpBB wiki: - * http://wiki.phpbb.com/JavaScript_Function.phpbb.ajaxify - * - * @param {object} options Options. - */ -phpbb.ajaxify = function(options) { - var $elements = $(options.selector), - refresh = options.refresh, - callback = options.callback, - overlay = (typeof options.overlay !== 'undefined') ? options.overlay : true, - isForm = $elements.is('form'), - isText = $elements.is('input[type="text"], textarea'), - eventName; - - if (isForm) { - eventName = 'submit'; - } else if (isText) { - eventName = 'keyup'; - } else { - eventName = 'click'; - } - - $elements.on(eventName, function(event) { - var action, method, data, submit, that = this, $this = $(this); - - if ($this.find('input[type="submit"][data-clicked]').attr('data-ajax') === 'false') { - return; + string = string.split('&'); + for (i = 0; i < string.length; i++) { + split = string[i].split('='); + params[split[0]] = decodeURIComponent(split[1]); } - /** - * Handler for AJAX errors - */ - function errorHandler(jqXHR, textStatus, errorThrown) { - if (typeof console !== 'undefined' && console.log) { - console.log('AJAX error. status: ' + textStatus + ', message: ' + errorThrown); - } - phpbb.clearLoadingTimeout(); - var responseText, errorText = false; - try { - responseText = JSON.parse(jqXHR.responseText); - responseText = responseText.message; - } catch (e) {} - if (typeof responseText === 'string' && responseText.length > 0) { - errorText = responseText; - } else if (typeof errorThrown === 'string' && errorThrown.length > 0) { - errorText = errorThrown; - } else { - errorText = $dark.attr('data-ajax-error-text-' + textStatus); - if (typeof errorText !== 'string' || !errorText.length) { - errorText = $dark.attr('data-ajax-error-text'); - } - } - phpbb.alert($dark.attr('data-ajax-error-title'), errorText); - } + return params; + }; - /** - * This is a private function used to handle the callbacks, refreshes - * and alert. It calls the callback, refreshes the page if necessary, and - * displays an alert to the user and removes it after an amount of time. - * - * It cannot be called from outside this function, and is purely here to - * avoid repetition of code. - * - * @param {object} res The object sent back by the server. - */ - function returnHandler(res) { - var alert; - - phpbb.clearLoadingTimeout(); - - // Is a confirmation required? - if (typeof res.S_CONFIRM_ACTION === 'undefined') { - // If a confirmation is not required, display an alert and call the - // callbacks. - if (typeof res.MESSAGE_TITLE !== 'undefined') { - alert = phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT); - } else { - $dark.fadeOut(phpbb.alertTime); - - if ($loadingIndicator) { - $loadingIndicator.fadeOut(phpbb.alertTime); - } - } - - if (typeof phpbb.ajaxCallbacks[callback] === 'function') { - phpbb.ajaxCallbacks[callback].call(that, res); - } - - // If the server says to refresh the page, check whether the page should - // be refreshed and refresh page after specified time if required. - if (res.REFRESH_DATA) { - if (typeof refresh === 'function') { - refresh = refresh(res.REFRESH_DATA.url); - } else if (typeof refresh !== 'boolean') { - refresh = false; - } - - phpbbAlertTimer = setTimeout(function() { - if (refresh) { - window.location = res.REFRESH_DATA.url; - } - - // Hide the alert even if we refresh the page, in case the user - // presses the back button. - $dark.fadeOut(phpbb.alertTime, function() { - if (typeof alert !== 'undefined') { - alert.hide(); - } - }); - }, res.REFRESH_DATA.time * 1000); // Server specifies time in seconds - } - } else { - // If confirmation is required, display a dialog to the user. - phpbb.confirm(res.MESSAGE_BODY, function(del) { - if (!del) { - return; - } - - phpbb.loadingIndicator(); - data = $('' + res.S_HIDDEN_FIELDS + '').serialize(); - $.ajax({ - url: res.S_CONFIRM_ACTION, - type: 'POST', - data: data + '&confirm=' + res.YES_VALUE + '&' + $('form', '#phpbb_confirm').serialize(), - success: returnHandler, - error: errorHandler - }); - }, false); - } - } - - // If the element is a form, POST must be used and some extra data must - // be taken from the form. - var runFilter = (typeof options.filter === 'function'); - data = {}; + /** + * Makes a link use AJAX instead of loading an entire page. + * + * This function will work for links (both standard links and links which + * invoke confirm_box) and forms. It will be called automatically for links + * and forms with the data-ajax attribute set, and will call the necessary + * callback. + * + * For more info, view the following page on the phpBB wiki: + * http://wiki.phpbb.com/JavaScript_Function.phpbb.ajaxify + * + * @param {object} options Options. + */ + phpbb.ajaxify = function(options) { + const $elements = $(options.selector); + let { refresh } = options; + const { callback } = options; + const overlay = typeof options.overlay === 'undefined' ? true : options.overlay; + const isForm = $elements.is('form'); + const isText = $elements.is('input[type="text"], textarea'); + let eventName; if (isForm) { - action = $this.attr('action').replace('&', '&'); - data = $this.serializeArray(); - method = $this.attr('method') || 'GET'; - - if ($this.find('input[type="submit"][data-clicked]')) { - submit = $this.find('input[type="submit"][data-clicked]'); - data.push({ - name: submit.attr('name'), - value: submit.val() - }); - } + eventName = 'submit'; } else if (isText) { - var name = $this.attr('data-name') || this.name; - action = $this.attr('data-url').replace('&', '&'); - data[name] = this.value; - method = 'POST'; + eventName = 'keyup'; } else { - action = this.href; - data = null; - method = 'GET'; + eventName = 'click'; } - var sendRequest = function() { - var dataOverlay = $this.attr('data-overlay'); - if (overlay && (typeof dataOverlay === 'undefined' || dataOverlay === 'true')) { - phpbb.loadingIndicator(); + $elements.on(eventName, function(event) { + let action; + let method; + let data; + let submit; + const that = this; + const $this = $(this); + + if ($this.find('input[type="submit"][data-clicked]').attr('data-ajax') === 'false') { + return; } - var request = $.ajax({ - url: action, - type: method, - data: data, - success: returnHandler, - error: errorHandler, - cache: false - }); - - request.always(function() { - if ($loadingIndicator && $loadingIndicator.is(':visible')) { - $loadingIndicator.fadeOut(phpbb.alertTime); + /** + * Handler for AJAX errors + */ + function errorHandler(jqXHR, textStatus, errorThrown) { + if (typeof console !== 'undefined' && console.log) { + console.log('AJAX error. status: ' + textStatus + ', message: ' + errorThrown); } - }); - }; - // If filter function returns false, cancel the AJAX functionality, - // and return true (meaning that the HTTP request will be sent normally). - if (runFilter && !options.filter.call(this, data, event, sendRequest)) { + phpbb.clearLoadingTimeout(); + let responseText; + let errorText = false; + try { + responseText = JSON.parse(jqXHR.responseText); + responseText = responseText.message; + } catch {} + + if (typeof responseText === 'string' && responseText.length > 0) { + errorText = responseText; + } else if (typeof errorThrown === 'string' && errorThrown.length > 0) { + errorText = errorThrown; + } else { + errorText = $dark.attr('data-ajax-error-text-' + textStatus); + if (typeof errorText !== 'string' || !errorText.length) { + errorText = $dark.attr('data-ajax-error-text'); + } + } + + phpbb.alert($dark.attr('data-ajax-error-title'), errorText); + } + + /** + * This is a private function used to handle the callbacks, refreshes + * and alert. It calls the callback, refreshes the page if necessary, and + * displays an alert to the user and removes it after an amount of time. + * + * It cannot be called from outside this function, and is purely here to + * avoid repetition of code. + * + * @param {object} res The object sent back by the server. + */ + function returnHandler(res) { + let alert; + + phpbb.clearLoadingTimeout(); + + // Is a confirmation required? + if (typeof res.S_CONFIRM_ACTION === 'undefined') { + // If a confirmation is not required, display an alert and call the + // callbacks. + if (typeof res.MESSAGE_TITLE === 'undefined') { + $dark.fadeOut(phpbb.alertTime); + + if ($loadingIndicator) { + $loadingIndicator.fadeOut(phpbb.alertTime); + } + } else { + alert = phpbb.alert(res.MESSAGE_TITLE, res.MESSAGE_TEXT); + } + + if (typeof phpbb.ajaxCallbacks[callback] === 'function') { + phpbb.ajaxCallbacks[callback].call(that, res); + } + + // If the server says to refresh the page, check whether the page should + // be refreshed and refresh page after specified time if required. + if (res.REFRESH_DATA) { + if (typeof refresh === 'function') { + refresh = refresh(res.REFRESH_DATA.url); + } else if (typeof refresh !== 'boolean') { + refresh = false; + } + + phpbbAlertTimer = setTimeout(() => { + if (refresh) { + window.location = res.REFRESH_DATA.url; + } + + // Hide the alert even if we refresh the page, in case the user + // presses the back button. + $dark.fadeOut(phpbb.alertTime, () => { + if (typeof alert !== 'undefined') { + alert.hide(); + } + }); + }, res.REFRESH_DATA.time * 1000); // Server specifies time in seconds + } + } else { + // If confirmation is required, display a dialog to the user. + phpbb.confirm(res.MESSAGE_BODY, del => { + if (!del) { + return; + } + + phpbb.loadingIndicator(); + data = $('
' + res.S_HIDDEN_FIELDS + '
').serialize(); + $.ajax({ + url: res.S_CONFIRM_ACTION, + type: 'POST', + data: data + '&confirm=' + res.YES_VALUE + '&' + $('form', '#phpbb_confirm').serialize(), + success: returnHandler, + error: errorHandler, + }); + }, false); + } + } + + // If the element is a form, POST must be used and some extra data must + // be taken from the form. + const runFilter = (typeof options.filter === 'function'); + data = {}; + + if (isForm) { + action = $this.attr('action').replace('&', '&'); + data = $this.serializeArray(); + method = $this.attr('method') || 'GET'; + + if ($this.find('input[type="submit"][data-clicked]')) { + submit = $this.find('input[type="submit"][data-clicked]'); + data.push({ + name: submit.attr('name'), + value: submit.val(), + }); + } + } else if (isText) { + const name = $this.attr('data-name') || this.name; + action = $this.attr('data-url').replace('&', '&'); + data[name] = this.value; + method = 'POST'; + } else { + action = this.href; + data = null; + method = 'GET'; + } + + const sendRequest = function() { + const dataOverlay = $this.attr('data-overlay'); + if (overlay && (typeof dataOverlay === 'undefined' || dataOverlay === 'true')) { + phpbb.loadingIndicator(); + } + + const request = $.ajax({ + url: action, + type: method, + data, + success: returnHandler, + error: errorHandler, + cache: false, + }); + + request.always(() => { + if ($loadingIndicator && $loadingIndicator.is(':visible')) { + $loadingIndicator.fadeOut(phpbb.alertTime); + } + }); + }; + + // If filter function returns false, cancel the AJAX functionality, + // and return true (meaning that the HTTP request will be sent normally). + if (runFilter && !options.filter.call(this, data, event, sendRequest)) { + return; + } + + sendRequest(); + event.preventDefault(); + }); + + if (isForm) { + $elements.find('input:submit').click(function() { + const $this = $(this); + + // Remove data-clicked attribute from any submit button of form + $this.parents('form:first').find('input:submit[data-clicked]').removeAttr('data-clicked'); + + $this.attr('data-clicked', 'true'); + }); + } + + return this; + }; + + phpbb.search = { + cache: { + data: [], + }, + tpl: [], + container: [], + }; + + /** + * Get cached search data. + * + * @param {string} id Search ID. + * @returns {bool|object} Cached data object. Returns false if no data exists. + */ + phpbb.search.cache.get = function(id) { + if (this.data[id]) { + return this.data[id]; + } + + return false; + }; + + /** + * Set search cache data value. + * + * @param {string} id Search ID. + * @param {string} key Data key. + * @param {string} value Data value. + */ + phpbb.search.cache.set = function(id, key, value) { + if (!this.data[id]) { + this.data[id] = { results: [] }; + } + + this.data[id][key] = value; + }; + + /** + * Cache search result. + * + * @param {string} id Search ID. + * @param {string} keyword Keyword. + * @param {Array} results Search results. + */ + phpbb.search.cache.setResults = function(id, keyword, results) { + this.data[id].results[keyword] = results; + }; + + /** + * Trim spaces from keyword and lower its case. + * + * @param {string} keyword Search keyword to clean. + * @returns {string} Cleaned string. + */ + phpbb.search.cleanKeyword = function(keyword) { + return $.trim(keyword).toLowerCase(); + }; + + /** + * Get clean version of search keyword. If textarea supports several keywords + * (one per line), it fetches the current keyword based on the caret position. + * + * @param {jQuery} $input Search input|textarea. + * @param {string} keyword Input|textarea value. + * @param {bool} multiline Whether textarea supports multiple search keywords. + * + * @returns string Clean string. + */ + phpbb.search.getKeyword = function($input, keyword, multiline) { + if (multiline) { + const line = phpbb.search.getKeywordLine($input); + keyword = keyword.split('\n').splice(line, 1); + } + + return phpbb.search.cleanKeyword(keyword); + }; + + /** + * Get the textarea line number on which the keyword resides - for textareas + * that support multiple keywords (one per line). + * + * @param {jQuery} $textarea Search textarea. + * @returns {int} The line number. + */ + phpbb.search.getKeywordLine = function($textarea) { + const { selectionStart } = $textarea.get(0); + return $textarea.val().substr(0, selectionStart).split('\n').length - 1; + }; + + /** + * Set the value on the input|textarea. If textarea supports multiple + * keywords, only the active keyword is replaced. + * + * @param {jQuery} $input Search input|textarea. + * @param {string} value Value to set. + * @param {bool} multiline Whether textarea supports multiple search keywords. + */ + phpbb.search.setValue = function($input, value, multiline) { + if (multiline) { + const line = phpbb.search.getKeywordLine($input); + const lines = $input.val().split('\n'); + lines[line] = value; + value = lines.join('\n'); + } + + $input.val(value); + }; + + /** + * Sets the onclick event to set the value on the input|textarea to the + * selected search result. + * + * @param {jQuery} $input Search input|textarea. + * @param {object} value Result object. + * @param {jQuery} $row Result element. + * @param {jQuery} $container jQuery object for the search container. + */ + phpbb.search.setValueOnClick = function($input, value, $row, $container) { + $row.click(() => { + phpbb.search.setValue($input, value.result, $input.attr('data-multiline')); + phpbb.search.closeResults($input, $container); + }); + }; + + /** + * Runs before the AJAX search request is sent and determines whether + * there is a need to contact the server. If there are cached results + * already, those are displayed instead. Executes the AJAX request function + * itself due to the need to use a timeout to limit the number of requests. + * + * @param {Array} data Data to be sent to the server. + * @param {object} event Onkeyup event object. + * @param {function} sendRequest Function to execute AJAX request. + * + * @returns {boolean} Returns false. + */ + phpbb.search.filter = function(data, event, sendRequest) { + const $this = $(this); + const dataName = $this.attr('data-name') === undefined ? $this.attr('name') : $this.attr('data-name'); + const minLength = parseInt($this.attr('data-min-length'), 10); + const searchID = $this.attr('data-results'); + const keyword = phpbb.search.getKeyword($this, data[dataName], $this.attr('data-multiline')); + const cache = phpbb.search.cache.get(searchID); + const key = event.keyCode || event.which; + let proceed = true; + data[dataName] = keyword; + + // No need to search if enter was pressed + // for selecting a value from the results. + if (key === keymap.ENTER) { + return false; + } + + if (cache.timeout) { + clearTimeout(cache.timeout); + } + + const timeout = setTimeout(function() { + // Check min length and existence of cache. + if (minLength > keyword.length) { + proceed = false; + } else if (cache.lastSearch) { + // Has the keyword actually changed? + if (cache.lastSearch === keyword) { + proceed = false; + } else { + // Do we already have results for this? + if (cache.results[keyword]) { + const response = { + keyword, + results: cache.results[keyword], + }; + phpbb.search.handleResponse(response, $this, true); + proceed = false; + } + + // If the previous search didn't yield results and the string only had characters added to it, + // then we won't bother sending a request. + if (keyword.indexOf(cache.lastSearch) === 0 && cache.results[cache.lastSearch].length === 0) { + phpbb.search.cache.set(searchID, 'lastSearch', keyword); + phpbb.search.cache.setResults(searchID, keyword, []); + proceed = false; + } + } + } + + if (proceed) { + sendRequest.call(this); + } + }, 350); + phpbb.search.cache.set(searchID, 'timeout', timeout); + + return false; + }; + + /** + * Handle search result response. + * + * @param {object} res Data received from server. + * @param {jQuery} $input Search input|textarea. + * @param {bool} fromCache Whether the results are from the cache. + * @param {function} callback Optional callback to run when assigning each search result. + */ + phpbb.search.handleResponse = function(res, $input, fromCache, callback) { + if (typeof res !== 'object') { return; } - sendRequest(); - event.preventDefault(); - }); + const searchID = $input.attr('data-results'); + const $container = $(searchID); - if (isForm) { - $elements.find('input:submit').click(function () { - var $this = $(this); + if (this.cache.get(searchID).callback) { + callback = this.cache.get(searchID).callback; + } else if (typeof callback === 'function') { + this.cache.set(searchID, 'callback', callback); + } - // Remove data-clicked attribute from any submit button of form - $this.parents('form:first').find('input:submit[data-clicked]').removeAttr('data-clicked'); + if (!fromCache) { + this.cache.setResults(searchID, res.keyword, res.results); + } - $this.attr('data-clicked', 'true'); - }); - } + this.cache.set(searchID, 'lastSearch', res.keyword); + this.showResults(res.results, $input, $container, callback); + }; - return this; -}; + /** + * Show search results. + * + * @param {Array} results Search results. + * @param {jQuery} $input Search input|textarea. + * @param {jQuery} $container Search results container element. + * @param {function} callback Optional callback to run when assigning each search result. + */ + phpbb.search.showResults = function(results, $input, $container, callback) { + const $resultContainer = $('.search-results', $container); + this.clearResults($resultContainer); -phpbb.search = { - cache: { - data: [] - }, - tpl: [], - container: [] -}; + if (!results.length) { + $container.hide(); + return; + } -/** - * Get cached search data. - * - * @param {string} id Search ID. - * @returns {bool|object} Cached data object. Returns false if no data exists. - */ -phpbb.search.cache.get = function(id) { - if (this.data[id]) { - return this.data[id]; - } - return false; -}; + const searchID = $container.attr('id'); + let tpl; + let row; -/** - * Set search cache data value. - * - * @param {string} id Search ID. - * @param {string} key Data key. - * @param {string} value Data value. - */ -phpbb.search.cache.set = function(id, key, value) { - if (!this.data[id]) { - this.data[id] = { results: [] }; - } - this.data[id][key] = value; -}; + if (!this.tpl[searchID]) { + tpl = $('.search-result-tpl', $container); + this.tpl[searchID] = tpl.clone().removeClass('search-result-tpl'); + tpl.remove(); + } -/** - * Cache search result. - * - * @param {string} id Search ID. - * @param {string} keyword Keyword. - * @param {Array} results Search results. - */ -phpbb.search.cache.setResults = function(id, keyword, results) { - this.data[id].results[keyword] = results; -}; + tpl = this.tpl[searchID]; -/** - * Trim spaces from keyword and lower its case. - * - * @param {string} keyword Search keyword to clean. - * @returns {string} Cleaned string. - */ -phpbb.search.cleanKeyword = function(keyword) { - return $.trim(keyword).toLowerCase(); -}; + $.each(results, function(i, item) { + row = tpl.clone(); + row.find('.search-result').html(item.display); -/** - * Get clean version of search keyword. If textarea supports several keywords - * (one per line), it fetches the current keyword based on the caret position. - * - * @param {jQuery} $input Search input|textarea. - * @param {string} keyword Input|textarea value. - * @param {bool} multiline Whether textarea supports multiple search keywords. - * - * @returns string Clean string. - */ -phpbb.search.getKeyword = function($input, keyword, multiline) { - if (multiline) { - var line = phpbb.search.getKeywordLine($input); - keyword = keyword.split('\n').splice(line, 1); - } - return phpbb.search.cleanKeyword(keyword); -}; - -/** - * Get the textarea line number on which the keyword resides - for textareas - * that support multiple keywords (one per line). - * - * @param {jQuery} $textarea Search textarea. - * @returns {int} The line number. - */ -phpbb.search.getKeywordLine = function ($textarea) { - var selectionStart = $textarea.get(0).selectionStart; - return $textarea.val().substr(0, selectionStart).split('\n').length - 1; -}; - -/** - * Set the value on the input|textarea. If textarea supports multiple - * keywords, only the active keyword is replaced. - * - * @param {jQuery} $input Search input|textarea. - * @param {string} value Value to set. - * @param {bool} multiline Whether textarea supports multiple search keywords. - */ -phpbb.search.setValue = function($input, value, multiline) { - if (multiline) { - var line = phpbb.search.getKeywordLine($input), - lines = $input.val().split('\n'); - lines[line] = value; - value = lines.join('\n'); - } - $input.val(value); -}; - -/** - * Sets the onclick event to set the value on the input|textarea to the - * selected search result. - * - * @param {jQuery} $input Search input|textarea. - * @param {object} value Result object. - * @param {jQuery} $row Result element. - * @param {jQuery} $container jQuery object for the search container. - */ -phpbb.search.setValueOnClick = function($input, value, $row, $container) { - $row.click(function() { - phpbb.search.setValue($input, value.result, $input.attr('data-multiline')); - phpbb.search.closeResults($input, $container); - }); -}; - -/** - * Runs before the AJAX search request is sent and determines whether - * there is a need to contact the server. If there are cached results - * already, those are displayed instead. Executes the AJAX request function - * itself due to the need to use a timeout to limit the number of requests. - * - * @param {Array} data Data to be sent to the server. - * @param {object} event Onkeyup event object. - * @param {function} sendRequest Function to execute AJAX request. - * - * @returns {boolean} Returns false. - */ -phpbb.search.filter = function(data, event, sendRequest) { - var $this = $(this), - dataName = ($this.attr('data-name') !== undefined) ? $this.attr('data-name') : $this.attr('name'), - minLength = parseInt($this.attr('data-min-length'), 10), - searchID = $this.attr('data-results'), - keyword = phpbb.search.getKeyword($this, data[dataName], $this.attr('data-multiline')), - cache = phpbb.search.cache.get(searchID), - key = event.keyCode || event.which, - proceed = true; - data[dataName] = keyword; - - // No need to search if enter was pressed - // for selecting a value from the results. - if (key === keymap.ENTER) { - return false; - } - - if (cache.timeout) { - clearTimeout(cache.timeout); - } - - var timeout = setTimeout(function() { - // Check min length and existence of cache. - if (minLength > keyword.length) { - proceed = false; - } else if (cache.lastSearch) { - // Has the keyword actually changed? - if (cache.lastSearch === keyword) { - proceed = false; - } else { - // Do we already have results for this? - if (cache.results[keyword]) { - var response = { - keyword: keyword, - results: cache.results[keyword] - }; - phpbb.search.handleResponse(response, $this, true); - proceed = false; - } - - // If the previous search didn't yield results and the string only had characters added to it, - // then we won't bother sending a request. - if (keyword.indexOf(cache.lastSearch) === 0 && cache.results[cache.lastSearch].length === 0) { - phpbb.search.cache.set(searchID, 'lastSearch', keyword); - phpbb.search.cache.setResults(searchID, keyword, []); - proceed = false; - } + if (typeof callback === 'function') { + callback.call(this, $input, item, row, $container); } - } - if (proceed) { - sendRequest.call(this); - } - }, 350); - phpbb.search.cache.set(searchID, 'timeout', timeout); + row.appendTo($resultContainer).show(); + }); + $container.show(); - return false; -}; + phpbb.search.navigateResults($input, $container, $resultContainer); + }; -/** - * Handle search result response. - * - * @param {object} res Data received from server. - * @param {jQuery} $input Search input|textarea. - * @param {bool} fromCache Whether the results are from the cache. - * @param {function} callback Optional callback to run when assigning each search result. - */ -phpbb.search.handleResponse = function(res, $input, fromCache, callback) { - if (typeof res !== 'object') { - return; - } + /** + * Clear search results. + * + * @param {jQuery} $container Search results container. + */ + phpbb.search.clearResults = function($container) { + $container.children(':not(.search-result-tpl)').remove(); + }; - var searchID = $input.attr('data-results'), - $container = $(searchID); - - if (this.cache.get(searchID).callback) { - callback = this.cache.get(searchID).callback; - } else if (typeof callback === 'function') { - this.cache.set(searchID, 'callback', callback); - } - - if (!fromCache) { - this.cache.setResults(searchID, res.keyword, res.results); - } - - this.cache.set(searchID, 'lastSearch', res.keyword); - this.showResults(res.results, $input, $container, callback); -}; - -/** - * Show search results. - * - * @param {Array} results Search results. - * @param {jQuery} $input Search input|textarea. - * @param {jQuery} $container Search results container element. - * @param {function} callback Optional callback to run when assigning each search result. - */ -phpbb.search.showResults = function(results, $input, $container, callback) { - var $resultContainer = $('.search-results', $container); - this.clearResults($resultContainer); - - if (!results.length) { + /** + * Close search results. + * + * @param {jQuery} $input Search input|textarea. + * @param {jQuery} $container Search results container. + */ + phpbb.search.closeResults = function($input, $container) { + $input.off('.phpbb.search'); $container.hide(); - return; - } + }; - var searchID = $container.attr('id'), - tpl, - row; - - if (!this.tpl[searchID]) { - tpl = $('.search-result-tpl', $container); - this.tpl[searchID] = tpl.clone().removeClass('search-result-tpl'); - tpl.remove(); - } - tpl = this.tpl[searchID]; - - $.each(results, function(i, item) { - row = tpl.clone(); - row.find('.search-result').html(item.display); - - if (typeof callback === 'function') { - callback.call(this, $input, item, row, $container); - } - row.appendTo($resultContainer).show(); - }); - $container.show(); - - phpbb.search.navigateResults($input, $container, $resultContainer); -}; - -/** - * Clear search results. - * - * @param {jQuery} $container Search results container. - */ -phpbb.search.clearResults = function($container) { - $container.children(':not(.search-result-tpl)').remove(); -}; - -/** - * Close search results. - * - * @param {jQuery} $input Search input|textarea. - * @param {jQuery} $container Search results container. - */ -phpbb.search.closeResults = function($input, $container) { - $input.off('.phpbb.search'); - $container.hide(); -}; - -/** - * Navigate search results. - * - * @param {jQuery} $input Search input|textarea. - * @param {jQuery} $container Search results container. - * @param {jQuery} $resultContainer Search results list container. - */ -phpbb.search.navigateResults = function($input, $container, $resultContainer) { + /** + * Navigate search results. + * + * @param {jQuery} $input Search input|textarea. + * @param {jQuery} $container Search results container. + * @param {jQuery} $resultContainer Search results list container. + */ + phpbb.search.navigateResults = function($input, $container, $resultContainer) { // Add a namespace to the event (.phpbb.search), // so it can be unbound specifically later on. // Rebind it, to ensure the event is 'dynamic'. - $input.off('.phpbb.search'); - $input.on('keydown.phpbb.search', function(event) { - var key = event.keyCode || event.which, - $active = $resultContainer.children('.active'); + $input.off('.phpbb.search'); + $input.on('keydown.phpbb.search', event => { + const key = event.keyCode || event.which; + const $active = $resultContainer.children('.active'); - switch (key) { - // Close the results - case keymap.ESC: + if (key === keymap.ESC) { phpbb.search.closeResults($input, $container); - break; - - // Set the value for the selected result - case keymap.ENTER: + } else if (key === keymap.ENTER) { if ($active.length) { - var value = $active.find('.search-result > span').text(); + const value = $active.find('.search-result > span').text(); phpbb.search.setValue($input, value, $input.attr('data-multiline')); } @@ -760,13 +770,9 @@ phpbb.search.navigateResults = function($input, $container, $resultContainer) { // Do not submit the form event.preventDefault(); - break; - - // Navigate the results - case keymap.ARROW_DOWN: - case keymap.ARROW_UP: - var up = key === keymap.ARROW_UP, - $children = $resultContainer.children(); + } else if (key === keymap.ARROW_DOWN || key === keymap.ARROW_UP) { + const up = key === keymap.ARROW_UP; + const $children = $resultContainer.children(); if (!$active.length) { if (up) { @@ -794,472 +800,473 @@ phpbb.search.navigateResults = function($input, $container, $resultContainer) { // Do not change cursor position in the input element event.preventDefault(); - break; + } + }); + }; + + $('#phpbb').click(function() { + const $this = $(this); + + if (!$this.is('.live-search') && !$this.parents().is('.live-search')) { + phpbb.search.closeResults($('input, textarea'), $('.live-search')); } }); -}; -$('#phpbb').click(function() { - var $this = $(this); + phpbb.history = {}; - if (!$this.is('.live-search') && !$this.parents().is('.live-search')) { - phpbb.search.closeResults($('input, textarea'), $('.live-search')); - } -}); + /** + * Check whether a method in the native history object is supported. + * + * @param {string} fn Method name. + * @returns {bool} Returns true if the method is supported. + */ + phpbb.history.isSupported = function(fn) { + return !(typeof history === 'undefined' || typeof history[fn] === 'undefined'); + }; -phpbb.history = {}; + /** + * Wrapper for the pushState and replaceState methods of the + * native history object. + * + * @param {string} mode Mode. Either push or replace. + * @param {string} url New URL. + * @param {string} [title] Optional page title. + * @param {object} [obj] Optional state object. + */ + phpbb.history.alterUrl = function(mode, url, title, obj) { + const fn = mode + 'State'; -/** -* Check whether a method in the native history object is supported. -* -* @param {string} fn Method name. -* @returns {bool} Returns true if the method is supported. -*/ -phpbb.history.isSupported = function(fn) { - return !(typeof history === 'undefined' || typeof history[fn] === 'undefined'); -}; + if (!url || !phpbb.history.isSupported(fn)) { + return; + } -/** -* Wrapper for the pushState and replaceState methods of the -* native history object. -* -* @param {string} mode Mode. Either push or replace. -* @param {string} url New URL. -* @param {string} [title] Optional page title. -* @param {object} [obj] Optional state object. -*/ -phpbb.history.alterUrl = function(mode, url, title, obj) { - var fn = mode + 'State'; + if (!title) { + title = document.title; + } - if (!url || !phpbb.history.isSupported(fn)) { - return; - } - if (!title) { - title = document.title; - } - if (!obj) { - obj = null; - } + if (!obj) { + obj = null; + } - history[fn](obj, title, url); -}; + history[fn](obj, title, url); + }; -/** -* Wrapper for the native history.replaceState method. -* -* @param {string} url New URL. -* @param {string} [title] Optional page title. -* @param {object} [obj] Optional state object. -*/ -phpbb.history.replaceUrl = function(url, title, obj) { - phpbb.history.alterUrl('replace', url, title, obj); -}; + /** + * Wrapper for the native history.replaceState method. + * + * @param {string} url New URL. + * @param {string} [title] Optional page title. + * @param {object} [obj] Optional state object. + */ + phpbb.history.replaceUrl = function(url, title, obj) { + phpbb.history.alterUrl('replace', url, title, obj); + }; -/** -* Wrapper for the native history.pushState method. -* -* @param {string} url New URL. -* @param {string} [title] Optional page title. -* @param {object} [obj] Optional state object. -*/ -phpbb.history.pushUrl = function(url, title, obj) { - phpbb.history.alterUrl('push', url, title, obj); -}; + /** + * Wrapper for the native history.pushState method. + * + * @param {string} url New URL. + * @param {string} [title] Optional page title. + * @param {object} [obj] Optional state object. + */ + phpbb.history.pushUrl = function(url, title, obj) { + phpbb.history.alterUrl('push', url, title, obj); + }; -/** -* Hide the optgroups that are not the selected timezone -* -* @param {bool} keepSelection Shall we keep the value selected, or shall the -* user be forced to repick one. -*/ -phpbb.timezoneSwitchDate = function(keepSelection) { - var $timezoneCopy = $('#timezone_copy'); - var $timezone = $('#timezone'); - var $tzDate = $('#tz_date'); - var $tzSelectDateSuggest = $('#tz_select_date_suggest'); + /** + * Hide the optgroups that are not the selected timezone + * + * @param {bool} keepSelection Shall we keep the value selected, or shall the + * user be forced to repick one. + */ + phpbb.timezoneSwitchDate = function(keepSelection) { + const $timezoneCopy = $('#timezone_copy'); + const $timezone = $('#timezone'); + const $tzDate = $('#tz_date'); + const $tzSelectDateSuggest = $('#tz_select_date_suggest'); - if ($timezoneCopy.length === 0) { + if ($timezoneCopy.length === 0) { // We make a backup of the original dropdown, so we can remove optgroups // instead of setting display to none, because IE and chrome will not // hide options inside of optgroups and selects via css - $timezone.clone() - .attr('id', 'timezone_copy') - .css('display', 'none') - .attr('name', 'tz_copy') - .insertAfter('#timezone'); - } else { + $timezone.clone() + .attr('id', 'timezone_copy') + .css('display', 'none') + .attr('name', 'tz_copy') + .insertAfter('#timezone'); + } else { // Copy the content of our backup, so we can remove all unneeded options - $timezone.html($timezoneCopy.html()); - } + $timezone.html($timezoneCopy.html()); + } - if ($tzDate.val() !== '') { - $timezone.children('optgroup').remove(':not([data-tz-value="' + $tzDate.val() + '"])'); - } + if ($tzDate.val() !== '') { + $timezone.children('optgroup').remove(':not([data-tz-value="' + $tzDate.val() + '"])'); + } - if ($tzDate.val() === $tzSelectDateSuggest.attr('data-suggested-tz')) { - $tzSelectDateSuggest.css('display', 'none'); - } else { - $tzSelectDateSuggest.css('display', 'inline'); - } + if ($tzDate.val() === $tzSelectDateSuggest.attr('data-suggested-tz')) { + $tzSelectDateSuggest.css('display', 'none'); + } else { + $tzSelectDateSuggest.css('display', 'inline'); + } - var $tzOptions = $timezone.children('optgroup[data-tz-value="' + $tzDate.val() + '"]').children('option'); + const $tzOptions = $timezone.children('optgroup[data-tz-value="' + $tzDate.val() + '"]').children('option'); - if ($tzOptions.length === 1) { + if ($tzOptions.length === 1) { // If there is only one timezone for the selected date, we just select that automatically. - $tzOptions.prop('selected', true); - keepSelection = true; - } - - if (typeof keepSelection !== 'undefined' && !keepSelection) { - var $timezoneOptions = $timezone.find('optgroup option'); - if ($timezoneOptions.filter(':selected').length <= 0) { - $timezoneOptions.filter(':first').prop('selected', true); + $tzOptions.prop('selected', true); + keepSelection = true; } - } -}; -/** -* Display the date/time select -*/ -phpbb.timezoneEnableDateSelection = function() { - $('#tz_select_date').css('display', 'block'); -}; - -/** -* Preselect a date/time or suggest one, if it is not picked. -* -* @param {bool} forceSelector Shall we select the suggestion? -*/ -phpbb.timezonePreselectSelect = function(forceSelector) { - - // The offset returned here is in minutes and negated. - var offset = (new Date()).getTimezoneOffset(); - var sign = '-'; - - if (offset < 0) { - sign = '+'; - offset = -offset; - } - - var minutes = offset % 60; - var hours = (offset - minutes) / 60; - - if (hours === 0) { - hours = '00'; - sign = '+'; - } else if (hours < 10) { - hours = '0' + hours.toString(); - } else { - hours = hours.toString(); - } - - if (minutes < 10) { - minutes = '0' + minutes.toString(); - } else { - minutes = minutes.toString(); - } - - var prefix = 'UTC' + sign + hours + ':' + minutes; - var prefixLength = prefix.length; - var selectorOptions = $('option', '#tz_date'); - var i; - - var $tzSelectDateSuggest = $('#tz_select_date_suggest'); - - for (i = 0; i < selectorOptions.length; ++i) { - var option = selectorOptions[i]; - - if (option.value.substring(0, prefixLength) === prefix) { - if ($('#tz_date').val() !== option.value && !forceSelector) { - // We do not select the option for the user, but notify him, - // that we would suggest a different setting. - phpbb.timezoneSwitchDate(true); - $tzSelectDateSuggest.css('display', 'inline'); - } else { - option.selected = true; - phpbb.timezoneSwitchDate(!forceSelector); - $tzSelectDateSuggest.css('display', 'none'); + if (typeof keepSelection !== 'undefined' && !keepSelection) { + const $timezoneOptions = $timezone.find('optgroup option'); + if ($timezoneOptions.filter(':selected').length <= 0) { + $timezoneOptions.filter(':first').prop('selected', true); } - - var suggestion = $tzSelectDateSuggest.attr('data-l-suggestion'); - - $tzSelectDateSuggest.attr('title', suggestion.replace('%s', option.innerHTML)); - $tzSelectDateSuggest.attr('value', suggestion.replace('%s', option.innerHTML.substring(0, 9))); - $tzSelectDateSuggest.attr('data-suggested-tz', option.innerHTML); - - // Found the suggestion, there cannot be more, so return from here. - return; } - } -}; - -phpbb.ajaxCallbacks = {}; - -/** - * Adds an AJAX callback to be used by phpbb.ajaxify. - * - * See the phpbb.ajaxify comments for information on stuff like parameters. - * - * @param {string} id The name of the callback. - * @param {function} callback The callback to be called. - */ -phpbb.addAjaxCallback = function(id, callback) { - if (typeof callback === 'function') { - phpbb.ajaxCallbacks[id] = callback; - } - return this; -}; - -/** - * This callback handles live member searches. - */ -phpbb.addAjaxCallback('member_search', function(res) { - phpbb.search.handleResponse(res, $(this), false, phpbb.getFunctionByName('phpbb.search.setValueOnClick')); -}); - -/** - * This callback alternates text - it replaces the current text with the text in - * the alt-text data attribute, and replaces the text in the attribute with the - * current text so that the process can be repeated. - */ -phpbb.addAjaxCallback('alt_text', function() { - var $anchor, - updateAll = $(this).data('update-all'), - altText; - - if (updateAll !== undefined && updateAll.length) { - $anchor = $(updateAll); - } else { - $anchor = $(this); - } - - $anchor.each(function() { - var $this = $(this); - altText = $this.attr('data-alt-text'); - $this.attr('data-alt-text', $.trim($this.text())); - $this.attr('title', altText); - $this.children('span').text(altText); - }); -}); - -/** - * This callback is based on the alt_text callback. - * - * It replaces the current text with the text in the alt-text data attribute, - * and replaces the text in the attribute with the current text so that the - * process can be repeated. - * Additionally it replaces the class of the link's parent - * and changes the link itself. - */ -phpbb.addAjaxCallback('toggle_link', function() { - var $anchor; - var updateAll = $(this).data('update-all'); - var toggleText; - var toggleUrl; - var toggleIcon; - - if (updateAll !== undefined && updateAll.length) { - $anchor = $(updateAll); - } else { - $anchor = $(this); - } - - $anchor.each(function() { - var $this = $(this); - - // Toggle link text - toggleText = $.trim($this.attr('data-toggle-text')); - $this.attr('data-toggle-text', $.trim($this.children('span').text())); - $this.attr('title', toggleText); - $this.children('span').last().text(toggleText); - - // Toggle link url - toggleUrl = $this.attr('data-toggle-url'); - $this.attr('data-toggle-url', $this.attr('href')); - $this.attr('href', toggleUrl); - - // Toggle Icon - $this.children().first().toggleClass('is-active').next().toggleClass('is-active') - }); -}); - -/** -* Automatically resize textarea -* -* This function automatically resizes textarea elements when user -* types text. -* -* @param {jQuery} $items jQuery object(s) to resize -* @param {object} [options] Optional parameter that adjusts default -* configuration. See configuration variable -* -* Optional parameters: -* minWindowHeight {number} Minimum browser window height when textareas are resized. Default = 500 -* minHeight {number} Minimum height of textarea. Default = 200 -* maxHeight {number} Maximum height of textarea. Default = 500 -* heightDiff {number} Minimum difference between window and textarea height. Default = 200 -* resizeCallback {function} Function to call after resizing textarea -* resetCallback {function} Function to call when resize has been canceled - -* Callback function format: function(item) {} -* this points to DOM object -* item is a jQuery object, same as this -*/ -phpbb.resizeTextArea = function($items, options) { - // Configuration - var configuration = { - minWindowHeight: 500, - minHeight: 200, - maxHeight: 500, - heightDiff: 200, - resizeCallback: function() {}, - resetCallback: function() {} }; - if (phpbb.isTouch) { - return; - } - - if (arguments.length > 1) { - configuration = $.extend(configuration, options); - } - - function resetAutoResize(item) { - var $item = $(item); - if ($item.hasClass('auto-resized')) { - $(item) - .css({ height: '', resize: '' }) - .removeClass('auto-resized'); - configuration.resetCallback.call(item, $item); - } - } - - function autoResize(item) { - function setHeight(height) { - height += parseInt($item.css('height'), 10) - $item.innerHeight(); - $item - .css({ height: height + 'px', resize: 'none' }) - .addClass('auto-resized'); - configuration.resizeCallback.call(item, $item); - } - - var windowHeight = $(window).height(); - - if (windowHeight < configuration.minWindowHeight) { - resetAutoResize(item); - return; - } - - var maxHeight = Math.min( - Math.max(windowHeight - configuration.heightDiff, configuration.minHeight), - configuration.maxHeight - ), - $item = $(item), - height = parseInt($item.innerHeight(), 10), - scrollHeight = (item.scrollHeight) ? item.scrollHeight : 0; - - if (height < 0) { - return; - } - - if (height > maxHeight) { - setHeight(maxHeight); - } else if (scrollHeight > (height + 5)) { - setHeight(Math.min(maxHeight, scrollHeight)); - } - } - - $items.on('focus change keyup', function() { - $(this).each(function() { - autoResize(this); - }); - }).change(); - - $(window).resize(function() { - $items.each(function() { - if ($(this).hasClass('auto-resized')) { - autoResize(this); - } - }); - }); -}; - -/** -* Check if cursor in textarea is currently inside a bbcode tag -* -* @param {object} textarea Textarea DOM object -* @param {Array} startTags List of start tags to look for -* For example, Array('[code]', '[code=') -* @param {Array} endTags List of end tags to look for -* For example, Array('[/code]') -* -* @returns {boolean} True if cursor is in bbcode tag -*/ -phpbb.inBBCodeTag = function(textarea, startTags, endTags) { - var start = textarea.selectionStart, - lastEnd = -1, - lastStart = -1, - i, index, value; - - if (typeof start !== 'number') { - return false; - } - - value = textarea.value.toLowerCase(); - - for (i = 0; i < startTags.length; i++) { - var tagLength = startTags[i].length; - if (start >= tagLength) { - index = value.lastIndexOf(startTags[i], start - tagLength); - lastStart = Math.max(lastStart, index); - } - } - if (lastStart === -1) { - return false; - } - - if (start > 0) { - for (i = 0; i < endTags.length; i++) { - index = value.lastIndexOf(endTags[i], start - 1); - lastEnd = Math.max(lastEnd, index); - } - } - - return (lastEnd < lastStart); -}; - - -/** -* Adjust textarea to manage code bbcode -* -* This function allows to use tab characters when typing code -* and keeps indentation of previous line of code when adding new -* line while typing code. -* -* Editor's functionality is changed only when cursor is between -* [code] and [/code] bbcode tags. -* -* @param {object} textarea Textarea DOM object to apply editor to -*/ -phpbb.applyCodeEditor = function(textarea) { - // list of allowed start and end bbcode code tags, in lower case - var startTags = ['[code]', '[code='], - startTagsEnd = ']', - endTags = ['[/code]']; - - if (!textarea || typeof textarea.selectionStart !== 'number') { - return; - } - - if ($(textarea).data('code-editor') === true) { - return; - } - - function inTag() { - return phpbb.inBBCodeTag(textarea, startTags, endTags); - } + /** + * Display the date/time select + */ + phpbb.timezoneEnableDateSelection = function() { + $('#tz_select_date').css('display', 'block'); + }; /** + * Preselect a date/time or suggest one, if it is not picked. + * + * @param {bool} forceSelector Shall we select the suggestion? + */ + phpbb.timezonePreselectSelect = function(forceSelector) { + // The offset returned here is in minutes and negated. + let offset = (new Date()).getTimezoneOffset(); + let sign = '-'; + + if (offset < 0) { + sign = '+'; + offset = -offset; + } + + let minutes = offset % 60; + let hours = (offset - minutes) / 60; + + if (hours === 0) { + hours = '00'; + sign = '+'; + } else if (hours < 10) { + hours = '0' + hours.toString(); + } else { + hours = hours.toString(); + } + + if (minutes < 10) { + minutes = '0' + minutes.toString(); + } else { + minutes = minutes.toString(); + } + + const prefix = 'UTC' + sign + hours + ':' + minutes; + const prefixLength = prefix.length; + const selectorOptions = $('option', '#tz_date'); + let i; + + const $tzSelectDateSuggest = $('#tz_select_date_suggest'); + + for (i = 0; i < selectorOptions.length; ++i) { + const option = selectorOptions[i]; + + if (option.value.substring(0, prefixLength) === prefix) { + if ($('#tz_date').val() !== option.value && !forceSelector) { + // We do not select the option for the user, but notify him, + // that we would suggest a different setting. + phpbb.timezoneSwitchDate(true); + $tzSelectDateSuggest.css('display', 'inline'); + } else { + option.selected = true; + phpbb.timezoneSwitchDate(!forceSelector); + $tzSelectDateSuggest.css('display', 'none'); + } + + const suggestion = $tzSelectDateSuggest.attr('data-l-suggestion'); + + $tzSelectDateSuggest.attr('title', suggestion.replace('%s', option.innerHTML)); + $tzSelectDateSuggest.attr('value', suggestion.replace('%s', option.innerHTML.substring(0, 9))); + $tzSelectDateSuggest.attr('data-suggested-tz', option.innerHTML); + + // Found the suggestion, there cannot be more, so return from here. + return; + } + } + }; + + phpbb.ajaxCallbacks = {}; + + /** + * Adds an AJAX callback to be used by phpbb.ajaxify. + * + * See the phpbb.ajaxify comments for information on stuff like parameters. + * + * @param {string} id The name of the callback. + * @param {function} callback The callback to be called. + */ + phpbb.addAjaxCallback = function(id, callback) { + if (typeof callback === 'function') { + phpbb.ajaxCallbacks[id] = callback; + } + + return this; + }; + + /** + * This callback handles live member searches. + */ + phpbb.addAjaxCallback('member_search', function(res) { + phpbb.search.handleResponse(res, $(this), false, phpbb.getFunctionByName('phpbb.search.setValueOnClick')); + }); + + /** + * This callback alternates text - it replaces the current text with the text in + * the alt-text data attribute, and replaces the text in the attribute with the + * current text so that the process can be repeated. + */ + phpbb.addAjaxCallback('alt_text', function() { + let $anchor; + const updateAll = $(this).data('update-all'); + let altText; + + if (updateAll !== undefined && updateAll.length) { + $anchor = $(updateAll); + } else { + $anchor = $(this); + } + + $anchor.each(function() { + const $this = $(this); + altText = $this.attr('data-alt-text'); + $this.attr('data-alt-text', $.trim($this.text())); + $this.attr('title', altText); + $this.children('span').text(altText); + }); + }); + + /** + * This callback is based on the alt_text callback. + * + * It replaces the current text with the text in the alt-text data attribute, + * and replaces the text in the attribute with the current text so that the + * process can be repeated. + * Additionally it replaces the class of the link's parent + * and changes the link itself. + */ + phpbb.addAjaxCallback('toggle_link', function() { + let $anchor; + const updateAll = $(this).data('update-all'); + let toggleText; + let toggleUrl; + + if (updateAll !== undefined && updateAll.length) { + $anchor = $(updateAll); + } else { + $anchor = $(this); + } + + $anchor.each(function() { + const $this = $(this); + + // Toggle link text + toggleText = $.trim($this.attr('data-toggle-text')); + $this.attr('data-toggle-text', $.trim($this.children('span').text())); + $this.attr('title', toggleText); + $this.children('span').last().text(toggleText); + + // Toggle link url + toggleUrl = $this.attr('data-toggle-url'); + $this.attr('data-toggle-url', $this.attr('href')); + $this.attr('href', toggleUrl); + + // Toggle Icon + $this.children().first().toggleClass('is-active').next().toggleClass('is-active'); + }); + }); + + /** + * Automatically resize textarea + * + * This function automatically resizes textarea elements when user + * types text. + * + * @param {jQuery} $items jQuery object(s) to resize + * @param {object} [options] Optional parameter that adjusts default + * configuration. See configuration variable + * + * Optional parameters: + * minWindowHeight {number} Minimum browser window height when textareas are resized. Default = 500 + * minHeight {number} Minimum height of textarea. Default = 200 + * maxHeight {number} Maximum height of textarea. Default = 500 + * heightDiff {number} Minimum difference between window and textarea height. Default = 200 + * resizeCallback {function} Function to call after resizing textarea + * resetCallback {function} Function to call when resize has been canceled + + * Callback function format: function(item) {} + * this points to DOM object + * item is a jQuery object, same as this + */ + phpbb.resizeTextArea = function($items, options) { + // Configuration + let configuration = { + minWindowHeight: 500, + minHeight: 200, + maxHeight: 500, + heightDiff: 200, + resizeCallback() {}, + resetCallback() {}, + }; + + if (phpbb.isTouch) { + return; + } + + if (arguments.length > 1) { + configuration = $.extend(configuration, options); + } + + function resetAutoResize(item) { + const $item = $(item); + if ($item.hasClass('auto-resized')) { + $(item) + .css({ height: '', resize: '' }) + .removeClass('auto-resized'); + configuration.resetCallback.call(item, $item); + } + } + + function autoResize(item) { + function setHeight(height) { + height += parseInt($item.css('height'), 10) - $item.innerHeight(); + $item + .css({ height: height + 'px', resize: 'none' }) + .addClass('auto-resized'); + configuration.resizeCallback.call(item, $item); + } + + const windowHeight = $(window).height(); + + if (windowHeight < configuration.minWindowHeight) { + resetAutoResize(item); + return; + } + + const maxHeight = Math.min( + Math.max(windowHeight - configuration.heightDiff, configuration.minHeight), + configuration.maxHeight, + ); + const $item = $(item); + const height = parseInt($item.innerHeight(), 10); + const scrollHeight = (item.scrollHeight) ? item.scrollHeight : 0; + + if (height < 0) { + return; + } + + if (height > maxHeight) { + setHeight(maxHeight); + } else if (scrollHeight > (height + 5)) { + setHeight(Math.min(maxHeight, scrollHeight)); + } + } + + $items.on('focus change keyup', function() { + $(this).each(function() { + autoResize(this); + }); + }).change(); + + $(window).resize(() => { + $items.each(function() { + if ($(this).hasClass('auto-resized')) { + autoResize(this); + } + }); + }); + }; + + /** + * Check if cursor in textarea is currently inside a bbcode tag + * + * @param {object} textarea Textarea DOM object + * @param {Array} startTags List of start tags to look for + * For example, Array('[code]', '[code=') + * @param {Array} endTags List of end tags to look for + * For example, Array('[/code]') + * + * @returns {boolean} True if cursor is in bbcode tag + */ + phpbb.inBBCodeTag = function(textarea, startTags, endTags) { + const start = textarea.selectionStart; + let lastEnd = -1; + let lastStart = -1; + let i; + let index; + + if (typeof start !== 'number') { + return false; + } + + const value = textarea.value.toLowerCase(); + + for (i = 0; i < startTags.length; i++) { + const tagLength = startTags[i].length; + if (start >= tagLength) { + index = value.lastIndexOf(startTags[i], start - tagLength); + lastStart = Math.max(lastStart, index); + } + } + + if (lastStart === -1) { + return false; + } + + if (start > 0) { + for (i = 0; i < endTags.length; i++) { + index = value.lastIndexOf(endTags[i], start - 1); + lastEnd = Math.max(lastEnd, index); + } + } + + return (lastEnd < lastStart); + }; + + /** + * Adjust textarea to manage code bbcode + * + * This function allows to use tab characters when typing code + * and keeps indentation of previous line of code when adding new + * line while typing code. + * + * Editor's functionality is changed only when cursor is between + * [code] and [/code] bbcode tags. + * + * @param {object} textarea Textarea DOM object to apply editor to + */ + phpbb.applyCodeEditor = function(textarea) { + // list of allowed start and end bbcode code tags, in lower case + const startTags = [ '[code]', '[code=' ]; + const startTagsEnd = ']'; + const endTags = [ '[/code]' ]; + + if (!textarea || typeof textarea.selectionStart !== 'number') { + return; + } + + if ($(textarea).data('code-editor') === true) { + return; + } + + function inTag() { + return phpbb.inBBCodeTag(textarea, startTags, endTags); + } + + /** * Get line of text before cursor * * @param {boolean} stripCodeStart If true, only part of line @@ -1267,244 +1274,249 @@ phpbb.applyCodeEditor = function(textarea) { * * @returns {string} Line of text */ - function getLastLine(stripCodeStart) { - var start = textarea.selectionStart, - value = textarea.value, - index = value.lastIndexOf('\n', start - 1); + function getLastLine(stripCodeStart) { + const start = textarea.selectionStart; + let { value } = textarea; + let index = value.lastIndexOf('\n', start - 1); - value = value.substring(index + 1, start); + value = value.substring(index + 1, start); - if (stripCodeStart) { - for (var i = 0; i < startTags.length; i++) { - index = value.lastIndexOf(startTags[i]); - if (index >= 0) { - var tagLength = startTags[i].length; + if (stripCodeStart) { + for (let i = 0; i < startTags.length; i++) { + index = value.lastIndexOf(startTags[i]); + if (index >= 0) { + const tagLength = startTags[i].length; - value = value.substring(index + tagLength); - if (startTags[i].lastIndexOf(startTagsEnd) !== tagLength) { - index = value.indexOf(startTagsEnd); + value = value.substring(index + tagLength); + if (startTags[i].lastIndexOf(startTagsEnd) !== tagLength) { + index = value.indexOf(startTagsEnd); - if (index >= 0) { - value = value.substr(index + 1); + // eslint-disable-next-line max-depth + if (index >= 0) { + value = value.substr(index + 1); + } } } } } + + return value; } - return value; - } - - /** + /** * Append text at cursor position * * @param {string} text Text to append */ - function appendText(text) { - var start = textarea.selectionStart, - end = textarea.selectionEnd, - value = textarea.value; + function appendText(text) { + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + const { value } = textarea; - textarea.value = value.substr(0, start) + text + value.substr(end); - textarea.selectionStart = textarea.selectionEnd = start + text.length; - } - - $(textarea).data('code-editor', true).on('keydown', function(event) { - var key = event.keyCode || event.which; - - // intercept tabs - if (key === keymap.TAB && - !event.ctrlKey && - !event.shiftKey && - !event.altKey && - !event.metaKey) { - if (inTag()) { - appendText('\t'); - event.preventDefault(); - return; - } + textarea.value = value.substr(0, start) + text + value.substr(end); + textarea.selectionStart = start + text.length; + textarea.selectionEnd = textarea.selectionStart; } - // intercept new line characters - if (key === keymap.ENTER) { - if (inTag()) { - var lastLine = getLastLine(true), - code = '' + /^\s*/g.exec(lastLine); + $(textarea).data('code-editor', true).on('keydown', event => { + const key = event.keyCode || event.which; - if (code.length > 0) { - appendText('\n' + code); + // intercept tabs + if (key === keymap.TAB + && !event.ctrlKey + && !event.shiftKey + && !event.altKey + && !event.metaKey) { + if (inTag()) { + appendText('\t'); event.preventDefault(); + return; } } - } - }); -}; -/** - * Show drag and drop animation when textarea is present - * - * This function will enable the drag and drop animation for a specified - * textarea. - * - * @param {HTMLElement} textarea Textarea DOM object to apply editor to - */ -phpbb.showDragNDrop = function(textarea) { - if (!textarea) { - return; - } + // intercept new line characters + if (key === keymap.ENTER) { + if (inTag()) { + const lastLine = getLastLine(true); + const code = String(/^\s*/g.exec(lastLine)); - $('body').on('dragenter dragover', function () { - $(textarea).addClass('drag-n-drop'); - }).on('dragleave dragout dragend drop', function() { - $(textarea).removeClass('drag-n-drop'); - }); - $(textarea).on('dragenter dragover', function () { - $(textarea).addClass('drag-n-drop-highlight'); - }).on('dragleave dragout dragend drop', function() { - $(textarea).removeClass('drag-n-drop-highlight'); - }); -}; - -/** -* List of classes that toggle dropdown menu, -* list of classes that contain visible dropdown menu -* -* Add your own classes to strings with comma (probably you -* will never need to do that) -*/ -phpbb.dropdownHandles = '.dropdown-container.dropdown-visible .dropdown-toggle'; -phpbb.dropdownVisibleContainers = '.dropdown-container.dropdown-visible'; - -/** -* Dropdown toggle event handler -* This handler is used by phpBB.registerDropdown() and other functions -*/ -phpbb.toggleDropdown = function(event_) { - var $this = $(this), - options = $this.data('dropdown-options'), - parent = options.parent, - visible = parent.hasClass('dropdown-visible'), - direction; - - if (!visible) { - // Prevent link default action - event_.preventDefault(); - event_.stopPropagation(); - // Hide other dropdown menus - $(phpbb.dropdownHandles).each(phpbb.toggleDropdown); - - // Figure out direction of dropdown - direction = options.direction; - var verticalDirection = options.verticalDirection, - offset = $this.offset(); - - if (direction === 'auto') { - if (($(window).width() - $this.outerWidth(true)) / 2 > offset.left) { - direction = 'right'; - } else { - direction = 'left'; + if (code.length > 0) { + appendText('\n' + code); + event.preventDefault(); + } + } } - } - parent.toggleClass(options.leftClass, direction === 'left') - .toggleClass(options.rightClass, direction === 'right'); - - if (verticalDirection === 'auto') { - var height = $(window).height(), - top = offset.top - $(window).scrollTop(); - - verticalDirection = (top < height * 0.7) ? 'down' : 'up'; - } - parent.toggleClass(options.upClass, verticalDirection === 'up') - .toggleClass(options.downClass, verticalDirection === 'down'); - } - - options.dropdown.toggle(); - parent.toggleClass(options.visibleClass, !visible) - .toggleClass('dropdown-visible', !visible); - - // Check dimensions when showing dropdown - // !visible because variable shows state of dropdown before it was toggled - if (!visible) { - var windowWidth = $(window).width(); - - options.dropdown.find('.dropdown-contents').each(function() { - var $this = $(this); - - $this.css({ - marginLeft: 0, - left: 0, - marginRight: 0, - maxWidth: (windowWidth - 4) + 'px' - }); - - var offset = $this.offset().left, - width = $this.outerWidth(true); - - if (offset < 2) { - $this.css('left', (2 - offset) + 'px'); - } else if ((offset + width + 2) > windowWidth) { - $this.css('margin-left', (windowWidth - offset - width - 2) + 'px'); - } - - // Check whether the vertical scrollbar is present. - $this.toggleClass('dropdown-nonscroll', this.scrollHeight === $this.innerHeight()); - }); - var freeSpace = parent.offset().left - 4; + }; - if (direction === 'left') { - options.dropdown.css('margin-left', '-' + freeSpace + 'px'); - - // Try to position the notification dropdown correctly in RTL-responsive mode - if (options.dropdown.hasClass('dropdown-extended')) { - var contentWidth, - fullFreeSpace = freeSpace + parent.outerWidth(); - - options.dropdown.find('.dropdown-contents').each(function() { - contentWidth = parseInt($(this).outerWidth(), 10); - $(this).css({ marginLeft: 0, left: 0 }); - }); - - var maxOffset = Math.min(contentWidth, fullFreeSpace) + 'px'; - options.dropdown.css({ - width: maxOffset, - marginLeft: -maxOffset - }); - } - } else { - options.dropdown.css('margin-right', '-' + (windowWidth + freeSpace) + 'px'); + /** + * Show drag and drop animation when textarea is present + * + * This function will enable the drag and drop animation for a specified + * textarea. + * + * @param {HTMLElement} textarea Textarea DOM object to apply editor to + */ + phpbb.showDragNDrop = function(textarea) { + if (!textarea) { + return; } - } - // Prevent event propagation - if (arguments.length > 0) { - try { - var e = arguments[0]; - e.preventDefault(); - e.stopPropagation(); - } catch (error) { } - } - return false; -}; + $('body').on('dragenter dragover', () => { + $(textarea).addClass('drag-n-drop'); + }).on('dragleave dragout dragend drop', () => { + $(textarea).removeClass('drag-n-drop'); + }); + $(textarea).on('dragenter dragover', () => { + $(textarea).addClass('drag-n-drop-highlight'); + }).on('dragleave dragout dragend drop', () => { + $(textarea).removeClass('drag-n-drop-highlight'); + }); + }; -/** -* Toggle dropdown submenu -*/ -phpbb.toggleSubmenu = function(e) { - $(this).siblings('.dropdown-submenu').toggle(); - e.preventDefault(); -}; + /** + * List of classes that toggle dropdown menu, + * list of classes that contain visible dropdown menu + * + * Add your own classes to strings with comma (probably you + * will never need to do that) + */ + phpbb.dropdownHandles = '.dropdown-container.dropdown-visible .dropdown-toggle'; + phpbb.dropdownVisibleContainers = '.dropdown-container.dropdown-visible'; -/** -* Register dropdown menu -* Shows/hides dropdown, decides which side to open to -* -* @param {jQuery} toggle Link that toggles dropdown. -* @param {jQuery} dropdown Dropdown menu. -* @param {Object} options List of options. Optional. -*/ -phpbb.registerDropdown = function(toggle, dropdown, options) { - var ops = { + /** + * Dropdown toggle event handler + * This handler is used by phpBB.registerDropdown() and other functions + */ + phpbb.toggleDropdown = function(event_) { + const $this = $(this); + const options = $this.data('dropdown-options'); + const { parent } = options; + const visible = parent.hasClass('dropdown-visible'); + let direction; + + if (!visible) { + // Prevent link default action + event_.preventDefault(); + event_.stopPropagation(); + // Hide other dropdown menus + $(phpbb.dropdownHandles).each(phpbb.toggleDropdown); + + // Figure out direction of dropdown + direction = options.direction; + let { verticalDirection } = options; + const offset = $this.offset(); + + if (direction === 'auto') { + if (($(window).width() - $this.outerWidth(true)) / 2 > offset.left) { + direction = 'right'; + } else { + direction = 'left'; + } + } + + parent.toggleClass(options.leftClass, direction === 'left') + .toggleClass(options.rightClass, direction === 'right'); + + if (verticalDirection === 'auto') { + const height = $(window).height(); + const top = offset.top - $(window).scrollTop(); + + verticalDirection = (top < height * 0.7) ? 'down' : 'up'; + } + + parent.toggleClass(options.upClass, verticalDirection === 'up') + .toggleClass(options.downClass, verticalDirection === 'down'); + } + + options.dropdown.toggle(); + parent.toggleClass(options.visibleClass, !visible) + .toggleClass('dropdown-visible', !visible); + + // Check dimensions when showing dropdown + // !visible because variable shows state of dropdown before it was toggled + if (!visible) { + const windowWidth = $(window).width(); + + options.dropdown.find('.dropdown-contents').each(function() { + const $this = $(this); + + $this.css({ + marginLeft: 0, + left: 0, + marginRight: 0, + maxWidth: (windowWidth - 4) + 'px', + }); + + const offset = $this.offset().left; + const width = $this.outerWidth(true); + + if (offset < 2) { + $this.css('left', (2 - offset) + 'px'); + } else if ((offset + width + 2) > windowWidth) { + $this.css('margin-left', (windowWidth - offset - width - 2) + 'px'); + } + + // Check whether the vertical scrollbar is present. + $this.toggleClass('dropdown-nonscroll', this.scrollHeight === $this.innerHeight()); + }); + const freeSpace = parent.offset().left - 4; + + if (direction === 'left') { + options.dropdown.css('margin-left', '-' + freeSpace + 'px'); + + // Try to position the notification dropdown correctly in RTL-responsive mode + if (options.dropdown.hasClass('dropdown-extended')) { + let contentWidth; + const fullFreeSpace = freeSpace + parent.outerWidth(); + + options.dropdown.find('.dropdown-contents').each(function() { + contentWidth = parseInt($(this).outerWidth(), 10); + $(this).css({ marginLeft: 0, left: 0 }); + }); + + const maxOffset = Math.min(contentWidth, fullFreeSpace) + 'px'; + options.dropdown.css({ + width: maxOffset, + marginLeft: -maxOffset, + }); + } + } else { + options.dropdown.css('margin-right', '-' + (windowWidth + freeSpace) + 'px'); + } + } + + // Prevent event propagation + if (arguments.length > 0) { + try { + // eslint-disable-next-line prefer-rest-params + const e = arguments[0]; + e.preventDefault(); + e.stopPropagation(); + } catch { } + } + + return false; + }; + + /** + * Toggle dropdown submenu + */ + phpbb.toggleSubmenu = function(e) { + $(this).siblings('.dropdown-submenu').toggle(); + e.preventDefault(); + }; + + /** + * Register dropdown menu + * Shows/hides dropdown, decides which side to open to + * + * @param {jQuery} toggle Link that toggles dropdown. + * @param {jQuery} dropdown Dropdown menu. + * @param {Object} options List of options. Optional. + */ + phpbb.registerDropdown = function(toggle, dropdown, options) { + let ops = { parent: toggle.parent(), // Parent item to add classes to direction: 'auto', // Direction of dropdown menu. Possible values: auto, left, right verticalDirection: 'auto', // Vertical direction. Possible values: auto, up, down @@ -1512,401 +1524,410 @@ phpbb.registerDropdown = function(toggle, dropdown, options) { leftClass: 'dropdown-left', // Class to add to parent item when dropdown opens to left side rightClass: 'dropdown-right', // Class to add to parent item when dropdown opens to right side upClass: 'dropdown-up', // Class to add to parent item when dropdown opens above menu item - downClass: 'dropdown-down' // Class to add to parent item when dropdown opens below menu item + downClass: 'dropdown-down', // Class to add to parent item when dropdown opens below menu item }; - if (options) { - ops = $.extend(ops, options); - } - ops.dropdown = dropdown; - - ops.parent.addClass('dropdown-container'); - toggle.addClass('dropdown-toggle'); - - toggle.data('dropdown-options', ops); - - toggle.click(phpbb.toggleDropdown); - $('.dropdown-toggle-submenu', ops.parent).click(phpbb.toggleSubmenu); -}; - -/** -* Get the HTML for a color palette table. -* -* @param {string} dir Palette direction - either v or h -* @param {int} width Palette cell width. -* @param {int} height Palette cell height. -*/ -phpbb.colorPalette = function(dir, width, height) { - var r, g, b, - numberList = new Array(6), - color = '', - html = ''; - - numberList[0] = '00'; - numberList[1] = '40'; - numberList[2] = '80'; - numberList[3] = 'BF'; - numberList[4] = 'FF'; - - var tableClass = (dir === 'h') ? 'horizontal-palette' : 'vertical-palette'; - html += ''; - - for (r = 0; r < 5; r++) { - if (dir === 'h') { - html += ''; + if (options) { + ops = $.extend(ops, options); } - for (g = 0; g < 5; g++) { - if (dir === 'v') { + ops.dropdown = dropdown; + + ops.parent.addClass('dropdown-container'); + toggle.addClass('dropdown-toggle'); + + toggle.data('dropdown-options', ops); + + toggle.click(phpbb.toggleDropdown); + $('.dropdown-toggle-submenu', ops.parent).click(phpbb.toggleSubmenu); + }; + + /** + * Get the HTML for a color palette table. + * + * @param {string} dir Palette direction - either v or h + * @param {int} width Palette cell width. + * @param {int} height Palette cell height. + */ + phpbb.colorPalette = function(dir, width, height) { + let r; + let g; + let b; + const numberList = new Array(6); + let color = ''; + let html = ''; + + numberList[0] = '00'; + numberList[1] = '40'; + numberList[2] = '80'; + numberList[3] = 'BF'; + numberList[4] = 'FF'; + + const tableClass = (dir === 'h') ? 'horizontal-palette' : 'vertical-palette'; + html += '
'; + + for (r = 0; r < 5; r++) { + if (dir === 'h') { html += ''; } - for (b = 0; b < 5; b++) { - color = '' + numberList[r] + numberList[g] + numberList[b]; - html += ''; + for (g = 0; g < 5; g++) { + if (dir === 'v') { + html += ''; + } + + for (b = 0; b < 5; b++) { + color = String(numberList[r]) + numberList[g] + numberList[b]; + html += ''; + } + + if (dir === 'v') { + html += ''; + } } - if (dir === 'v') { + if (dir === 'h') { html += ''; } } - if (dir === 'h') { - html += ''; + html += '
'; - html += '
'; + html += '
'; + return html; + }; + + /** + * Register a color palette. + * + * @param {jQuery} el jQuery object for the palette container. + */ + phpbb.registerPalette = function(el) { + const orientation = el.attr('data-color-palette') || el.attr('data-orientation'); // data-orientation kept for backwards compat. + const height = el.attr('data-height'); + const width = el.attr('data-width'); + const target = el.attr('data-target'); + const bbcode = el.attr('data-bbcode'); + + // Insert the palette HTML into the container. + el.html(phpbb.colorPalette(orientation, width, height)); + + // Add toggle control. + $('#color_palette_toggle').click(e => { + el.toggle(); + e.preventDefault(); + }); + + // Attach event handler when a palette cell is clicked. + $(el).on('click', 'a', function(e) { + const color = $(this).attr('data-color'); + + if (bbcode) { + bbfontstyle('[color=#' + color + ']', '[/color]'); + } else { + $(target).val(color); + } + + e.preventDefault(); + }); + }; + + /** + * Set display of page element + * + * @param {string} id The ID of the element to change + * @param {int} action Set to 0 if element display should be toggled, -1 for + * hiding the element, and 1 for showing it. + * @param {string} type Display type that should be used, e.g. inline, block or + * other CSS "display" types + */ + phpbb.toggleDisplay = function(id, action, type) { + if (!type) { + type = 'block'; } - } - html += ''; - return html; -}; -/** -* Register a color palette. -* -* @param {jQuery} el jQuery object for the palette container. -*/ -phpbb.registerPalette = function(el) { - var orientation = el.attr('data-color-palette') || el.attr('data-orientation'), // data-orientation kept for backwards compat. - height = el.attr('data-height'), - width = el.attr('data-width'), - target = el.attr('data-target'), - bbcode = el.attr('data-bbcode'); + const $element = $('#' + id); - // Insert the palette HTML into the container. - el.html(phpbb.colorPalette(orientation, width, height)); - - // Add toggle control. - $('#color_palette_toggle').click(function(e) { - el.toggle(); - e.preventDefault(); - }); - - // Attach event handler when a palette cell is clicked. - $(el).on('click', 'a', function(e) { - var color = $(this).attr('data-color'); - - if (bbcode) { - bbfontstyle('[color=#' + color + ']', '[/color]'); - } else { - $(target).val(color); + const display = $element.css('display'); + if (!action) { + action = (display === '' || display === type) ? -1 : 1; } - e.preventDefault(); - }); -}; -/** -* Set display of page element -* -* @param {string} id The ID of the element to change -* @param {int} action Set to 0 if element display should be toggled, -1 for -* hiding the element, and 1 for showing it. -* @param {string} type Display type that should be used, e.g. inline, block or -* other CSS "display" types -*/ -phpbb.toggleDisplay = function(id, action, type) { - if (!type) { - type = 'block'; - } + $element.css('display', ((action === 1) ? type : 'none')); + }; - var $element = $('#' + id); + /** + * Toggle additional settings based on the selected + * option of select element. + * + * @param {jQuery} el jQuery select element object. + */ + phpbb.toggleSelectSettings = function(el) { + el.children().each(function() { + const $this = $(this); + const $setting = $($this.data('toggle-setting')); + $setting.toggle($this.is(':selected')); - var display = $element.css('display'); - if (!action) { - action = (display === '' || display === type) ? -1 : 1; - } - $element.css('display', ((action === 1) ? type : 'none')); -}; + // Disable any input elements that are not visible right now + if ($this.is(':selected')) { + $($this.data('toggle-setting') + ' input').prop('disabled', false); + } else { + $($this.data('toggle-setting') + ' input').prop('disabled', true); + } + }); + }; -/** -* Toggle additional settings based on the selected -* option of select element. -* -* @param {jQuery} el jQuery select element object. -*/ -phpbb.toggleSelectSettings = function(el) { - el.children().each(function() { - var $this = $(this), - $setting = $($this.data('toggle-setting')); - $setting.toggle($this.is(':selected')); + /** + * Get function from name. + * Based on http://stackoverflow.com/a/359910 + * + * @param {string} functionName Function to get. + * @returns function + */ + phpbb.getFunctionByName = function(functionName) { + const namespaces = functionName.split('.'); + const func = namespaces.pop(); + let context = window; - // Disable any input elements that are not visible right now - if ($this.is(':selected')) { - $($this.data('toggle-setting') + ' input').prop('disabled', false); - } else { - $($this.data('toggle-setting') + ' input').prop('disabled', true); + for (let i = 0; i < namespaces.length; i++) { + context = context[namespaces[i]]; } - }); -}; -/** -* Get function from name. -* Based on http://stackoverflow.com/a/359910 -* -* @param {string} functionName Function to get. -* @returns function -*/ -phpbb.getFunctionByName = function (functionName) { - var namespaces = functionName.split('.'), - func = namespaces.pop(), - context = window; + return context[func]; + }; - for (var i = 0; i < namespaces.length; i++) { - context = context[namespaces[i]]; - } - return context[func]; -}; + /** + * Convert raw key ArrayBuffer to base64 string. + * + * @param {ArrayBuffer} rawKey Raw key array buffer as exported by SubtleCrypto exportKey() + * @returns {string} Base64 encoded raw key string + */ + phpbb.rawKeyToBase64 = rawKey => { + const keyBuffer = new Uint8Array(rawKey); + let keyText = ''; + const keyLength = keyBuffer.byteLength; + for (let i = 0; i < keyLength; i++) { + keyText += String.fromCharCode(keyBuffer[i]); + } -/** - * Convert raw key ArrayBuffer to base64 string. - * - * @param {ArrayBuffer} rawKey Raw key array buffer as exported by SubtleCrypto exportKey() - * @returns {string} Base64 encoded raw key string - */ -phpbb.rawKeyToBase64 = (rawKey) => { - const keyBuffer = new Uint8Array(rawKey); - let keyText = ''; - const keyLength = keyBuffer.byteLength; - for (let i = 0; i < keyLength; i++) { - keyText += String.fromCharCode(keyBuffer[i]); - } + return window.btoa(keyText); + }; - return window.btoa(keyText); -}; + /** + * Base64URL encode base64 encoded string + * + * @param {string} base64String Base64 encoded string + * @returns {string} Base64URL encoded string + */ + phpbb.base64UrlEncode = base64String => base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); -/** - * Base64URL encode base64 encoded string - * - * @param {string} base64String Base64 encoded string - * @returns {string} Base64URL encoded string - */ -phpbb.base64UrlEncode = (base64String) => { - return base64String.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); -}; + /** + * Register page dropdowns. + */ + phpbb.registerPageDropdowns = function() { + const $body = $('body'); -/** -* Register page dropdowns. -*/ -phpbb.registerPageDropdowns = function() { - var $body = $('body'); - - $body.find('.dropdown-container').each(function() { - var $this = $(this), - $trigger = $this.find('.dropdown-trigger:first'), - $contents = $this.find('.dropdown'), - options = { + $body.find('.dropdown-container').each(function() { + const $this = $(this); + let $trigger = $this.find('.dropdown-trigger:first'); + let $contents = $this.find('.dropdown'); + const options = { direction: 'auto', - verticalDirection: 'auto' - }, - data; + verticalDirection: 'auto', + }; + let data; - if (!$trigger.length) { - data = $this.attr('data-dropdown-trigger'); - $trigger = data ? $this.children(data) : $this.children('a:first'); + if (!$trigger.length) { + data = $this.attr('data-dropdown-trigger'); + $trigger = data ? $this.children(data) : $this.children('a:first'); + } + + if (!$contents.length) { + data = $this.attr('data-dropdown-contents'); + $contents = data ? $this.children(data) : $this.children('div:first'); + } + + if (!$trigger.length || !$contents.length) { + return; + } + + if ($this.hasClass('dropdown-up')) { + options.verticalDirection = 'up'; + } + + if ($this.hasClass('dropdown-down')) { + options.verticalDirection = 'down'; + } + + if ($this.hasClass('dropdown-left')) { + options.direction = 'left'; + } + + if ($this.hasClass('dropdown-right')) { + options.direction = 'right'; + } + + phpbb.registerDropdown($trigger, $contents, options); + }); + + // Hide active dropdowns when click event happens outside + $body.click(e => { + const $parents = $(e.target).parents(); + if (!$parents.is(phpbb.dropdownVisibleContainers)) { + $(phpbb.dropdownHandles).each(phpbb.toggleDropdown); + } + }); + }; + + /** + * Handle avatars to be lazy loaded. + */ + phpbb.lazyLoadAvatars = () => { + $('.avatar[data-src]').each(function() { + const $avatar = $(this); + + $avatar + .attr('src', $avatar.data('src')) + .removeAttr('data-src'); + }); + }; + + /** + * Get editor text area element + * + * @param {string} formName Name of form + * @param {string} textareaName Textarea name + * + * @return {HTMLElement|null} Text area element or null if textarea couldn't be found + */ + phpbb.getEditorTextArea = function(formName, textareaName) { + let doc; + + // find textarea, make sure browser supports necessary functions + if (document.forms[formName]) { + doc = document; + } else { + doc = opener.document; } - if (!$contents.length) { - data = $this.attr('data-dropdown-contents'); - $contents = data ? $this.children(data) : $this.children('div:first'); - } - - if (!$trigger.length || !$contents.length) { + if (!doc.forms[formName]) { return; } - if ($this.hasClass('dropdown-up')) { - options.verticalDirection = 'up'; - } - if ($this.hasClass('dropdown-down')) { - options.verticalDirection = 'down'; - } - if ($this.hasClass('dropdown-left')) { - options.direction = 'left'; - } - if ($this.hasClass('dropdown-right')) { - options.direction = 'right'; - } + return doc.forms[formName].elements[textareaName]; + }; - phpbb.registerDropdown($trigger, $contents, options); - }); + phpbb.recaptcha = { + button: null, + ready: false, - // Hide active dropdowns when click event happens outside - $body.click(function(e) { - var $parents = $(e.target).parents(); - if (!$parents.is(phpbb.dropdownVisibleContainers)) { - $(phpbb.dropdownHandles).each(phpbb.toggleDropdown); - } - }); -}; + token: $('input[name="recaptcha_token"]'), + form: $('.g-recaptcha').parents('form'), + v3: $('[data-recaptcha-v3]'), -/** - * Handle avatars to be lazy loaded. - */ -phpbb.lazyLoadAvatars = function loadAvatars() { - $('.avatar[data-src]').each(function () { - var $avatar = $(this); - - $avatar - .attr('src', $avatar.data('src')) - .removeAttr('data-src'); - }); -}; - -/** - * Get editor text area element - * - * @param {string} formName Name of form - * @param {string} textareaName Textarea name - * - * @return {HTMLElement|null} Text area element or null if textarea couldn't be found - */ -phpbb.getEditorTextArea = function(formName, textareaName) { - let doc; - - // find textarea, make sure browser supports necessary functions - if (document.forms[formName]) { - doc = document; - } else { - doc = opener.document; - } - - if (!doc.forms[formName]) { - return; - } - - return doc.forms[formName].elements[textareaName]; -} - -phpbb.recaptcha = { - button: null, - ready: false, - - token: $('input[name="recaptcha_token"]'), - form: $('.g-recaptcha').parents('form'), - v3: $('[data-recaptcha-v3]'), - - load: function() { - phpbb.recaptcha.bindButton(); - phpbb.recaptcha.bindForm(); - }, - bindButton: function() { - phpbb.recaptcha.form.find('input[type="submit"]').on('click', function() { + load() { + phpbb.recaptcha.bindButton(); + phpbb.recaptcha.bindForm(); + }, + bindButton() { + phpbb.recaptcha.form.find('input[type="submit"]').on('click', function() { // Listen to all the submit buttons for the form that has reCAPTCHA protection, // and store it so we can click the exact same button later on when we are ready. - phpbb.recaptcha.button = this; - }); - }, - bindForm: function() { - phpbb.recaptcha.form.on('submit', function(e) { - // If ready is false, it means the user pressed a submit button. - // And the form was not submitted by us, after the token was loaded. - if (!phpbb.recaptcha.ready) { - // If version 3 is used, we need to make a different execution, - // including the action and the site key. - if (phpbb.recaptcha.v3.length) { - grecaptcha.execute( - phpbb.recaptcha.v3.data('recaptcha-v3'), - {action: phpbb.recaptcha.v3.val()} - ).then(function(token) { + phpbb.recaptcha.button = this; + }); + }, + bindForm() { + phpbb.recaptcha.form.on('submit', e => { + // If ready is false, it means the user pressed a submit button. + // And the form was not submitted by us, after the token was loaded. + if (!phpbb.recaptcha.ready) { + // If version 3 is used, we need to make a different execution, + // including the action and the site key. + if (phpbb.recaptcha.v3.length) { + // eslint-disable-next-line no-undef + grecaptcha.execute( + phpbb.recaptcha.v3.data('recaptcha-v3'), + { action: phpbb.recaptcha.v3.val() }, + ).then(token => { // Place the token inside the form - phpbb.recaptcha.token.val(token); + phpbb.recaptcha.token.val(token); - // And now we submit the form. - phpbb.recaptcha.submitForm(); - }); - } else { + // And now we submit the form. + phpbb.recaptcha.submitForm(); + }); + } else { // Regular version 2 execution - grecaptcha.execute(); - } + // eslint-disable-next-line no-undef + grecaptcha.execute(); + } - // Do not submit the form - e.preventDefault(); - } - }); - }, - submitForm: function() { + // Do not submit the form + e.preventDefault(); + } + }); + }, + submitForm() { // Now we are ready, so set it to true. // so the 'submit' event doesn't run multiple times. - phpbb.recaptcha.ready = true; + phpbb.recaptcha.ready = true; - if (phpbb.recaptcha.button) { + if (phpbb.recaptcha.button) { // If there was a specific button pressed initially, trigger the same button - phpbb.recaptcha.button.click(); - } else { - if (typeof phpbb.recaptcha.form.submit !== 'function') { + phpbb.recaptcha.button.click(); + } else { + if (typeof phpbb.recaptcha.form.submit !== 'function') { // Rename input[name="submit"] so that we can submit the form - phpbb.recaptcha.form.submit.name = 'submit_btn'; + phpbb.recaptcha.form.submit.name = 'submit_btn'; + } + + phpbb.recaptcha.form.submit(); } + }, + }; - phpbb.recaptcha.form.submit(); - } - } -}; - -// reCAPTCHA v2 doesn't accept callback functions nested inside objects -// so we need to make this helper functions here -window.phpbbRecaptchaOnLoad = function() { - phpbb.recaptcha.load(); -}; - -window.phpbbRecaptchaOnSubmit = function() { - phpbb.recaptcha.submitForm(); -}; - -$(window).on('load', phpbb.lazyLoadAvatars); - -/** -* Apply code editor to all textarea elements with data-bbcode attribute -*/ -$(function() { - // reCAPTCHA v3 needs to be initialized - if (phpbb.recaptcha.v3.length) { + // reCAPTCHA v2 doesn't accept callback functions nested inside objects + // so we need to make this helper functions here + window.phpbbRecaptchaOnLoad = function() { phpbb.recaptcha.load(); - } + }; - $('textarea[data-bbcode]').each(function() { - phpbb.applyCodeEditor(this); - }); + window.phpbbRecaptchaOnSubmit = function() { + phpbb.recaptcha.submitForm(); + }; - phpbb.registerPageDropdowns(); + $(window).on('load', phpbb.lazyLoadAvatars); - $('[data-color-palette], [data-orientation]').each(function() { - phpbb.registerPalette($(this)); - }); + /** + * Apply code editor to all textarea elements with data-bbcode attribute + */ + $(() => { + // reCAPTCHA v3 needs to be initialized + if (phpbb.recaptcha.v3.length) { + phpbb.recaptcha.load(); + } - // Update browser history URL to point to specific post in viewtopic.php - // when using view=unread#unread link. - phpbb.history.replaceUrl($('#unread[data-url]').data('url')); + $('textarea[data-bbcode]').each(function() { + phpbb.applyCodeEditor(this); + }); - // Hide settings that are not selected via select element. - $('select[data-togglable-settings]').each(function() { - var $this = $(this); + phpbb.registerPageDropdowns(); - $this.change(function() { + $('[data-color-palette], [data-orientation]').each(function() { + phpbb.registerPalette($(this)); + }); + + // Update browser history URL to point to specific post in viewtopic.php + // when using view=unread#unread link. + phpbb.history.replaceUrl($('#unread[data-url]').data('url')); + + // Hide settings that are not selected via select element. + $('select[data-togglable-settings]').each(function() { + const $this = $(this); + + $this.change(() => { + phpbb.toggleSelectSettings($this); + }); phpbb.toggleSelectSettings($this); }); - phpbb.toggleSelectSettings($this); }); -}); - })(jQuery); // Avoid conflicts with other libraries diff --git a/phpBB/assets/javascript/editor.js b/phpBB/assets/javascript/editor.js index 3b86ca380d..72a982db42 100644 --- a/phpBB/assets/javascript/editor.js +++ b/phpBB/assets/javascript/editor.js @@ -1,4 +1,7 @@ /* global phpbb */ +/* eslint camelcase: 0 */ +/* eslint no-undef: 0 */ +/* eslint no-unused-vars: 0 */ /** * bbCode control by subBlue design [ www.subBlue.com ] @@ -6,25 +9,25 @@ */ // Startup variables -var imageTag = false; -var theSelection = false; -var bbcodeEnabled = true; +const imageTag = false; +let theSelection = false; +const bbcodeEnabled = true; // Check for Browser & Platform for PC & IE specific bits // More details from: http://www.mozilla.org/docs/web-developer/sniffer/browser_type.html -var clientPC = navigator.userAgent.toLowerCase(); // Get client info -var clientVer = parseInt(navigator.appVersion, 10); // Get browser version +const clientPC = navigator.userAgent.toLowerCase(); // Get client info +const clientVer = parseInt(navigator.appVersion, 10); // Get browser version -var is_ie = ((clientPC.indexOf('msie') !== -1) && (clientPC.indexOf('opera') === -1)); -var is_win = ((clientPC.indexOf('win') !== -1) || (clientPC.indexOf('16bit') !== -1)); -var baseHeight; +const is_ie = ((clientPC.indexOf('msie') !== -1) && (clientPC.indexOf('opera') === -1)); +const is_win = ((clientPC.indexOf('win') !== -1) || (clientPC.indexOf('16bit') !== -1)); +let baseHeight; /** * Fix a bug involving the TextRange object. From * http://www.frostjedi.com/terra/scripts/demo/caretBug.html */ function initInsertions() { - var doc; + let doc; if (document.forms[form_name]) { doc = document; @@ -32,9 +35,9 @@ function initInsertions() { doc = opener.document; } - var textarea = doc.forms[form_name].elements[text_name]; + const textarea = doc.forms[form_name].elements[text_name]; - if (is_ie && typeof(baseHeight) !== 'number') { + if (is_ie && typeof (baseHeight) !== 'number') { textarea.focus(); baseHeight = doc.selection.createRange().duplicate().boundingHeight; @@ -48,11 +51,11 @@ function initInsertions() { * bbstyle */ function bbstyle(bbnumber) { - if (bbnumber !== -1) { - bbfontstyle(bbtags[bbnumber], bbtags[bbnumber+1]); - } else { + if (bbnumber === -1) { insert_text('[*]'); document.forms[form_name].elements[text_name].focus(); + } else { + bbfontstyle(bbtags[bbnumber], bbtags[bbnumber + 1]); } } @@ -62,7 +65,7 @@ function bbstyle(bbnumber) { function bbfontstyle(bbopen, bbclose) { theSelection = false; - var textarea = document.forms[form_name].elements[text_name]; + const textarea = document.forms[form_name].elements[text_name]; textarea.focus(); @@ -84,9 +87,9 @@ function bbfontstyle(bbopen, bbclose) { return; } - //The new position for the cursor after adding the bbcode - var caret_pos = getCaretPosition(textarea).start; - var new_pos = caret_pos + bbopen.length; + // The new position for the cursor after adding the bbcode + const caret_pos = getCaretPosition(textarea).start; + const new_pos = caret_pos + bbopen.length; // Open tag insert_text(bbopen + bbclose); @@ -96,11 +99,10 @@ function bbfontstyle(bbopen, bbclose) { if (!isNaN(textarea.selectionStart)) { textarea.selectionStart = new_pos; textarea.selectionEnd = new_pos; - } - // IE - else if (document.selection) { - var range = textarea.createTextRange(); - range.move("character", new_pos); + } else if (document.selection) { + // IE + const range = textarea.createTextRange(); + range.move('character', new_pos); range.select(); storeCaret(textarea); } @@ -112,12 +114,12 @@ function bbfontstyle(bbopen, bbclose) { * Insert text at position */ function insert_text(text, spaces, popup) { - var textarea; + let textarea; - if (!popup) { - textarea = document.forms[form_name].elements[text_name]; - } else { + if (popup) { textarea = opener.document.forms[form_name].elements[text_name]; + } else { + textarea = document.forms[form_name].elements[text_name]; } if (spaces) { @@ -127,8 +129,8 @@ function insert_text(text, spaces, popup) { // Since IE9, IE also has textarea.selectionStart, but it still needs to be treated the old way. // Therefore we simply add a !is_ie here until IE fixes the text-selection completely. if (!isNaN(textarea.selectionStart) && !is_ie) { - var sel_start = textarea.selectionStart; - var sel_end = textarea.selectionEnd; + const sel_start = textarea.selectionStart; + const sel_end = textarea.selectionEnd; mozWrap(textarea, text, ''); textarea.selectionStart = sel_start + text.length; @@ -139,10 +141,10 @@ function insert_text(text, spaces, popup) { storeCaret(textarea); } - var caret_pos = textarea.caretPos; + const caret_pos = textarea.caretPos; caret_pos.text = caret_pos.text.charAt(caret_pos.text.length - 1) === ' ' ? caret_pos.text + text + ' ' : caret_pos.text + text; } else { - textarea.value = textarea.value + text; + textarea.value += text; } if (!popup) { @@ -162,15 +164,16 @@ function attachInline(index, filename) { * Add quote text to message */ function addquote(post_id, username, l_wrote, attributes) { - var message_name = 'message_' + post_id; - var theSelection = ''; - var divarea = false; - var i; + const message_name = 'message_' + post_id; + let theSelection = ''; + let divarea = false; + let i; if (l_wrote === undefined) { // Backwards compatibility l_wrote = 'wrote'; } + if (typeof attributes !== 'object') { attributes = {}; } @@ -195,10 +198,10 @@ function addquote(post_id, username, l_wrote, attributes) { if (divarea.innerHTML) { theSelection = divarea.innerHTML.replace(/
/ig, '\n'); theSelection = theSelection.replace(//ig, '\n'); - theSelection = theSelection.replace(/<\;/ig, '<'); - theSelection = theSelection.replace(/>\;/ig, '>'); - theSelection = theSelection.replace(/&\;/ig, '&'); - theSelection = theSelection.replace(/ \;/ig, ' '); + theSelection = theSelection.replace(/</ig, '<'); + theSelection = theSelection.replace(/>/ig, '>'); + theSelection = theSelection.replace(/&/ig, '&'); + theSelection = theSelection.replace(/ /ig, ' '); } else if (document.all) { theSelection = divarea.innerText; } else if (divarea.textContent) { @@ -213,8 +216,8 @@ function addquote(post_id, username, l_wrote, attributes) { attributes.author = username; insert_text(generateQuote(theSelection, attributes)); } else { - insert_text(username + ' ' + l_wrote + ':' + '\n'); - var lines = split_lines(theSelection); + insert_text(username + ' ' + l_wrote + ':\n'); + const lines = split_lines(theSelection); for (i = 0; i < lines.length; i++) { insert_text('> ' + lines[i] + '\n'); } @@ -237,20 +240,22 @@ function addquote(post_id, username, l_wrote, attributes) { */ function generateQuote(text, attributes) { text = text.replace(/^\s+/, '').replace(/\s+$/, ''); - var quote = '[quote'; + let quote = '[quote'; if (attributes.author) { // Add the author as the BBCode's default attribute quote += '=' + formatAttributeValue(attributes.author); delete attributes.author; } - for (var name in attributes) { - if (attributes.hasOwnProperty(name)) { - var value = attributes[name]; + + for (const name in attributes) { + if (Object.hasOwn(attributes, name)) { + const value = attributes[name]; quote += ' ' + name + '=' + formatAttributeValue(value.toString()); } } + quote += ']'; - var newline = ((quote + text + '[/quote]').length > 80 || text.indexOf('\n') > -1) ? '\n' : ''; + const newline = ((quote + text + '[/quote]').length > 80 || text.indexOf('\n') > -1) ? '\n' : ''; quote += newline + text + newline + '[/quote]'; return quote; @@ -271,25 +276,26 @@ function formatAttributeValue(str) { // Return as-is if it contains none of: space, ' " \ or ] return str; } - var singleQuoted = "'" + str.replace(/[\\']/g, '\\$&') + "'", - doubleQuoted = '"' + str.replace(/[\\"]/g, '\\$&') + '"'; + + const singleQuoted = '\'' + str.replace(/[\\']/g, '\\$&') + '\''; + const doubleQuoted = '"' + str.replace(/[\\"]/g, '\\$&') + '"'; return (singleQuoted.length < doubleQuoted.length) ? singleQuoted : doubleQuoted; } function split_lines(text) { - var lines = text.split('\n'); - var splitLines = new Array(); - var j = 0; - var i; + const lines = text.split('\n'); + const splitLines = []; + let j = 0; + let i; - for(i = 0; i < lines.length; i++) { + for (i = 0; i < lines.length; i++) { if (lines[i].length <= 80) { splitLines[j] = lines[i]; j++; } else { - var line = lines[i]; - var splitAt; + let line = lines[i]; + let splitAt; do { splitAt = line.indexOf(' ', 80); @@ -302,9 +308,10 @@ function split_lines(text) { j++; } } - while(splitAt !== -1); + while (splitAt !== -1); } } + return splitLines; } @@ -312,22 +319,20 @@ function split_lines(text) { * From http://www.massless.org/mozedit/ */ function mozWrap(txtarea, open, close) { - var selLength = (typeof(txtarea.textLength) === 'undefined') ? txtarea.value.length : txtarea.textLength; - var selStart = txtarea.selectionStart; - var selEnd = txtarea.selectionEnd; - var scrollTop = txtarea.scrollTop; + const selLength = (typeof (txtarea.textLength) === 'undefined') ? txtarea.value.length : txtarea.textLength; + const selStart = txtarea.selectionStart; + const selEnd = txtarea.selectionEnd; + const { scrollTop } = txtarea; - var s1 = (txtarea.value).substring(0,selStart); - var s2 = (txtarea.value).substring(selStart, selEnd); - var s3 = (txtarea.value).substring(selEnd, selLength); + const s1 = (txtarea.value).substring(0, selStart); + const s2 = (txtarea.value).substring(selStart, selEnd); + const s3 = (txtarea.value).substring(selEnd, selLength); txtarea.value = s1 + open + s2 + close + s3; txtarea.selectionStart = selStart + open.length; txtarea.selectionEnd = selEnd + open.length; txtarea.focus(); txtarea.scrollTop = scrollTop; - - return; } /** @@ -343,33 +348,32 @@ function storeCaret(textEl) { /** * Caret Position object */ -function caretPosition() { - var start = null; - var end = null; +function CaretPosition() { + const start = null; + const end = null; } /** * Get the caret position in an textarea */ function getCaretPosition(txtarea) { - var caretPos = new caretPosition(); + const caretPos = new CaretPosition(); // simple Gecko/Opera way if (txtarea.selectionStart || txtarea.selectionStart === 0) { caretPos.start = txtarea.selectionStart; caretPos.end = txtarea.selectionEnd; - } - // dirty and slow IE way - else if (document.selection) { + } else if (document.selection) { + // dirty and slow IE way // get current selection - var range = document.selection.createRange(); + const range = document.selection.createRange(); // a new selection of the whole textarea - var range_all = document.body.createTextRange(); + const range_all = document.body.createTextRange(); range_all.moveToElementText(txtarea); // calculate selection start point by moving beginning of range_all to beginning of range - var sel_start; + let sel_start; for (sel_start = 0; range_all.compareEndPoints('StartToStart', range) < 0; sel_start++) { range_all.moveStart('character', 1); } @@ -404,7 +408,7 @@ function getCaretPosition(txtarea) { phpbb.showDragNDrop(textarea); } - $('textarea').on('keydown', function (e) { + $('textarea').on('keydown', function(e) { if (e.which === 13 && (e.metaKey || e.ctrlKey)) { $(this).closest('form').find(':submit').click(); } diff --git a/phpBB/assets/javascript/installer.js b/phpBB/assets/javascript/installer.js index cfe685f88b..cf7820b26d 100644 --- a/phpBB/assets/javascript/installer.js +++ b/phpBB/assets/javascript/installer.js @@ -2,21 +2,22 @@ * Installer's AJAX frontend handler */ +/* eslint no-prototype-builtins: 0 */ (function($) { // Avoid conflicts with other libraries 'use strict'; // Installer variables - var pollTimer = null; - var nextReadPosition = 0; - var progressBarTriggered = false; - var progressTimer = null; - var currentProgress = 0; - var refreshRequested = false; - var transmissionOver = false; - var statusCount = 0; + let pollTimer = null; + let nextReadPosition = 0; + let progressBarTriggered = false; + let progressTimer = null; + let currentProgress = 0; + let refreshRequested = false; + let transmissionOver = false; + let statusCount = 0; // Template related variables - var $contentWrapper = $('.install-body').find('.main'); + const $contentWrapper = $('.install-body').find('.main'); // Intercept form submits interceptFormSubmit($('#install_install')); @@ -42,12 +43,15 @@ */ function addMessage(type, messages) { // Get message containers - var $errorContainer = $('#error-container'); - var $warningContainer = $('#warning-container'); - var $logContainer = $('#log-container'); + const $errorContainer = $('#error-container'); + const $warningContainer = $('#warning-container'); + const $logContainer = $('#log-container'); - var $title, $description, $msgElement, arraySize = messages.length; - for (var i = 0; i < arraySize; i++) { + let $title; + let $description; + let $msgElement; + const arraySize = messages.length; + for (let i = 0; i < arraySize; i++) { $msgElement = $('
'); $title = $(''); $title.text(messages[i].title); @@ -59,24 +63,19 @@ $msgElement.append($description); } - switch (type) { - case 'error': - $msgElement.addClass('errorbox'); - $errorContainer.append($msgElement); - break; - case 'warning': - $msgElement.addClass('warningbox'); - $warningContainer.append($msgElement); - break; - case 'log': - $msgElement.addClass('log'); - $logContainer.prepend($msgElement); - $logContainer.addClass('show_log_container'); - break; - case 'success': - $msgElement.addClass('successbox'); - $errorContainer.prepend($msgElement); - break; + if (type === 'error') { + $msgElement.addClass('errorbox'); + $errorContainer.append($msgElement); + } else if (type === 'warning') { + $msgElement.addClass('warningbox'); + $warningContainer.append($msgElement); + } else if (type === 'log') { + $msgElement.addClass('log'); + $logContainer.prepend($msgElement); + $logContainer.addClass('show_log_container'); + } else if (type === 'success') { + $msgElement.addClass('successbox'); + $errorContainer.prepend($msgElement); } } } @@ -84,12 +83,14 @@ /** * Render a download box */ - function addDownloadBox(downloadArray) - { - var $downloadContainer = $('#download-wrapper'); - var $downloadBox, $title, $content, $link; + function addDownloadBox(downloadArray) { + const $downloadContainer = $('#download-wrapper'); + let $downloadBox; + let $title; + let $content; + let $link; - for (var i = 0; i < downloadArray.length; i++) { + for (let i = 0; i < downloadArray.length; i++) { $downloadBox = $('
'); $downloadBox.addClass('download-box'); @@ -116,9 +117,8 @@ /** * Render update files' status */ - function addUpdateFileStatus(fileStatus) - { - var $statusContainer = $('#file-status-wrapper'); + function addUpdateFileStatus(fileStatus) { + const $statusContainer = $('#file-status-wrapper'); $statusContainer.html(fileStatus); } @@ -128,9 +128,9 @@ * @param formHtml */ function addForm(formHtml) { - var $formContainer = $('#form-wrapper'); + const $formContainer = $('#form-wrapper'); $formContainer.html(formHtml); - var $form = $('#install_install'); + const $form = $('#install_install'); interceptFormSubmit($form); } @@ -140,14 +140,16 @@ * @param navObj */ function updateNavbarStatus(navObj) { - var navID, $stage, $stageListItem, $active; - $active = $('#activemenu'); + let navID; + let $stage; + let $stageListItem; + const $active = $('#activemenu'); if (navObj.hasOwnProperty('finished')) { // This should be an Array - var navItems = navObj.finished; + const navItems = navObj.finished; - for (var i = 0; i < navItems.length; i++) { + for (let i = 0; i < navItems.length; i++) { navID = 'installer-stage-' + navItems[i]; $stage = $('#' + navID); $stageListItem = $stage.parent(); @@ -179,12 +181,16 @@ * @param progressObject */ function setProgress(progressObject) { - var $statusText, $progressBar, $progressText, $progressFiller, $progressFillerText; + let $statusText; + let $progressBar; + let $progressText; + let $progressFiller; + let $progressFillerText; if (progressObject.task_name.length) { if (!progressBarTriggered) { // Create progress bar - var $progressBarWrapper = $('#progress-bar-container'); + const $progressBarWrapper = $('#progress-bar-container'); // Create progress bar elements $progressBar = $('
'); @@ -234,9 +240,9 @@ // Set cookies function setCookies(cookies) { - var cookie; + let cookie; - for (var i = 0; i < cookies.length; i++) { + for (let i = 0; i < cookies.length; i++) { // Set cookie name and value cookie = encodeURIComponent(cookies[i].name) + '=' + encodeURIComponent(cookies[i].value); // Set path @@ -246,11 +252,11 @@ } // Redirects user - function redirect(url, use_ajax) { - if (use_ajax) { + function redirect(url, useAjax) { + if (useAjax) { resetPolling(); - var xhReq = createXhrObject(); + const xhReq = createXhrObject(); xhReq.open('GET', url, true); xhReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhReq.send(); @@ -268,7 +274,7 @@ */ function parseMessage(messageJSON) { $('#loading_indicator').css('display', 'none'); - var responseObject; + let responseObject; try { responseObject = JSON.parse(messageJSON); @@ -276,6 +282,7 @@ if (window.console) { console.log('Failed to parse JSON object\n\nMessage: ' + err.message + '\n\nServer Response: ' + messageJSON); } else { + // eslint-disable-next-line no-alert alert('Failed to parse JSON object\n\nMessage: ' + err.message + '\n\nServer Response: ' + messageJSON); } @@ -359,10 +366,12 @@ } else { $('#loading_indicator').css('display', 'none'); addMessage('error', - [{ + [ { + // eslint-disable-next-line no-undef title: installLang.title, - description: installLang.msg - }] + // eslint-disable-next-line no-undef + description: installLang.msg, + } ], ); } } @@ -371,9 +380,9 @@ * Queries the installer's status */ function queryInstallerStatus() { - var url = $(location).attr('pathname'); - var lookUp = 'install/app.php'; - var position = url.indexOf(lookUp); + let url = $(location).attr('pathname'); + let lookUp = 'install/app.php'; + let position = url.indexOf(lookUp); if (position === -1) { lookUp = 'install'; @@ -385,7 +394,7 @@ } url = url.substring(0, position) + lookUp + '/installer/status'; - $.getJSON(url, function(data) { + $.getJSON(url, data => { processTimeoutResponse(data.status); }); } @@ -396,9 +405,12 @@ * @param xhReq XHR object */ function pollContent(xhReq) { - var messages = xhReq.responseText; - var msgSeparator = '}\n\n'; - var unprocessed, messageEndIndex, endOfMessageIndex, message; + const messages = xhReq.responseText; + const msgSeparator = '}\n\n'; + let unprocessed; + let messageEndIndex; + let endOfMessageIndex; + let message; do { unprocessed = messages.substring(nextReadPosition); @@ -416,7 +428,7 @@ $('#loading_indicator').css('display', 'none'); resetPolling(); - var timeoutDetected = !transmissionOver; + const timeoutDetected = !transmissionOver; if (refreshRequested) { refreshRequested = false; @@ -444,7 +456,7 @@ return; } - var $progressBar = $('#progress-bar'); + const $progressBar = $('#progress-bar'); currentProgress++; $progressFillerText.css('width', $progressBar.width()); @@ -459,14 +471,14 @@ * @param progressLimit */ function incrementProgressBar(progressLimit) { - var $progressFiller = $('#progress-bar-filler'); - var $progressFillerText = $('#progress-bar-filler-text'); - var $progressText = $('#progress-bar-text'); - var progressStart = $progressFiller.width() / $progressFiller.offsetParent().width() * 100; + const $progressFiller = $('#progress-bar-filler'); + const $progressFillerText = $('#progress-bar-filler-text'); + const $progressText = $('#progress-bar-text'); + const progressStart = $progressFiller.width() / $progressFiller.offsetParent().width() * 100; currentProgress = Math.floor(progressStart); clearInterval(progressTimer); - progressTimer = setInterval(function() { + progressTimer = setInterval(() => { incrementFiller($progressText, $progressFiller, $progressFillerText, progressLimit); }, 10); } @@ -487,7 +499,7 @@ function startPolling(xhReq) { resetPolling(); transmissionOver = false; - pollTimer = setInterval(function () { + pollTimer = setInterval(() => { pollContent(xhReq); }, 250); } @@ -498,7 +510,7 @@ function doRefresh() { resetPolling(); - var xhReq = createXhrObject(); + const xhReq = createXhrObject(); xhReq.open('GET', $(location).attr('pathname'), true); xhReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhReq.send(); @@ -515,47 +527,47 @@ // Clear content $contentWrapper.html(''); - var $header = $('
'); + const $header = $('
'); $header.attr('id', 'header-container'); $contentWrapper.append($header); - var $description = $('
'); + const $description = $('
'); $description.attr('id', 'description-container'); $contentWrapper.append($description); - var $errorContainer = $('
'); + const $errorContainer = $('
'); $errorContainer.attr('id', 'error-container'); $contentWrapper.append($errorContainer); - var $warningContainer = $('
'); + const $warningContainer = $('
'); $warningContainer.attr('id', 'warning-container'); $contentWrapper.append($warningContainer); - var $progressContainer = $('
'); + const $progressContainer = $('
'); $progressContainer.attr('id', 'progress-bar-container'); $contentWrapper.append($progressContainer); - var $logContainer = $('
'); + const $logContainer = $('
'); $logContainer.attr('id', 'log-container'); $contentWrapper.append($logContainer); - var $installerContentWrapper = $('
'); + const $installerContentWrapper = $('
'); $installerContentWrapper.attr('id', 'content-container'); $contentWrapper.append($installerContentWrapper); - var $installerDownloadWrapper = $('
'); + const $installerDownloadWrapper = $('
'); $installerDownloadWrapper.attr('id', 'download-wrapper'); $installerContentWrapper.append($installerDownloadWrapper); - var $updaterFileStatusWrapper = $('
'); + const $updaterFileStatusWrapper = $('
'); $updaterFileStatusWrapper.attr('id', 'file-status-wrapper'); $installerContentWrapper.append($updaterFileStatusWrapper); - var $formWrapper = $('
'); + const $formWrapper = $('
'); $formWrapper.attr('id', 'form-wrapper'); $installerContentWrapper.append($formWrapper); - var $spinner = $('
'); + const $spinner = $('
'); $spinner.attr('id', 'loading_indicator'); $spinner.html(' '); $contentWrapper.append($spinner); @@ -565,7 +577,7 @@ function submitForm($form, $submitBtn) { $form.css('display', 'none'); - var xhReq = createXhrObject(); + const xhReq = createXhrObject(); xhReq.open('POST', $form.attr('action'), true); xhReq.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); xhReq.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); @@ -590,7 +602,7 @@ * @returns {*} */ function getFormFields($form, $submitBtn) { - var formData = $form.serialize(); + let formData = $form.serialize(); formData += ((formData.length) ? '&' : '') + encodeURIComponent($submitBtn.attr('name')) + '='; formData += encodeURIComponent($submitBtn.attr('value')); @@ -605,11 +617,13 @@ function interceptFormSubmit($form) { if (!$form.length) { return; - } else if ($form.find('input[name="admin_name"]').length > 0) { + } + + if ($form.find('input[name="admin_name"]').length > 0) { setAdminTimezone($form); } - $form.find(':submit').bind('click', function (event) { + $form.find(':submit').bind('click', function(event) { event.preventDefault(); submitForm($form, $(this)); }); @@ -623,7 +637,8 @@ function setAdminTimezone($form) { // Set admin timezone if it does not exist yet if ($form.find('input[name="admin_timezone"]').length === 0) { - const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; + // eslint-disable-next-line new-cap + const { timeZone } = Intl.DateTimeFormat().resolvedOptions(); // Add timezone as form entry const timezoneEntry = $(''); diff --git a/phpBB/assets/javascript/plupload.js b/phpBB/assets/javascript/plupload.js index 8c52aae819..f2f0fb5497 100644 --- a/phpBB/assets/javascript/plupload.js +++ b/phpBB/assets/javascript/plupload.js @@ -1,714 +1,723 @@ -/* global phpbb, plupload, attachInline */ +/* global phpbb, plupload, attachInline, activateSubPanel */ +/* eslint camelcase: 0 */ plupload.addI18n(phpbb.plupload.i18n); phpbb.plupload.ids = []; -(function($) { // Avoid conflicts with other libraries +(function($) { // Avoid conflicts with other libraries + 'use strict'; -'use strict'; - -/** - * Set up the uploader. - */ -phpbb.plupload.initialize = function() { + /** + * Set up the uploader. + */ + phpbb.plupload.initialize = function() { // Initialize the Plupload uploader. - phpbb.plupload.uploader.init(); + phpbb.plupload.uploader.init(); - // Set attachment data. - phpbb.plupload.setData(phpbb.plupload.data); - phpbb.plupload.updateMultipartParams(phpbb.plupload.getSerializedData()); + // Set attachment data. + phpbb.plupload.setData(phpbb.plupload.data); + phpbb.plupload.updateMultipartParams(phpbb.plupload.getSerializedData()); - // Only execute if Plupload initialized successfully. - phpbb.plupload.uploader.bind('Init', function() { - phpbb.plupload.form = $(phpbb.plupload.config.form_hook)[0]; - let $attachRowTemplate = $('#attach-row-tpl'); - $attachRowTemplate.removeClass('attach-row-tpl'); - phpbb.plupload.rowTpl = $attachRowTemplate[0].outerHTML; + // Only execute if Plupload initialized successfully. + phpbb.plupload.uploader.bind('Init', () => { + phpbb.plupload.form = $(phpbb.plupload.config.form_hook)[0]; + const $attachRowTemplate = $('#attach-row-tpl'); + $attachRowTemplate.removeClass('attach-row-tpl'); + phpbb.plupload.rowTpl = $attachRowTemplate[0].outerHTML; - // Hide the basic upload panel and remove the attach row template. - $('#attach-row-tpl, #attach-panel-basic').remove(); - // Show multi-file upload options. - $('#attach-panel-multi').show(); - }); - - phpbb.plupload.uploader.bind('PostInit', function() { - // Point out the drag-and-drop zone if it's supported. - if (phpbb.plupload.uploader.features.dragdrop) { - $('#drag-n-drop-message').show(); - } - - // Ensure "Add files" button position is correctly calculated. - if ($('#attach-panel-multi').is(':visible')) { - phpbb.plupload.uploader.refresh(); - } - $('[data-subpanel="attach-panel"]').one('click', function() { - phpbb.plupload.uploader.refresh(); + // Hide the basic upload panel and remove the attach row template. + $('#attach-row-tpl, #attach-panel-basic').remove(); + // Show multi-file upload options. + $('#attach-panel-multi').show(); }); - }); -}; -/** - * Unsets all elements in the object uploader.settings.multipart_params whose keys - * begin with 'attachment_data[' - */ -phpbb.plupload.clearParams = function() { - var obj = phpbb.plupload.uploader.settings.multipart_params; - for (var key in obj) { - if (!obj.hasOwnProperty(key) || key.indexOf('attachment_data[') !== 0) { - continue; - } + phpbb.plupload.uploader.bind('PostInit', () => { + // Point out the drag-and-drop zone if it's supported. + if (phpbb.plupload.uploader.features.dragdrop) { + $('#drag-n-drop-message').show(); + } - delete phpbb.plupload.uploader.settings.multipart_params[key]; - } -}; + // Ensure "Add files" button position is correctly calculated. + if ($('#attach-panel-multi').is(':visible')) { + phpbb.plupload.uploader.refresh(); + } -/** - * Update uploader.settings.multipart_params object with new data. - * - * @param {object} obj - */ -phpbb.plupload.updateMultipartParams = function(obj) { - var settings = phpbb.plupload.uploader.settings; - settings.multipart_params = $.extend(settings.multipart_params, obj); -}; + $('[data-subpanel="attach-panel"]').one('click', () => { + phpbb.plupload.uploader.refresh(); + }); + }); + }; -/** - * Convert the array of attachment objects into an object that PHP would expect as POST data. - * - * @returns {object} An object in the form 'attachment_data[i][key]': value as - * expected by the server - */ -phpbb.plupload.getSerializedData = function() { - var obj = {}; - for (var i = 0; i < phpbb.plupload.data.length; i++) { - var datum = phpbb.plupload.data[i]; - for (var key in datum) { - if (!datum.hasOwnProperty(key)) { + /** + * Unsets all elements in the object uploader.settings.multipart_params whose keys + * begin with 'attachment_data[' + */ + phpbb.plupload.clearParams = function() { + const obj = phpbb.plupload.uploader.settings.multipart_params; + for (const key in obj) { + if (!Object.prototype.hasOwnProperty.call(obj, key) || key.indexOf('attachment_data[') !== 0) { continue; } - obj['attachment_data[' + i + '][' + key + ']'] = datum[key]; + delete phpbb.plupload.uploader.settings.multipart_params[key]; } - } - - // Insert form data - var $pluploadForm = $(phpbb.plupload.config.form_hook).first(); - obj.creation_time = $pluploadForm.find('input[type=hidden][name="creation_time"]').val(); - obj.form_token = $pluploadForm.find('input[type=hidden][name="form_token"]').val(); - - return obj; -}; - -/** - * Get the index from the phpbb.plupload.data array where the given - * attachment id appears. - * - * @param {int} attachId The attachment id of the file. - * @returns {bool|int} Index of the file if exists, otherwise false. - */ -phpbb.plupload.getIndex = function(attachId) { - var index = $.inArray(Number(attachId), phpbb.plupload.ids); - return (index !== -1) ? index : false; -}; - -/** - * Set the data in phpbb.plupload.data and phpbb.plupload.ids arrays. - * - * @param {Array} data Array containing the new data to use. In the form of - * array(index => object(property: value). Requires attach_id to be one of the object properties. - */ -phpbb.plupload.setData = function(data) { - // Make sure that the array keys are reset. - phpbb.plupload.ids = phpbb.plupload.data = []; - phpbb.plupload.data = data; - - for (var i = 0; i < data.length; i++) { - phpbb.plupload.ids.push(Number(data[i].attach_id)); - } -}; - -/** - * Update the attachment data in the HTML and the phpbb & phpbb.plupload objects. - * - * @param {Array} data Array containing the new data to use. - * @param {string} action The action that required the update. Used to update the inline attachment bbcodes. - * @param {int} index The index from phpbb.plupload_ids that was affected by the action. - * @param {Array} downloadUrl Optional array of download urls to update. - */ -phpbb.plupload.update = function(data, action, index, downloadUrl) { - - phpbb.plupload.updateBbcode(action, index); - phpbb.plupload.setData(data); - phpbb.plupload.updateRows(downloadUrl); - phpbb.plupload.clearParams(); - phpbb.plupload.updateMultipartParams(phpbb.plupload.getSerializedData()); -}; - -/** - * Update the relevant elements and hidden data for all attachments. - * - * @param {Array} downloadUrl Optional array of download urls to update. - */ -phpbb.plupload.updateRows = function(downloadUrl) { - for (var i = 0; i < phpbb.plupload.ids.length; i++) { - phpbb.plupload.updateRow(i, downloadUrl); - } -}; - -/** - * Insert a row for a new attachment. This expects an HTML snippet in the HTML - * using the id "attach-row-tpl" to be present. This snippet is cloned and the - * data for the file inserted into it. The row is then appended or prepended to - * #file-list based on the attach_order setting. - * - * @param {object} file Plupload file object for the new attachment. - */ -phpbb.plupload.insertRow = function(file) { - var row = $(phpbb.plupload.rowTpl); - - row.attr('id', file.id); - row.find('.file-name').html(plupload.xmlEncode(file.name)); - row.find('.file-size').html(plupload.formatSize(file.size)); - - if (phpbb.plupload.order === 'desc') { - $('#file-list').prepend(row); - } else { - $('#file-list').append(row); - } -}; - -/** - * Update the relevant elements and hidden data for an attachment. - * - * @param {int} index The index from phpbb.plupload.ids of the attachment to edit. - * @param {Array} downloadUrl Optional array of download urls to update. - */ -phpbb.plupload.updateRow = function(index, downloadUrl) { - var attach = phpbb.plupload.data[index], - row = $('[data-attach-id="' + attach.attach_id + '"]'); - - // Add the link to the file - if (typeof downloadUrl !== 'undefined' && typeof downloadUrl[index] !== 'undefined') { - var url = downloadUrl[index].replace('&', '&'), - link = $(''); - - link.attr('href', url).html(attach.real_filename); - row.find('.file-name').html(link); - } - - row.find('textarea').attr('name', 'comment_list[' + index + ']'); - phpbb.plupload.updateHiddenData(row, attach, index); -}; - -/** - * Update hidden input data for an attachment. - * - * @param {object} row jQuery object for the attachment row. - * @param {object} attach Attachment data object from phpbb.plupload.data - * @param {int} index Attachment index from phpbb.plupload.ids - */ -phpbb.plupload.updateHiddenData = function(row, attach, index) { - row.find('input[type="hidden"]').remove(); - - for (var key in attach) { - if (!attach.hasOwnProperty(key)) { - return; - } - - var input = $('') - .attr('type', 'hidden') - .attr('name', 'attachment_data[' + index + '][' + key + ']') - .attr('value', attach[key]); - $(row).append(input); - } -}; - -/** - * Deleting a file removes it from the queue and fires an AJAX event to the - * server to tell it to remove the temporary attachment. The server - * responds with the updated attachment data list so that any future - * uploads can maintain state with the server - * - * @param {object} row jQuery object for the attachment row. - * @param {int} attachId Attachment id of the file to be removed. - */ -phpbb.plupload.deleteFile = function(row, attachId) { - // If there's no attach id, then the file hasn't been uploaded. Simply delete the row. - if (typeof attachId === 'undefined') { - var file = phpbb.plupload.uploader.getFile(row.attr('id')); - phpbb.plupload.uploader.removeFile(file); - - row.slideUp(100, function() { - row.remove(); - phpbb.plupload.hideEmptyList(); - }); - } - - var index = phpbb.plupload.getIndex(attachId); - row.find('.file-status').toggleClass('file-uploaded file-working'); - - if (index === false) { - return; - } - var fields = {}; - fields['delete_file[' + index + ']'] = 1; - - var always = function() { - row.find('.file-status').removeClass('file-working'); }; - var done = function(response) { - if (typeof response !== 'object') { - return; - } - - // trigger_error() was called which likely means a permission error was encountered. - if (typeof response.title !== 'undefined') { - phpbb.plupload.uploader.trigger('Error', { message: response.message }); - // We will have to assume that the deletion failed. So leave the file status as uploaded. - row.find('.file-status').toggleClass('file-uploaded'); - - return; - } - - // Handle errors while deleting file - if (typeof response.error !== 'undefined') { - phpbb.alert(phpbb.plupload.lang.ERROR, response.error.message); - - // We will have to assume that the deletion failed. So leave the file status as uploaded. - row.find('.file-status').toggleClass('file-uploaded'); - - return; - } - - phpbb.plupload.update(response, 'removal', index); - // Check if the user can upload files now if he had reached the max files limit. - phpbb.plupload.handleMaxFilesReached(); - - if (row.attr('id')) { - var file = phpbb.plupload.uploader.getFile(row.attr('id')); - phpbb.plupload.uploader.removeFile(file); - } - row.slideUp(100, function() { - row.remove(); - // Hide the file list if it's empty now. - phpbb.plupload.hideEmptyList(); - }); - phpbb.plupload.uploader.trigger('FilesRemoved'); + /** + * Update uploader.settings.multipart_params object with new data. + * + * @param {object} obj + */ + phpbb.plupload.updateMultipartParams = function(obj) { + const { settings } = phpbb.plupload.uploader; + settings.multipart_params = $.extend(settings.multipart_params, obj); }; - $.ajax(phpbb.plupload.config.url, { - type: 'POST', - data: $.extend(fields, phpbb.plupload.getSerializedData()), - headers: phpbb.plupload.config.headers - }) - .always(always) - .done(done); -}; + /** + * Convert the array of attachment objects into an object that PHP would expect as POST data. + * + * @returns {object} An object in the form 'attachment_data[i][key]': value as + * expected by the server + */ + phpbb.plupload.getSerializedData = function() { + const obj = {}; + for (let i = 0; i < phpbb.plupload.data.length; i++) { + const datum = phpbb.plupload.data[i]; + for (const key in datum) { + if (!Object.prototype.hasOwnProperty.call(datum, key)) { + continue; + } -/** - * Check the attachment list and hide its container if it's empty. - */ -phpbb.plupload.hideEmptyList = function() { - if (!$('#file-list').children().length) { - $('#file-list-container').slideUp(100); - } -}; - -/** - * Update the indices used in inline attachment bbcodes. This ensures that the - * bbcodes correspond to the correct file after a file is added or removed. - * This should be called before the phpbb.plupload,data and phpbb.plupload.ids - * arrays are updated, otherwise it will not work correctly. - * - * @param {string} action The action that occurred -- either "addition" or "removal" - * @param {int} index The index of the attachment from phpbb.plupload.ids that was affected. - */ -phpbb.plupload.updateBbcode = function(action, index) { - var textarea = $('#message', phpbb.plupload.form), - text = textarea.val(), - removal = (action === 'removal'); - - // Return if the bbcode isn't used at all. - if (text.indexOf('[attachment=') === -1) { - return; - } - - function runUpdate(i) { - var regex = new RegExp('\\[attachment=' + i + '\\](.*?)\\[\\/attachment\\]', 'g'); - text = text.replace(regex, function updateBbcode(_, fileName) { - // Remove the bbcode if the file was removed. - if (removal && index === i) { - return ''; + obj['attachment_data[' + i + '][' + key + ']'] = datum[key]; } - var newIndex = i + ((removal) ? -1 : 1); - return '[attachment=' + newIndex + ']' + fileName + '[/attachment]'; - }); - } - - // Loop forwards when removing and backwards when adding ensures we don't - // corrupt the bbcode index. - var i; - if (removal) { - for (i = index; i < phpbb.plupload.ids.length; i++) { - runUpdate(i); } - } else { - for (i = phpbb.plupload.ids.length - 1; i >= index; i--) { - runUpdate(i); + + // Insert form data + const $pluploadForm = $(phpbb.plupload.config.form_hook).first(); + obj.creation_time = $pluploadForm.find('input[type=hidden][name="creation_time"]').val(); + obj.form_token = $pluploadForm.find('input[type=hidden][name="form_token"]').val(); + + return obj; + }; + + /** + * Get the index from the phpbb.plupload.data array where the given + * attachment id appears. + * + * @param {int} attachId The attachment id of the file. + * @returns {bool|int} Index of the file if exists, otherwise false. + */ + phpbb.plupload.getIndex = function(attachId) { + const index = $.inArray(Number(attachId), phpbb.plupload.ids); + return index === -1 ? false : index; + }; + + /** + * Set the data in phpbb.plupload.data and phpbb.plupload.ids arrays. + * + * @param {Array} data Array containing the new data to use. In the form of + * array(index => object(property: value). Requires attach_id to be one of the object properties. + */ + phpbb.plupload.setData = function(data) { + // Make sure that the array keys are reset. + phpbb.plupload.ids = []; + phpbb.plupload.data = []; + phpbb.plupload.data = data; + + for (let i = 0; i < data.length; i++) { + phpbb.plupload.ids.push(Number(data[i].attach_id)); } - } + }; - textarea.val(text); -}; + /** + * Update the attachment data in the HTML and the phpbb & phpbb.plupload objects. + * + * @param {Array} data Array containing the new data to use. + * @param {string} action The action that required the update. Used to update the inline attachment bbcodes. + * @param {int} index The index from phpbb.plupload_ids that was affected by the action. + * @param {Array} downloadUrl Optional array of download urls to update. + */ + phpbb.plupload.update = function(data, action, index, downloadUrl) { + phpbb.plupload.updateBbcode(action, index); + phpbb.plupload.setData(data); + phpbb.plupload.updateRows(downloadUrl); + phpbb.plupload.clearParams(); + phpbb.plupload.updateMultipartParams(phpbb.plupload.getSerializedData()); + }; -/** - * Get Plupload file objects based on their upload status. - * - * @param {int} status Plupload status - plupload.DONE, plupload.FAILED, - * plupload.QUEUED, plupload.STARTED, plupload.STOPPED - * - * @returns {Array} The Plupload file objects matching the status. - */ -phpbb.plupload.getFilesByStatus = function(status) { - var files = []; - - $.each(phpbb.plupload.uploader.files, function(i, file) { - if (file.status === status) { - files.push(file); + /** + * Update the relevant elements and hidden data for all attachments. + * + * @param {Array} downloadUrl Optional array of download urls to update. + */ + phpbb.plupload.updateRows = function(downloadUrl) { + for (let i = 0; i < phpbb.plupload.ids.length; i++) { + phpbb.plupload.updateRow(i, downloadUrl); } - }); - return files; -}; + }; -/** - * Check whether the user has reached the maximun number of files that he's allowed - * to upload. If so, disables the uploader and marks the queued files as failed. Otherwise - * makes sure that the uploader is enabled. - * - * @returns {bool} True if the limit has been reached. False if otherwise. - */ -phpbb.plupload.handleMaxFilesReached = function() { - // If there is no limit, the user is an admin or moderator. - if (!phpbb.plupload.maxFiles) { - return false; - } + /** + * Insert a row for a new attachment. This expects an HTML snippet in the HTML + * using the id "attach-row-tpl" to be present. This snippet is cloned and the + * data for the file inserted into it. The row is then appended or prepended to + * #file-list based on the attach_order setting. + * + * @param {object} file Plupload file object for the new attachment. + */ + phpbb.plupload.insertRow = function(file) { + const row = $(phpbb.plupload.rowTpl); - if (phpbb.plupload.maxFiles <= phpbb.plupload.ids.length) { - // Fail the rest of the queue. - phpbb.plupload.markQueuedFailed(phpbb.plupload.lang.TOO_MANY_ATTACHMENTS); - // Disable the uploader. - phpbb.plupload.disableUploader(); - phpbb.plupload.uploader.trigger('Error', { message: phpbb.plupload.lang.TOO_MANY_ATTACHMENTS }); + row.attr('id', file.id); + row.find('.file-name').html(plupload.xmlEncode(file.name)); + row.find('.file-size').html(plupload.formatSize(file.size)); - return true; - } else if (phpbb.plupload.maxFiles > phpbb.plupload.ids.length) { - // Enable the uploader if the user is under the limit - phpbb.plupload.enableUploader(); - } - return false; -}; + if (phpbb.plupload.order === 'desc') { + $('#file-list').prepend(row); + } else { + $('#file-list').append(row); + } + }; -/** - * Disable the uploader - */ -phpbb.plupload.disableUploader = function() { - $('#add_files').addClass('disabled'); - phpbb.plupload.uploader.disableBrowse(); -}; + /** + * Update the relevant elements and hidden data for an attachment. + * + * @param {int} index The index from phpbb.plupload.ids of the attachment to edit. + * @param {Array} downloadUrl Optional array of download urls to update. + */ + phpbb.plupload.updateRow = function(index, downloadUrl) { + const attach = phpbb.plupload.data[index]; + const row = $('[data-attach-id="' + attach.attach_id + '"]'); -/** - * Enable the uploader - */ -phpbb.plupload.enableUploader = function() { - $('#add_files').removeClass('disabled'); - phpbb.plupload.uploader.disableBrowse(false); -}; + // Add the link to the file + if (typeof downloadUrl !== 'undefined' && typeof downloadUrl[index] !== 'undefined') { + const url = downloadUrl[index].replace('&', '&'); + const link = $(''); -/** - * Mark all queued files as failed. - * - * @param {string} error Error message to present to the user. - */ -phpbb.plupload.markQueuedFailed = function(error) { - var files = phpbb.plupload.getFilesByStatus(plupload.QUEUED); + link.attr('href', url).html(attach.real_filename); + row.find('.file-name').html(link); + } - $.each(files, function(i, file) { - $('#' + file.id).find('.file-progress').hide(); - phpbb.plupload.fileError(file, error); - }); -}; + row.find('textarea').attr('name', 'comment_list[' + index + ']'); + phpbb.plupload.updateHiddenData(row, attach, index); + }; -/** - * Marks a file as failed and sets the error message for it. - * - * @param {object} file Plupload file object that failed. - * @param {string} error Error message to present to the user. - */ -phpbb.plupload.fileError = function(file, error) { - file.status = plupload.FAILED; - file.error = error; - $('#' + file.id).find('.file-status') - .addClass('file-error') - .attr({ - 'data-error-title': phpbb.plupload.lang.ERROR, - 'data-error-message': error - }); -}; + /** + * Update hidden input data for an attachment. + * + * @param {object} row jQuery object for the attachment row. + * @param {object} attach Attachment data object from phpbb.plupload.data + * @param {int} index Attachment index from phpbb.plupload.ids + */ + phpbb.plupload.updateHiddenData = function(row, attach, index) { + row.find('input[type="hidden"]').remove(); + for (const key in attach) { + if (!Object.prototype.hasOwnProperty.call(attach, key)) { + continue; + } -/** - * Set up the Plupload object and get some basic data. - */ -phpbb.plupload.uploader = new plupload.Uploader(phpbb.plupload.config); -phpbb.plupload.initialize(); + const input = $('') + .attr('type', 'hidden') + .attr('name', 'attachment_data[' + index + '][' + key + ']') + .attr('value', attach[key]); + $(row).append(input); + } + }; -/** - * Add a file filter to check for max file sizes per mime type. - */ -plupload.addFileFilter('mime_types_max_file_size', function(types, file, callback) { - if (file.size !== 'undefined') { - $(types).each(function(i, type) { - let extensions = [], - extsArray = type.extensions.split(','); + /** + * Deleting a file removes it from the queue and fires an AJAX event to the + * server to tell it to remove the temporary attachment. The server + * responds with the updated attachment data list so that any future + * uploads can maintain state with the server + * + * @param {object} row jQuery object for the attachment row. + * @param {int} attachId Attachment id of the file to be removed. + */ + phpbb.plupload.deleteFile = function(row, attachId) { + // If there's no attach id, then the file hasn't been uploaded. Simply delete the row. + if (typeof attachId === 'undefined') { + const file = phpbb.plupload.uploader.getFile(row.attr('id')); + phpbb.plupload.uploader.removeFile(file); - $(extsArray).each(function(i, extension) { - /^\s*\*\s*$/.test(extension) ? extensions.push("\\.*") : extensions.push("\\." + extension.replace(new RegExp("[" + "/^$.*+?|()[]{}\\".replace(/./g, "\\$&") + "]", "g"), "\\$&")); + row.slideUp(100, () => { + row.remove(); + phpbb.plupload.hideEmptyList(); }); + } - let regex = new RegExp("(" + extensions.join("|") + ")$", "i"); + const index = phpbb.plupload.getIndex(attachId); + row.find('.file-status').toggleClass('file-uploaded file-working'); - if (regex.test(file.name)) { - if (type.max_file_size !== 'undefined' && type.max_file_size) { - if (file.size > type.max_file_size) { - phpbb.plupload.uploader.trigger('Error', { - code: plupload.FILE_SIZE_ERROR, - message: plupload.translate('File size error.'), - file: file - }); + if (index === false) { + return; + } - callback(false); + const fields = {}; + fields['delete_file[' + index + ']'] = 1; + + const always = function() { + row.find('.file-status').removeClass('file-working'); + }; + + const done = function(response) { + if (typeof response !== 'object') { + return; + } + + // trigger_error() was called which likely means a permission error was encountered. + if (typeof response.title !== 'undefined') { + phpbb.plupload.uploader.trigger('Error', { message: response.message }); + // We will have to assume that the deletion failed. So leave the file status as uploaded. + row.find('.file-status').toggleClass('file-uploaded'); + + return; + } + + // Handle errors while deleting file + if (typeof response.error !== 'undefined') { + phpbb.alert(phpbb.plupload.lang.ERROR, response.error.message); + + // We will have to assume that the deletion failed. So leave the file status as uploaded. + row.find('.file-status').toggleClass('file-uploaded'); + + return; + } + + phpbb.plupload.update(response, 'removal', index); + // Check if the user can upload files now if he had reached the max files limit. + phpbb.plupload.handleMaxFilesReached(); + + if (row.attr('id')) { + const file = phpbb.plupload.uploader.getFile(row.attr('id')); + phpbb.plupload.uploader.removeFile(file); + } + + row.slideUp(100, () => { + row.remove(); + // Hide the file list if it's empty now. + phpbb.plupload.hideEmptyList(); + }); + phpbb.plupload.uploader.trigger('FilesRemoved'); + }; + + $.ajax(phpbb.plupload.config.url, { + type: 'POST', + data: $.extend(fields, phpbb.plupload.getSerializedData()), + headers: phpbb.plupload.config.headers, + }) + .always(always) + .done(done); + }; + + /** + * Check the attachment list and hide its container if it's empty. + */ + phpbb.plupload.hideEmptyList = function() { + if (!$('#file-list').children().length) { + $('#file-list-container').slideUp(100); + } + }; + + /** + * Update the indices used in inline attachment bbcodes. This ensures that the + * bbcodes correspond to the correct file after a file is added or removed. + * This should be called before the phpbb.plupload,data and phpbb.plupload.ids + * arrays are updated, otherwise it will not work correctly. + * + * @param {string} action The action that occurred -- either "addition" or "removal" + * @param {int} index The index of the attachment from phpbb.plupload.ids that was affected. + */ + phpbb.plupload.updateBbcode = function(action, index) { + const textarea = $('#message', phpbb.plupload.form); + let text = textarea.val(); + const removal = (action === 'removal'); + + // Return if the bbcode isn't used at all. + if (text.indexOf('[attachment=') === -1) { + return; + } + + function runUpdate(i) { + const regex = new RegExp('\\[attachment=' + i + '\\](.*?)\\[\\/attachment\\]', 'g'); + text = text.replace(regex, (_, fileName) => { + // Remove the bbcode if the file was removed. + if (removal && index === i) { + return ''; + } + + const newIndex = i + ((removal) ? -1 : 1); + return '[attachment=' + newIndex + ']' + fileName + '[/attachment]'; + }); + } + + // Loop forwards when removing and backwards when adding ensures we don't + // corrupt the bbcode index. + let i; + if (removal) { + for (i = index; i < phpbb.plupload.ids.length; i++) { + runUpdate(i); + } + } else { + for (i = phpbb.plupload.ids.length - 1; i >= index; i--) { + runUpdate(i); + } + } + + textarea.val(text); + }; + + /** + * Get Plupload file objects based on their upload status. + * + * @param {int} status Plupload status - plupload.DONE, plupload.FAILED, + * plupload.QUEUED, plupload.STARTED, plupload.STOPPED + * + * @returns {Array} The Plupload file objects matching the status. + */ + phpbb.plupload.getFilesByStatus = function(status) { + const files = []; + + $.each(phpbb.plupload.uploader.files, (i, file) => { + if (file.status === status) { + files.push(file); + } + }); + return files; + }; + + /** + * Check whether the user has reached the maximun number of files that he's allowed + * to upload. If so, disables the uploader and marks the queued files as failed. Otherwise + * makes sure that the uploader is enabled. + * + * @returns {bool} True if the limit has been reached. False if otherwise. + */ + phpbb.plupload.handleMaxFilesReached = function() { + // If there is no limit, the user is an admin or moderator. + if (!phpbb.plupload.maxFiles) { + return false; + } + + if (phpbb.plupload.maxFiles <= phpbb.plupload.ids.length) { + // Fail the rest of the queue. + phpbb.plupload.markQueuedFailed(phpbb.plupload.lang.TOO_MANY_ATTACHMENTS); + // Disable the uploader. + phpbb.plupload.disableUploader(); + phpbb.plupload.uploader.trigger('Error', { message: phpbb.plupload.lang.TOO_MANY_ATTACHMENTS }); + + return true; + } + + if (phpbb.plupload.maxFiles > phpbb.plupload.ids.length) { + // Enable the uploader if the user is under the limit + phpbb.plupload.enableUploader(); + } + + return false; + }; + + /** + * Disable the uploader + */ + phpbb.plupload.disableUploader = function() { + $('#add_files').addClass('disabled'); + phpbb.plupload.uploader.disableBrowse(); + }; + + /** + * Enable the uploader + */ + phpbb.plupload.enableUploader = function() { + $('#add_files').removeClass('disabled'); + phpbb.plupload.uploader.disableBrowse(false); + }; + + /** + * Mark all queued files as failed. + * + * @param {string} error Error message to present to the user. + */ + phpbb.plupload.markQueuedFailed = function(error) { + const files = phpbb.plupload.getFilesByStatus(plupload.QUEUED); + + $.each(files, (i, file) => { + $('#' + file.id).find('.file-progress').hide(); + phpbb.plupload.fileError(file, error); + }); + }; + + /** + * Marks a file as failed and sets the error message for it. + * + * @param {object} file Plupload file object that failed. + * @param {string} error Error message to present to the user. + */ + phpbb.plupload.fileError = function(file, error) { + file.status = plupload.FAILED; + file.error = error; + $('#' + file.id).find('.file-status') + .addClass('file-error') + .attr({ + 'data-error-title': phpbb.plupload.lang.ERROR, + 'data-error-message': error, + }); + }; + + /** + * Set up the Plupload object and get some basic data. + */ + phpbb.plupload.uploader = new plupload.Uploader(phpbb.plupload.config); + phpbb.plupload.initialize(); + + /** + * Add a file filter to check for max file sizes per mime type. + */ + plupload.addFileFilter('mime_types_max_file_size', (types, file, callback) => { + if (file.size !== 'undefined') { + $(types).each((i, type) => { + const extensions = []; + const extsArray = type.extensions.split(','); + + $(extsArray).each((i, extension) => { + if (/^\s*\*\s*$/.test(extension)) { + extensions.push('\\.*'); + } else { + extensions.push('\\.' + extension.replace(new RegExp('[' + '/^$.*+?|()[]{}\\'.replace(/./g, '\\$&') + ']', 'g'), '\\$&')); + } + }); + + const regex = new RegExp('(' + extensions.join('|') + ')$', 'i'); + + if (regex.test(file.name)) { + if (type.max_file_size !== 'undefined' && type.max_file_size) { + if (file.size > type.max_file_size) { + phpbb.plupload.uploader.trigger('Error', { + code: plupload.FILE_SIZE_ERROR, + message: plupload.translate('File size error.'), + file, + }); + + callback(false); + } else { + callback(true); + } } else { callback(true); } - } else { - callback(true); + + return false; } + }); + } + }); - return false; - } - }); - } -}); + const $fileList = $('#file-list'); -var $fileList = $('#file-list'); + /** + * Insert inline attachment bbcode. + */ + $fileList.on('click', '.file-inline-bbcode', function(e) { + const attachId = $(this).parents('.attach-row').attr('data-attach-id'); + const index = phpbb.plupload.getIndex(attachId); -/** - * Insert inline attachment bbcode. - */ -$fileList.on('click', '.file-inline-bbcode', function(e) { - var attachId = $(this).parents('.attach-row').attr('data-attach-id'), - index = phpbb.plupload.getIndex(attachId); + attachInline(index, phpbb.plupload.data[index].real_filename); + e.preventDefault(); + }); - attachInline(index, phpbb.plupload.data[index].real_filename); - e.preventDefault(); -}); + /** + * Delete a file. + */ + $fileList.on('click', '.file-delete', function(e) { + const row = $(this).parents('.attach-row'); + const attachId = row.attr('data-attach-id'); -/** - * Delete a file. - */ -$fileList.on('click', '.file-delete', function(e) { - var row = $(this).parents('.attach-row'), - attachId = row.attr('data-attach-id'); + phpbb.plupload.deleteFile(row, attachId); + e.preventDefault(); + }); - phpbb.plupload.deleteFile(row, attachId); - e.preventDefault(); -}); + /** + * Display the error message for a particular file when the error icon is clicked. + */ + $fileList.on('click', '.file-error', function(e) { + phpbb.alert($(this).attr('data-error-title'), $(this).attr('data-error-message')); + e.preventDefault(); + }); -/** - * Display the error message for a particular file when the error icon is clicked. - */ -$fileList.on('click', '.file-error', function(e) { - phpbb.alert($(this).attr('data-error-title'), $(this).attr('data-error-message')); - e.preventDefault(); -}); + /** + * Fires when an error occurs. + */ + phpbb.plupload.uploader.bind('Error', (up, error) => { + error.file.name = plupload.xmlEncode(error.file.name); -/** - * Fires when an error occurs. - */ -phpbb.plupload.uploader.bind('Error', function(up, error) { - error.file.name = plupload.xmlEncode(error.file.name); + // The error message that Plupload provides for these is vague, so we'll be more specific. + if (error.code === plupload.FILE_EXTENSION_ERROR) { + error.message = plupload.translate('Invalid file extension:') + ' ' + error.file.name; + } else if (error.code === plupload.FILE_SIZE_ERROR) { + error.message = plupload.translate('File too large:') + ' ' + error.file.name; + } - // The error message that Plupload provides for these is vague, so we'll be more specific. - if (error.code === plupload.FILE_EXTENSION_ERROR) { - error.message = plupload.translate('Invalid file extension:') + ' ' + error.file.name; - } else if (error.code === plupload.FILE_SIZE_ERROR) { - error.message = plupload.translate('File too large:') + ' ' + error.file.name; - } - phpbb.alert(phpbb.plupload.lang.ERROR, error.message); -}); + phpbb.alert(phpbb.plupload.lang.ERROR, error.message); + }); -/** - * Fires before a given file is about to be uploaded. This allows us to - * send the real filename along with the chunk. This is necessary because - * for some reason the filename is set to 'blob' whenever a file is chunked - * - * @param {object} up The plupload.Uploader object - * @param {object} file The plupload.File object that is about to be uploaded - */ -phpbb.plupload.uploader.bind('BeforeUpload', function(up, file) { - if (phpbb.plupload.handleMaxFilesReached()) { - return; - } + /** + * Fires before a given file is about to be uploaded. This allows us to + * send the real filename along with the chunk. This is necessary because + * for some reason the filename is set to 'blob' whenever a file is chunked + * + * @param {object} up The plupload.Uploader object + * @param {object} file The plupload.File object that is about to be uploaded + */ + phpbb.plupload.uploader.bind('BeforeUpload', (up, file) => { + if (phpbb.plupload.handleMaxFilesReached()) { + return; + } - phpbb.plupload.updateMultipartParams({ real_filename: file.name }); -}); + phpbb.plupload.updateMultipartParams({ real_filename: file.name }); + }); -/** - * Fired when a single chunk of any given file is uploaded. This parses the - * response from the server and checks for an error. If an error occurs it - * is reported to the user and the upload of this particular file is halted - * - * @param {object} up The plupload.Uploader object - * @param {object} file The plupload.File object whose chunk has just - * been uploaded - * @param {object} response The response object from the server - */ -phpbb.plupload.uploader.bind('ChunkUploaded', function(up, file, response) { - if (response.chunk >= response.chunks - 1) { - return; - } + /** + * Fired when a single chunk of any given file is uploaded. This parses the + * response from the server and checks for an error. If an error occurs it + * is reported to the user and the upload of this particular file is halted + * + * @param {object} up The plupload.Uploader object + * @param {object} file The plupload.File object whose chunk has just + * been uploaded + * @param {object} response The response object from the server + */ + phpbb.plupload.uploader.bind('ChunkUploaded', (up, file, response) => { + if (response.chunk >= response.chunks - 1) { + return; + } - var json = {}; - try { - json = $.parseJSON(response.response); - } catch (e) { - file.status = plupload.FAILED; - up.trigger('FileUploaded', file, { - response: JSON.stringify({ - error: { - message: 'Error parsing server response.' - } - }) - }); - } + let json = {}; + try { + json = $.parseJSON(response.response); + } catch { + file.status = plupload.FAILED; + up.trigger('FileUploaded', file, { + response: JSON.stringify({ + error: { + message: 'Error parsing server response.', + }, + }), + }); + } - // If trigger_error() was called, then a permission error likely occurred. - if (typeof json.title !== 'undefined') { - json.error = { message: json.message }; - } + // If trigger_error() was called, then a permission error likely occurred. + if (typeof json.title !== 'undefined') { + json.error = { message: json.message }; + } - if (json.error) { - file.status = plupload.FAILED; - up.trigger('FileUploaded', file, { - response: JSON.stringify({ - error: { - message: json.error.message - } - }) - }); - } -}); + if (json.error) { + file.status = plupload.FAILED; + up.trigger('FileUploaded', file, { + response: JSON.stringify({ + error: { + message: json.error.message, + }, + }), + }); + } + }); -/** - * Fires when files are added to the queue. - */ -phpbb.plupload.uploader.bind('FilesAdded', function(up, files) { + /** + * Fires when files are added to the queue. + */ + phpbb.plupload.uploader.bind('FilesAdded', (up, files) => { // Prevent unnecessary requests to the server if the user already uploaded // the maximum number of files allowed. - if (phpbb.plupload.handleMaxFilesReached()) { - return; - } + if (phpbb.plupload.handleMaxFilesReached()) { + return; + } - // Switch the active tab if the style supports it - if (typeof activateSubPanel === 'function') { - activateSubPanel('attach-panel'); // jshint ignore: line - } + // Switch the active tab if the style supports it + if (typeof activateSubPanel === 'function') { + activateSubPanel('attach-panel'); // jshint ignore: line + } - // Show the file list if there aren't any files currently. - var $fileListContainer = $('#file-list-container'); - if (!$fileListContainer.is(':visible')) { - $fileListContainer.show(100); - } + // Show the file list if there aren't any files currently. + const $fileListContainer = $('#file-list-container'); + if (!$fileListContainer.is(':visible')) { + $fileListContainer.show(100); + } - $.each(files, function(i, file) { - phpbb.plupload.insertRow(file); - }); - - up.bind('UploadProgress', function(up, file) { - $('.file-progress-bar', '#' + file.id).css('width', file.percent + '%'); - $('#file-total-progress-bar').css('width', up.total.percent + '%'); - }); - - // Do not allow more files to be added to the running queue. - phpbb.plupload.disableUploader(); - - // Start uploading the files once the user has selected them. - up.start(); -}); - - -/** - * Fires when an entire file has been uploaded. It checks for errors - * returned by the server otherwise parses the list of attachment data and - * appends it to the next file upload so that the server can maintain state - * with regards to the attachments in a given post - * - * @param {object} up The plupload.Uploader object - * @param {object} file The plupload.File object that has just been - * uploaded - * @param {string} response The response string from the server - */ -phpbb.plupload.uploader.bind('FileUploaded', function(up, file, response) { - var json = {}, - row = $('#' + file.id), - error; - - // Hide the progress indicator. - row.find('.file-progress').hide(); - - try { - json = JSON.parse(response.response); - } catch (e) { - error = 'Error parsing server response.'; - } - - // If trigger_error() was called, then a permission error likely occurred. - if (typeof json.title !== 'undefined') { - error = json.message; - up.trigger('Error', { message: error }); - - // The rest of the queue will fail. - phpbb.plupload.markQueuedFailed(error); - } else if (json.error) { - error = json.error.message; - } - - if (typeof error !== 'undefined') { - phpbb.plupload.fileError(file, error); - } else if (file.status === plupload.DONE) { - file.attachment_data = json.data[0]; - - row.attr('data-attach-id', file.attachment_data.attach_id); - row.find('.file-inline-bbcode').show(); - row.find('.file-status').addClass('file-uploaded'); - phpbb.plupload.update(json.data, 'addition', 0, [json.download_url]); - } -}); - -/** - * Fires when the entire queue of files have been uploaded. - */ -phpbb.plupload.uploader.bind('UploadComplete', function() { - // Hide the progress bar - setTimeout(function() { - $('#file-total-progress-bar').fadeOut(500, function() { - $(this).css('width', 0).show(); + $.each(files, (i, file) => { + phpbb.plupload.insertRow(file); }); - }, 2000); - // Re-enable the uploader - phpbb.plupload.enableUploader(); -}); + up.bind('UploadProgress', (up, file) => { + $('.file-progress-bar', '#' + file.id).css('width', file.percent + '%'); + $('#file-total-progress-bar').css('width', up.total.percent + '%'); + }); + // Do not allow more files to be added to the running queue. + phpbb.plupload.disableUploader(); + + // Start uploading the files once the user has selected them. + up.start(); + }); + + /** + * Fires when an entire file has been uploaded. It checks for errors + * returned by the server otherwise parses the list of attachment data and + * appends it to the next file upload so that the server can maintain state + * with regards to the attachments in a given post + * + * @param {object} up The plupload.Uploader object + * @param {object} file The plupload.File object that has just been + * uploaded + * @param {string} response The response string from the server + */ + phpbb.plupload.uploader.bind('FileUploaded', (up, file, response) => { + let json = {}; + const row = $('#' + file.id); + let error; + + // Hide the progress indicator. + row.find('.file-progress').hide(); + + try { + json = JSON.parse(response.response); + } catch { + error = 'Error parsing server response.'; + } + + // If trigger_error() was called, then a permission error likely occurred. + if (typeof json.title !== 'undefined') { + error = json.message; + up.trigger('Error', { message: error }); + + // The rest of the queue will fail. + phpbb.plupload.markQueuedFailed(error); + } else if (json.error) { + error = json.error.message; + } + + if (typeof error !== 'undefined') { + phpbb.plupload.fileError(file, error); + } else if (file.status === plupload.DONE) { + file.attachment_data = json.data[0]; + + row.attr('data-attach-id', file.attachment_data.attach_id); + row.find('.file-inline-bbcode').show(); + row.find('.file-status').addClass('file-uploaded'); + phpbb.plupload.update(json.data, 'addition', 0, [ json.download_url ]); + } + }); + + /** + * Fires when the entire queue of files have been uploaded. + */ + phpbb.plupload.uploader.bind('UploadComplete', () => { + // Hide the progress bar + setTimeout(() => { + $('#file-total-progress-bar').fadeOut(500, function() { + $(this).css('width', 0).show(); + }); + }, 2000); + + // Re-enable the uploader + phpbb.plupload.enableUploader(); + }); })(jQuery); // Avoid conflicts with other libraries diff --git a/phpBB/styles/prosilver/template/ajax.js b/phpBB/styles/prosilver/template/ajax.js index 93e8fcef80..c5a0778c8e 100644 --- a/phpBB/styles/prosilver/template/ajax.js +++ b/phpBB/styles/prosilver/template/ajax.js @@ -1,427 +1,425 @@ /* global phpbb */ +/* eslint camelcase: 0 */ -(function($) { // Avoid conflicts with other libraries +(function($) { // Avoid conflicts with other libraries + 'use strict'; -'use strict'; + // This callback will mark all forum icons read + phpbb.addAjaxCallback('mark_forums_read', function(res) { + const readTitle = res.NO_UNREAD_POSTS; + const unreadTitle = res.UNREAD_POSTS; + const iconsArray = { + forum_unread: 'forum_read', + forum_unread_subforum: 'forum_read_subforum', + forum_unread_locked: 'forum_read_locked', + }; -// This callback will mark all forum icons read -phpbb.addAjaxCallback('mark_forums_read', function(res) { - var readTitle = res.NO_UNREAD_POSTS; - var unreadTitle = res.UNREAD_POSTS; - var iconsArray = { - forum_unread: 'forum_read', - forum_unread_subforum: 'forum_read_subforum', - forum_unread_locked: 'forum_read_locked' - }; + $('li.row').find('dl[class*="forum_unread"]').each(function() { + const $this = $(this); - $('li.row').find('dl[class*="forum_unread"]').each(function() { - var $this = $(this); - - $.each(iconsArray, function(unreadClass, readClass) { - if ($this.hasClass(unreadClass)) { - $this.removeClass(unreadClass).addClass(readClass); - } + $.each(iconsArray, (unreadClass, readClass) => { + if ($this.hasClass(unreadClass)) { + $this.removeClass(unreadClass).addClass(readClass); + } + }); + $this.children('dt[title="' + unreadTitle + '"]').attr('title', readTitle); }); - $this.children('dt[title="' + unreadTitle + '"]').attr('title', readTitle); - }); - // Mark subforums read - $('a.subforum[class*="unread"]').removeClass('unread').addClass('read').children('.icon.icon-red').removeClass('icon-red').addClass('icon-blue'); + // Mark subforums read + $('a.subforum[class*="unread"]').removeClass('unread').addClass('read').children('.icon.icon-red').removeClass('icon-red').addClass('icon-blue'); - // Mark topics read if we are watching a category and showing active topics - if ($('#active_topics').length) { - phpbb.ajaxCallbacks.mark_topics_read.call(this, res, false); - } + // Mark topics read if we are watching a category and showing active topics + if ($('#active_topics').length) { + phpbb.ajaxCallbacks.mark_topics_read.call(this, res, false); + } - // Update mark forums read links - $('[data-ajax="mark_forums_read"]').attr('href', res.U_MARK_FORUMS); + // Update mark forums read links + $('[data-ajax="mark_forums_read"]').attr('href', res.U_MARK_FORUMS); - phpbb.closeDarkenWrapper(3000); -}); - -/** -* This callback will mark all topic icons read -* -* @param {bool} [update_topic_links=true] Whether "Mark topics read" links -* should be updated. Defaults to true. -*/ -phpbb.addAjaxCallback('mark_topics_read', function(res, updateTopicLinks) { - var readTitle = res.NO_UNREAD_POSTS; - var unreadTitle = res.UNREAD_POSTS; - var iconsArray = { - global_unread: 'global_read', - announce_unread: 'announce_read', - sticky_unread: 'sticky_read', - topic_unread: 'topic_read' - }; - var iconsState = ['', '_hot', '_hot_mine', '_locked', '_locked_mine', '_mine']; - var unreadClassSelectors; - var classMap = {}; - var classNames = []; - - if (typeof updateTopicLinks === 'undefined') { - updateTopicLinks = true; - } - - $.each(iconsArray, function(unreadClass, readClass) { - $.each(iconsState, function(key, value) { - // Only topics can be hot - if ((value === '_hot' || value === '_hot_mine') && unreadClass !== 'topic_unread') { - return true; - } - classMap[unreadClass + value] = readClass + value; - classNames.push(unreadClass + value); - }); - }); - - unreadClassSelectors = '.' + classNames.join(',.'); - - $('li.row').find(unreadClassSelectors).each(function() { - var $this = $(this); - $.each(classMap, function(unreadClass, readClass) { - if ($this.hasClass(unreadClass)) { - $this.removeClass(unreadClass).addClass(readClass); - } - }); - $this.children('dt[title="' + unreadTitle + '"]').attr('title', readTitle); - }); - - // Remove link to first unread post - $('a.unread').has('.icon-red').remove(); - - // Update mark topics read links - if (updateTopicLinks) { - $('[data-ajax="mark_topics_read"]').attr('href', res.U_MARK_TOPICS); - } - - phpbb.closeDarkenWrapper(3000); -}); - -// This callback will mark all notifications read -phpbb.addAjaxCallback('notification.mark_all_read', function(res) { - if (typeof res.success !== 'undefined') { - phpbb.markNotifications($('[data-notification-unread="true"]'), 0); - phpbb.toggleDropdown.call($('#notification-button')); phpbb.closeDarkenWrapper(3000); - } -}); + }); -// This callback will mark a notification read -phpbb.addAjaxCallback('notification.mark_read', function(res) { - if (typeof res.success !== 'undefined') { - var unreadCount = Number($('#notification-button strong').html()) - 1; - phpbb.markNotifications($(this).parent('[data-notification-unread="true"]'), unreadCount); - } -}); + /** + * This callback will mark all topic icons read + * + * @param {bool} [update_topic_links=true] Whether "Mark topics read" links + * should be updated. Defaults to true. + */ + phpbb.addAjaxCallback('mark_topics_read', (res, updateTopicLinks) => { + const readTitle = res.NO_UNREAD_POSTS; + const unreadTitle = res.UNREAD_POSTS; + const iconsArray = { + global_unread: 'global_read', + announce_unread: 'announce_read', + sticky_unread: 'sticky_read', + topic_unread: 'topic_read', + }; + const iconsState = [ '', '_hot', '_hot_mine', '_locked', '_locked_mine', '_mine' ]; + const classMap = {}; + const classNames = []; -/** - * Mark notification popup rows as read. - * - * @param {jQuery} $popup jQuery object(s) to mark read. - * @param {int} unreadCount The new unread notifications count. - */ -phpbb.markNotifications = function($popup, unreadCount) { + if (typeof updateTopicLinks === 'undefined') { + updateTopicLinks = true; + } + + $.each(iconsArray, (unreadClass, readClass) => { + $.each(iconsState, (key, value) => { + // Only topics can be hot + if ((value === '_hot' || value === '_hot_mine') && unreadClass !== 'topic_unread') { + return true; + } + + classMap[unreadClass + value] = readClass + value; + classNames.push(unreadClass + value); + }); + }); + + const unreadClassSelectors = '.' + classNames.join(',.'); + + $('li.row').find(unreadClassSelectors).each(function() { + const $this = $(this); + $.each(classMap, (unreadClass, readClass) => { + if ($this.hasClass(unreadClass)) { + $this.removeClass(unreadClass).addClass(readClass); + } + }); + $this.children('dt[title="' + unreadTitle + '"]').attr('title', readTitle); + }); + + // Remove link to first unread post + $('a.unread').has('.icon-red').remove(); + + // Update mark topics read links + if (updateTopicLinks) { + $('[data-ajax="mark_topics_read"]').attr('href', res.U_MARK_TOPICS); + } + + phpbb.closeDarkenWrapper(3000); + }); + + // This callback will mark all notifications read + phpbb.addAjaxCallback('notification.mark_all_read', res => { + if (typeof res.success !== 'undefined') { + phpbb.markNotifications($('[data-notification-unread="true"]'), 0); + phpbb.toggleDropdown.call($('#notification-button')); + phpbb.closeDarkenWrapper(3000); + } + }); + + // This callback will mark a notification read + phpbb.addAjaxCallback('notification.mark_read', function(res) { + if (typeof res.success !== 'undefined') { + const unreadCount = Number($('#notification-button strong').html()) - 1; + phpbb.markNotifications($(this).parent('[data-notification-unread="true"]'), unreadCount); + } + }); + + /** + * Mark notification popup rows as read. + * + * @param {jQuery} $popup jQuery object(s) to mark read. + * @param {int} unreadCount The new unread notifications count. + */ + phpbb.markNotifications = function($popup, unreadCount) { // Remove the unread status. - $popup.removeClass('bg2'); - $popup.find('a.mark_read').remove(); + $popup.removeClass('bg2'); + $popup.find('a.mark_read').remove(); - // Update the notification link to the real URL. - $popup.each(function() { - var link = $(this).find('a'); - link.attr('href', link.attr('data-real-url')); + // Update the notification link to the real URL. + $popup.each(function() { + const link = $(this).find('a'); + link.attr('href', link.attr('data-real-url')); + }); + + // Update the unread count. + $('strong', '#notification-button').html(unreadCount); + // Remove the Mark all read link and hide notification count if there are no unread notifications. + if (!unreadCount) { + $('#mark_all_notifications').remove(); + $('#notification-button > strong').addClass('hidden'); + } + + // Update page title + const $title = $('title'); + const originalTitle = $title.text().replace(/(\((\d+)\))/, ''); + $title.text((unreadCount ? '(' + unreadCount + ')' : '') + originalTitle); + }; + + // This callback finds the post from the delete link, and removes it. + phpbb.addAjaxCallback('post_delete', function() { + const $this = $(this); + let postId; + + if ($this.attr('data-refresh') === undefined) { + postId = $this[0].href.split('&p=')[1]; + const post = $this.parents('#p' + postId).css('pointer-events', 'none'); + if (post.hasClass('bg1') || post.hasClass('bg2')) { + const posts1 = post.nextAll('.bg1'); + post.nextAll('.bg2').removeClass('bg2').addClass('bg1'); + posts1.removeClass('bg1').addClass('bg2'); + } + + post.fadeOut(function() { + $(this).remove(); + }); + } }); - // Update the unread count. - $('strong', '#notification-button').html(unreadCount); - // Remove the Mark all read link and hide notification count if there are no unread notifications. - if (!unreadCount) { - $('#mark_all_notifications').remove(); - $('#notification-button > strong').addClass('hidden'); - } - - // Update page title - var $title = $('title'); - var originalTitle = $title.text().replace(/(\((\d+)\))/, ''); - $title.text((unreadCount ? '(' + unreadCount + ')' : '') + originalTitle); -}; - -// This callback finds the post from the delete link, and removes it. -phpbb.addAjaxCallback('post_delete', function() { - var $this = $(this), - postId; - - if ($this.attr('data-refresh') === undefined) { - postId = $this[0].href.split('&p=')[1]; - var post = $this.parents('#p' + postId).css('pointer-events', 'none'); - if (post.hasClass('bg1') || post.hasClass('bg2')) { - var posts1 = post.nextAll('.bg1'); - post.nextAll('.bg2').removeClass('bg2').addClass('bg1'); - posts1.removeClass('bg1').addClass('bg2'); - } - post.fadeOut(function() { + // This callback removes the approve / disapprove div or link. + phpbb.addAjaxCallback('post_visibility', function(res) { + const remove = (res.visible) ? $(this) : $(this).parents('.post'); + $(remove).css('pointer-events', 'none').fadeOut(function() { $(this).remove(); }); - } -}); -// This callback removes the approve / disapprove div or link. -phpbb.addAjaxCallback('post_visibility', function(res) { - var remove = (res.visible) ? $(this) : $(this).parents('.post'); - $(remove).css('pointer-events', 'none').fadeOut(function() { - $(this).remove(); - }); - - if (res.visible) { + if (res.visible) { // Remove the "Deleted by" message from the post on restoring. - remove.parents('.post').find('.post_deleted_msg').css('pointer-events', 'none').fadeOut(function() { - $(this).remove(); - }); - } -}); - -// This removes the parent row of the link or form that fired the callback. -phpbb.addAjaxCallback('row_delete', function() { - $(this).parents('tr').remove(); -}); - -// This handles friend / foe additions removals. -phpbb.addAjaxCallback('zebra', function(res) { - var zebra; - - if (res.success) { - zebra = $('.zebra'); - zebra.first().html(res.MESSAGE_TEXT); - zebra.not(':first').html(' ').prev().html(' '); - } -}); - -/** - * This callback updates the poll results after voting. - */ -phpbb.addAjaxCallback('vote_poll', function(res) { - if (typeof res.success !== 'undefined') { - var poll = $(this).closest('.topic_poll'); - var panel = poll.find('.panel'); - var resultsVisible = poll.find('dl:first-child .resultbar').is(':visible'); - var mostVotes = 0; - - // Set min-height to prevent the page from jumping when the content changes - var updatePanelHeight = function (height) { - height = (typeof height === 'undefined') ? panel.find('.inner').outerHeight() : height; - panel.css('min-height', height); - }; - updatePanelHeight(); - - // Remove the View results link - if (!resultsVisible) { - poll.find('.poll_view_results').hide(500); - } - - if (!res.can_vote) { - poll.find('.polls, .poll_max_votes, .poll_vote, .poll_option_select').fadeOut(500, function () { - poll.find('.resultbar, .poll_option_percent, .poll_total_votes').show(); + remove.parents('.post').find('.post_deleted_msg').css('pointer-events', 'none').fadeOut(function() { + $(this).remove(); }); - } else { - // If the user can still vote, simply slide down the results - poll.find('.resultbar, .poll_option_percent, .poll_total_votes').show(500); } + }); - // Get the votes count of the highest poll option - poll.find('[data-poll-option-id]').each(function() { - var option = $(this); - var optionId = option.attr('data-poll-option-id'); - mostVotes = (res.vote_counts[optionId] >= mostVotes) ? res.vote_counts[optionId] : mostVotes; - }); + // This removes the parent row of the link or form that fired the callback. + phpbb.addAjaxCallback('row_delete', function() { + $(this).parents('tr').remove(); + }); - // Update the total votes count - poll.find('.poll_total_vote_cnt').html(res.total_votes); + // This handles friend / foe additions removals. + phpbb.addAjaxCallback('zebra', res => { + let zebra; - // Update each option - poll.find('[data-poll-option-id]').each(function() { - var $this = $(this); - var optionId = $this.attr('data-poll-option-id'); - var voted = (typeof res.user_votes[optionId] !== 'undefined'); - var mostVoted = (res.vote_counts[optionId] === mostVotes); - var percent = (!res.total_votes) ? 0 : Math.round((res.vote_counts[optionId] / res.total_votes) * 100); - var percentRel = (mostVotes === 0) ? 0 : Math.round((res.vote_counts[optionId] / mostVotes) * 100); - var altText; + if (res.success) { + zebra = $('.zebra'); + zebra.first().html(res.MESSAGE_TEXT); + zebra.not(':first').html(' ').prev().html(' '); + } + }); - altText = $this.attr('data-alt-text'); - if (voted) { - $this.attr('title', $.trim(altText)); - } else { - $this.attr('title', ''); + /** + * This callback updates the poll results after voting. + */ + phpbb.addAjaxCallback('vote_poll', function(res) { + if (typeof res.success !== 'undefined') { + const poll = $(this).closest('.topic_poll'); + const panel = poll.find('.panel'); + const resultsVisible = poll.find('dl:first-child .resultbar').is(':visible'); + let mostVotes = 0; + + // Set min-height to prevent the page from jumping when the content changes + const updatePanelHeight = function(height) { + height = (typeof height === 'undefined') ? panel.find('.inner').outerHeight() : height; + panel.css('min-height', height); }; - $this.toggleClass('voted', voted); - $this.toggleClass('most-votes', mostVoted); - // Update the bars - var bar = $this.find('.resultbar div'); - var barTimeLapse = (res.can_vote) ? 500 : 1500; - var newBarClass = (percent === 100) ? 'pollbar5' : 'pollbar' + (Math.floor(percent / 20) + 1); + updatePanelHeight(); - setTimeout(function () { - bar.animate({ width: percentRel + '%' }, 500) - .removeClass('pollbar1 pollbar2 pollbar3 pollbar4 pollbar5') - .addClass(newBarClass) - .html(res.vote_counts[optionId]); - - var percentText = percent ? percent + '%' : res.NO_VOTES; - $this.find('.poll_option_percent').html(percentText); - }, barTimeLapse); - }); - - if (!res.can_vote) { - poll.find('.polls').delay(400).fadeIn(500); - } - - // Display "Your vote has been cast." message. Disappears after 5 seconds. - var confirmationDelay = (res.can_vote) ? 300 : 900; - poll.find('.vote-submitted').delay(confirmationDelay).slideDown(200, function() { - if (resultsVisible) { - updatePanelHeight(); + // Remove the View results link + if (!resultsVisible) { + poll.find('.poll_view_results').hide(500); } - $(this).delay(5000).fadeOut(500, function() { - resizePanel(300); + if (res.can_vote) { + // If the user can still vote, simply slide down the results + poll.find('.resultbar, .poll_option_percent, .poll_total_votes').show(500); + } else { + poll.find('.polls, .poll_max_votes, .poll_vote, .poll_option_select').fadeOut(500, () => { + poll.find('.resultbar, .poll_option_percent, .poll_total_votes').show(); + }); + } + + // Get the votes count of the highest poll option + poll.find('[data-poll-option-id]').each(function() { + const option = $(this); + const optionId = option.attr('data-poll-option-id'); + mostVotes = (res.vote_counts[optionId] >= mostVotes) ? res.vote_counts[optionId] : mostVotes; }); - }); - // Remove the gap resulting from removing options - setTimeout(function() { - resizePanel(500); - }, 1500); + // Update the total votes count + poll.find('.poll_total_vote_cnt').html(res.total_votes); - var resizePanel = function (time) { - var panelHeight = panel.height(); - var innerHeight = panel.find('.inner').outerHeight(); + // Update each option + poll.find('[data-poll-option-id]').each(function() { + const $this = $(this); + const optionId = $this.attr('data-poll-option-id'); + const voted = (typeof res.user_votes[optionId] !== 'undefined'); + const mostVoted = (res.vote_counts[optionId] === mostVotes); + const percent = res.total_votes ? Math.round((res.vote_counts[optionId] / res.total_votes) * 100) : 0; + const percentRel = (mostVotes === 0) ? 0 : Math.round((res.vote_counts[optionId] / mostVotes) * 100); - if (panelHeight !== innerHeight) { - panel.css({ minHeight: '', height: panelHeight }) - .animate({ height: innerHeight }, time, function () { - panel.css({ minHeight: innerHeight, height: '' }); - }); + const altText = $this.attr('data-alt-text'); + if (voted) { + $this.attr('title', $.trim(altText)); + } else { + $this.attr('title', ''); + } + + $this.toggleClass('voted', voted); + $this.toggleClass('most-votes', mostVoted); + + // Update the bars + const bar = $this.find('.resultbar div'); + const barTimeLapse = (res.can_vote) ? 500 : 1500; + const newBarClass = (percent === 100) ? 'pollbar5' : 'pollbar' + (Math.floor(percent / 20) + 1); + + setTimeout(() => { + bar.animate({ width: percentRel + '%' }, 500) + .removeClass('pollbar1 pollbar2 pollbar3 pollbar4 pollbar5') + .addClass(newBarClass) + .html(res.vote_counts[optionId]); + + const percentText = percent ? percent + '%' : res.NO_VOTES; + $this.find('.poll_option_percent').html(percentText); + }, barTimeLapse); + }); + + if (!res.can_vote) { + poll.find('.polls').delay(400).fadeIn(500); } - }; - } -}); -/** - * Show poll results when clicking View results link. - */ -$('.poll_view_results a').click(function(e) { - // Do not follow the link - e.preventDefault(); + // Display "Your vote has been cast." message. Disappears after 5 seconds. + const confirmationDelay = (res.can_vote) ? 300 : 900; + poll.find('.vote-submitted').delay(confirmationDelay).slideDown(200, function() { + if (resultsVisible) { + updatePanelHeight(); + } - var $poll = $(this).parents('.topic_poll'); + $(this).delay(5000).fadeOut(500, () => { + resizePanel(300); + }); + }); - $poll.find('.resultbar, .poll_option_percent, .poll_total_votes').show(500); - $poll.find('.poll_view_results').hide(500); -}); + // Remove the gap resulting from removing options + setTimeout(() => { + resizePanel(500); + }, 1500); -$('[data-ajax]').each(function() { - var $this = $(this); - var ajax = $this.attr('data-ajax'); - var filter = $this.attr('data-filter'); + const resizePanel = function(time) { + const panelHeight = panel.height(); + const innerHeight = panel.find('.inner').outerHeight(); - if (ajax !== 'false') { - var fn = (ajax !== 'true') ? ajax : null; - filter = (filter !== undefined) ? phpbb.getFunctionByName(filter) : null; - - phpbb.ajaxify({ - selector: this, - refresh: $this.attr('data-refresh') !== undefined, - filter: filter, - callback: fn - }); - } -}); - -/** - * This simply appends #preview to the action of the - * QR action when you click the Full Editor & Preview button - */ -$('#qr_full_editor').click(function() { - $('#qr_postform').attr('action', function(i, val) { - return val + '#preview'; - }); -}); - - -/** - * Make the display post links to use JS - */ -$('.display_post').click(function(e) { - // Do not follow the link - e.preventDefault(); - - var postId = $(this).attr('data-post-id'); - $('#post_content' + postId).show(); - $('#profile' + postId).show(); - $('#post_hidden' + postId).hide(); -}); - -/** - * Display hidden post on post review page - */ -$('.display_post_review').on('click', function(e) { - e.preventDefault(); - - let $displayPostLink = $(this); - $displayPostLink.closest('.post-ignore').removeClass('post-ignore'); - $displayPostLink.hide(); -}); - -/** -* Toggle the member search panel in memberlist.php. -* -* If user returns to search page after viewing results the search panel is automatically displayed. -* In any case the link will toggle the display status of the search panel and link text will be -* appropriately changed based on the status of the search panel. -*/ -$('#member_search').click(function () { - var $memberlistSearch = $('#memberlist_search'); - - $memberlistSearch.slideToggle('fast'); - phpbb.ajaxCallbacks.alt_text.call(this); - - // Focus on the username textbox if it's available and displayed - if ($memberlistSearch.is(':visible')) { - $('#username').focus(); - } - return false; -}); - -/** - * Show to top button if available on page - */ -const $scrollTopButton = $('.to-top-button'); - -if ($scrollTopButton.length) { - // Show or hide the button based on scroll position - $(window).scroll(function () { - if ($(this).scrollTop() > 300) { - $scrollTopButton.fadeIn(); // Fade in the button - } else { - $scrollTopButton.fadeOut(); // Fade out the button + if (panelHeight !== innerHeight) { + panel.css({ minHeight: '', height: panelHeight }) + .animate({ height: innerHeight }, time, () => { + panel.css({ minHeight: innerHeight, height: '' }); + }); + } + }; } }); - // Scroll smoothly to the top when the button is clicked - $scrollTopButton.click(function (e) { - e.preventDefault(); // Prevent the default anchor link behavior - $('html, body').animate({scrollTop: 0}, 500); // Smooth scroll to top + /** + * Show poll results when clicking View results link. + */ + $('.poll_view_results a').click(function(e) { + // Do not follow the link + e.preventDefault(); + + const $poll = $(this).parents('.topic_poll'); + + $poll.find('.resultbar, .poll_option_percent, .poll_total_votes').show(500); + $poll.find('.poll_view_results').hide(500); }); -} -/** -* Automatically resize textarea -*/ -$(function() { - var $textarea = $('textarea:not(#message-box textarea, .no-auto-resize)'); - phpbb.resizeTextArea($textarea, { minHeight: 75, maxHeight: 250 }); - phpbb.resizeTextArea($('textarea', '#message-box')); -}); + $('[data-ajax]').each(function() { + const $this = $(this); + const ajax = $this.attr('data-ajax'); + let filter = $this.attr('data-filter'); + if (ajax !== 'false') { + const fn = ajax === 'true' ? null : ajax; + filter = filter === undefined ? null : phpbb.getFunctionByName(filter); + phpbb.ajaxify({ + selector: this, + refresh: $this.attr('data-refresh') !== undefined, + filter, + callback: fn, + }); + } + }); + + /** + * This simply appends #preview to the action of the + * QR action when you click the Full Editor & Preview button + */ + $('#qr_full_editor').click(() => { + $('#qr_postform').attr('action', (i, val) => val + '#preview'); + }); + + /** + * Make the display post links to use JS + */ + $('.display_post').click(function(e) { + // Do not follow the link + e.preventDefault(); + + const postId = $(this).attr('data-post-id'); + $('#post_content' + postId).show(); + $('#profile' + postId).show(); + $('#post_hidden' + postId).hide(); + }); + + /** + * Display hidden post on post review page + */ + $('.display_post_review').on('click', function(e) { + e.preventDefault(); + + const $displayPostLink = $(this); + $displayPostLink.closest('.post-ignore').removeClass('post-ignore'); + $displayPostLink.hide(); + }); + + /** + * Toggle the member search panel in memberlist.php. + * + * If user returns to search page after viewing results the search panel is automatically displayed. + * In any case the link will toggle the display status of the search panel and link text will be + * appropriately changed based on the status of the search panel. + */ + $('#member_search').click(function() { + const $memberlistSearch = $('#memberlist_search'); + + $memberlistSearch.slideToggle('fast'); + phpbb.ajaxCallbacks.alt_text.call(this); + + // Focus on the username textbox if it's available and displayed + if ($memberlistSearch.is(':visible')) { + $('#username').focus(); + } + + return false; + }); + + /** + * Show to top button if available on page + */ + const $scrollTopButton = $('.to-top-button'); + + if ($scrollTopButton.length) { + // Show or hide the button based on scroll position + $(window).scroll(function() { + if ($(this).scrollTop() > 300) { + $scrollTopButton.fadeIn(); // Fade in the button + } else { + $scrollTopButton.fadeOut(); // Fade out the button + } + }); + + // Scroll smoothly to the top when the button is clicked + $scrollTopButton.click(e => { + e.preventDefault(); // Prevent the default anchor link behavior + $('html, body').animate({ scrollTop: 0 }, 500); // Smooth scroll to top + }); + } + + /** + * Automatically resize textarea + */ + $(() => { + const $textarea = $('textarea:not(#message-box textarea, .no-auto-resize)'); + phpbb.resizeTextArea($textarea, { minHeight: 75, maxHeight: 250 }); + phpbb.resizeTextArea($('textarea', '#message-box')); + }); })(jQuery); // Avoid conflicts with other libraries diff --git a/phpBB/styles/prosilver/template/forum_fn.js b/phpBB/styles/prosilver/template/forum_fn.js index 7f73d844e1..22523028c5 100644 --- a/phpBB/styles/prosilver/template/forum_fn.js +++ b/phpBB/styles/prosilver/template/forum_fn.js @@ -1,4 +1,6 @@ /* global phpbb */ +/* eslint camelcase: 0 */ +/* eslint no-unused-vars: 0 */ /** * phpBB forum functions @@ -34,10 +36,10 @@ function popup(url, width, height, name) { function pageJump(item) { 'use strict'; - var page = parseInt(item.val(), 10), - perPage = item.attr('data-per-page'), - baseUrl = item.attr('data-base-url'), - startName = item.attr('data-start-name'); + const page = parseInt(item.val(), 10); + const perPage = item.attr('data-per-page'); + const baseUrl = item.attr('data-base-url'); + const startName = item.attr('data-start-name'); if (page !== null && !isNaN(page) && page === Math.floor(page) && page > 0) { if (baseUrl.indexOf('?') === -1) { @@ -56,7 +58,7 @@ function marklist(id, name, state) { 'use strict'; jQuery('#' + id + ' input[type=checkbox][name]').each(function() { - var $this = jQuery(this); + const $this = jQuery(this); if ($this.attr('name').substr(0, name.length) === name && !$this.prop('disabled')) { $this.prop('checked', state); } @@ -78,39 +80,38 @@ function viewableArea(e, itself) { e = e.parentNode; } - if (!e.vaHeight) { + if (e.vaHeight) { + // Restore viewable area height to the default + e.style.height = e.vaHeight + 'px'; + e.style.overflow = 'auto'; + e.style.maxHeight = e.vaMaxHeight; + e.vaHeight = false; + } else { // Store viewable area height before changing style to auto e.vaHeight = e.offsetHeight; e.vaMaxHeight = e.style.maxHeight; e.style.height = 'auto'; e.style.maxHeight = 'none'; e.style.overflow = 'visible'; - } else { - // Restore viewable area height to the default - e.style.height = e.vaHeight + 'px'; - e.style.overflow = 'auto'; - e.style.maxHeight = e.vaMaxHeight; - e.vaHeight = false; } } /** * Alternate display of subPanels */ -jQuery(function($) { +jQuery($ => { 'use strict'; $('.sub-panels').each(function() { - - var $childNodes = $('a[data-subpanel]', this), - panels = $childNodes.map(function () { - return this.getAttribute('data-subpanel'); - }), - showPanel = this.getAttribute('data-show-panel'); + const $childNodes = $('a[data-subpanel]', this); + const panels = $childNodes.map(function() { + return this.getAttribute('data-subpanel'); + }); + const showPanel = this.getAttribute('data-show-panel'); if (panels.length) { activateSubPanel(showPanel, panels); - $childNodes.click(function () { + $childNodes.click(function() { activateSubPanel(this.getAttribute('data-subpanel'), panels); return false; }); @@ -124,11 +125,13 @@ jQuery(function($) { function activateSubPanel(p, panels) { 'use strict'; - var i, showPanel; + let i; + let showPanel; if (typeof p === 'string') { showPanel = p; } + $('input[name="show_panel"]').val(showPanel); if (typeof panels === 'undefined') { @@ -147,15 +150,16 @@ function selectCode(a) { 'use strict'; // Get ID of code block - var e = a.parentNode.parentNode.getElementsByTagName('CODE')[0]; - var s, r; + const e = a.parentNode.parentNode.getElementsByTagName('CODE')[0]; + let s; + let r; // Not IE and IE9+ if (window.getSelection) { s = window.getSelection(); // Safari and Chrome if (s.setBaseAndExtent) { - var l = (e.innerText.length > 1) ? e.innerText.length - 1 : 1; + const l = (e.innerText.length > 1) ? e.innerText.length - 1 : 1; try { s.setBaseAndExtent(e, 0, e, l); } catch (error) { @@ -164,12 +168,11 @@ function selectCode(a) { s.removeAllRanges(); s.addRange(r); } - } - // Firefox and Opera - else { + } else { + // Firefox and Opera // workaround for bug # 42885 if (window.opera && e.innerHTML.substring(e.innerHTML.length - 4) === '
') { - e.innerHTML = e.innerHTML + ' '; + e.innerHTML += ' '; } r = document.createRange(); @@ -177,25 +180,23 @@ function selectCode(a) { s.removeAllRanges(); s.addRange(r); } - } - // Some older browsers - else if (document.getSelection) { + } else if (document.getSelection) { + // Some older browsers s = document.getSelection(); r = document.createRange(); r.selectNodeContents(e); s.removeAllRanges(); s.addRange(r); - } - // IE - else if (document.selection) { + } else if (document.selection) { + // IE r = document.body.createTextRange(); r.moveToElementText(e); r.select(); } } -var inAutocomplete = false; -var lastKeyEntered = ''; +let inAutocomplete = false; +let lastKeyEntered = ''; /** * Check event key @@ -229,11 +230,11 @@ function phpbbCheckKey(event) { /** * Apply onkeypress event for forcing default submit button on ENTER key press */ -jQuery(function($) { +jQuery($ => { 'use strict'; - $('form input[type=text], form input[type=password]').on('keypress', function (e) { - var defaultButton = $(this).parents('form').find('input[type=submit].default-submit-action'); + $('form input[type=text], form input[type=password]').on('keypress', function(e) { + const defaultButton = $(this).parents('form').find('input[type=submit].default-submit-action'); if (!defaultButton || defaultButton.length <= 0) { return true; @@ -258,10 +259,10 @@ jQuery(function($) { function insertUser(formId, value) { 'use strict'; - var $form = jQuery(formId), - formName = $form.attr('data-form-name'), - fieldName = $form.attr('data-field-name'), - item = opener.document.forms[formName][fieldName]; + const $form = jQuery(formId); + const formName = $form.attr('data-form-name'); + const fieldName = $form.attr('data-field-name'); + const item = opener.document.forms[formName][fieldName]; if (item.value.length && item.type === 'textarea') { value = item.value + '\n' + value; @@ -293,9 +294,9 @@ function insert_single_user(formId, user) { function parseDocument($container) { 'use strict'; - var test = document.createElement('div'), - oldBrowser = (typeof test.style.borderRadius === 'undefined'), - $body = $('body'); + const test = document.createElement('div'); + const oldBrowser = (typeof test.style.borderRadius === 'undefined'); + const $body = $('body'); /** * Reset avatar dimensions when changing URL or EMAIL @@ -308,7 +309,7 @@ function parseDocument($container) { * Pagination */ $container.find('.pagination .page-jump-form :button').click(function() { - var $input = $(this).siblings('input.inputbox'); + const $input = $(this).siblings('input.inputbox'); pageJump($input); }); @@ -320,9 +321,9 @@ function parseDocument($container) { }); $container.find('.pagination .dropdown-trigger').click(function() { - var $dropdownContainer = $(this).parent(); + const $dropdownContainer = $(this).parent(); // Wait a little bit to make sure the dropdown has activated - setTimeout(function() { + setTimeout(() => { if ($dropdownContainer.hasClass('dropdown-visible')) { $dropdownContainer.find('input.inputbox').focus(); } @@ -333,28 +334,27 @@ function parseDocument($container) { * Resize navigation (breadcrumbs) block to keep all links on same line */ $container.find('.navlinks').each(function() { - var $this = $(this), - $left = $this.children().not('.rightside'), - $right = $this.children('.rightside'); + const $this = $(this); + const $left = $this.children().not('.rightside'); + const $right = $this.children('.rightside'); if ($left.length !== 1 || !$right.length) { return; } function resize() { - var width = 0, - diff = $left.outerWidth(true) - $left.width(), - minWidth = Math.max($this.width() / 3, 240), - maxWidth; + let width = 0; + const diff = $left.outerWidth(true) - $left.width(); + const minWidth = Math.max($this.width() / 3, 240); $right.each(function() { - var $this = $(this); + const $this = $(this); if ($this.is(':visible')) { width += $this.outerWidth(true); } }); - maxWidth = $this.width() - width - diff; + const maxWidth = $this.width() - width - diff; $left.css('max-width', Math.floor(Math.max(maxWidth, minWidth)) + 'px'); } @@ -366,25 +366,25 @@ function parseDocument($container) { * Makes breadcrumbs responsive */ $container.find('.breadcrumbs:not([data-skip-responsive])').each(function() { - var $this = $(this), - $links = $this.find('.crumb'), - length = $links.length, - classes = ['wrapped-max', 'wrapped-wide', 'wrapped-medium', 'wrapped-small', 'wrapped-tiny'], - classesLength = classes.length, - maxHeight = 0, - lastWidth = false, - wrapped = false; + const $this = $(this); + const $links = $this.find('.crumb'); + const { length } = $links; + const classes = [ 'wrapped-max', 'wrapped-wide', 'wrapped-medium', 'wrapped-small', 'wrapped-tiny' ]; + const classesLength = classes.length; + let maxHeight = 0; + let lastWidth = false; + let wrapped = false; // Set tooltips $this.find('a').each(function() { - var $link = $(this); + const $link = $(this); $link.attr('title', $link.text()); }); // Function that checks breadcrumbs function check() { - var height = $this.height(), - width; + const height = $this.height(); + let width; // Test max-width set in code for .navlinks above width = parseInt($this.css('max-width'), 10); @@ -404,6 +404,7 @@ function parseDocument($container) { return; } } + lastWidth = width; if (wrapped) { @@ -419,8 +420,8 @@ function parseDocument($container) { return; } - for (var i = 0; i < classesLength; i++) { - for (var j = length - 1; j >= 0; j--) { + for (let i = 0; i < classesLength; i++) { + for (let j = length - 1; j >= 0; j--) { $links.eq(j).addClass('wrapped ' + classes[i]); if ($this.height() <= maxHeight) { return; @@ -454,23 +455,23 @@ function parseDocument($container) { * responsive-show-all to list of classes */ $container.find('.topiclist.responsive-show-all > li > dl').each(function() { - var $this = $(this), - $block = $this.find('dt .responsive-show:last-child'), - first = true; + const $this = $(this); + let $block = $this.find('dt .responsive-show:last-child'); + let first = true; // Create block that is visible only on mobile devices - if (!$block.length) { + if ($block.length) { + first = ($.trim($block.text()).length === 0); + } else { $this.find('dt > .list-inner').append('