diff --git a/.ci/azure-pipelines-build.yml b/.ci/azure-pipelines-build.yml index 021b6471cd..9c3a51c9fc 100644 --- a/.ci/azure-pipelines-build.yml +++ b/.ci/azure-pipelines-build.yml @@ -26,8 +26,6 @@ jobs: - script: 'npm ci --no-audit' displayName: 'Install Dependencies' - env: - SKIP_PREPARE: 'true' - script: 'npm run build:development' displayName: 'Build Development' diff --git a/.eslintrc.js b/.eslintrc.js index e2811f35a9..91a4cf29c3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -68,6 +68,7 @@ module.exports = { 'padded-blocks': ['error', 'never'], 'prefer-const': ['error', { 'destructuring': 'all' }], 'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }], + 'radix': ['error'], '@babel/semi': ['error'], 'space-before-blocks': ['error'], 'space-infix-ops': 'error', diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index d0d11b4c5f..9d4ab5b257 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -21,11 +21,11 @@ jobs: - name: Checkout repository uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0 - name: Initialize CodeQL - uses: github/codeql-action/init@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2.2.5 + uses: github/codeql-action/init@16964e90ba004cdf0cd845b866b5df21038b7723 # v2.2.6 with: languages: ${{ matrix.language }} queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2.2.5 + uses: github/codeql-action/autobuild@16964e90ba004cdf0cd845b866b5df21038b7723 # v2.2.6 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@32dc499307d133bb5085bae78498c0ac2cf762d5 # v2.2.5 + uses: github/codeql-action/analyze@16964e90ba004cdf0cd845b866b5df21038b7723 # v2.2.6 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 15526f009a..4d8519501c 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,8 +24,6 @@ jobs: - name: Install Node.js dependencies run: npm ci --no-audit - env: - SKIP_PREPARE: true - name: Run a production build run: npm run build:production @@ -50,8 +48,6 @@ jobs: - name: Install Node.js dependencies run: npm ci --no-audit - env: - SKIP_PREPARE: true - name: Run eslint run: npm run lint @@ -76,8 +72,6 @@ jobs: - name: Install Node.js dependencies run: npm ci --no-audit - env: - SKIP_PREPARE: true - name: Run stylelint run: npm run stylelint:css @@ -102,8 +96,6 @@ jobs: - name: Install Node.js dependencies run: npm ci --no-audit - env: - SKIP_PREPARE: true - name: Run stylelint run: npm run stylelint:scss diff --git a/.github/workflows/tsc.yml b/.github/workflows/tsc.yml index be0f2596aa..2b1b3bf594 100644 --- a/.github/workflows/tsc.yml +++ b/.github/workflows/tsc.yml @@ -24,8 +24,6 @@ jobs: - name: Install Node.js dependencies run: npm ci --no-audit - env: - SKIP_PREPARE: true - name: Run tsc - run: npm run build:check \ No newline at end of file + run: npm run build:check diff --git a/debian/rules b/debian/rules index d9ba35ed30..091af0db11 100755 --- a/debian/rules +++ b/debian/rules @@ -12,6 +12,7 @@ override_dh_clistrip: override_dh_auto_build: npm ci --no-audit --unsafe-perm + npm run build:production mv $(CURDIR)/dist $(CURDIR)/web override_dh_auto_clean: diff --git a/deployment/Dockerfile.docker b/deployment/Dockerfile.docker index 33cf501665..5605e1150f 100644 --- a/deployment/Dockerfile.docker +++ b/deployment/Dockerfile.docker @@ -8,4 +8,6 @@ RUN apk add autoconf g++ make libpng-dev gifsicle alpine-sdk automake libtool ma WORKDIR ${SOURCE_DIR} COPY . . -RUN npm ci --no-audit --unsafe-perm && mv dist ${ARTIFACT_DIR} +RUN npm ci --no-audit --unsafe-perm \ + && npm run build:production \ + && mv dist ${ARTIFACT_DIR} diff --git a/deployment/Dockerfile.fedora b/deployment/Dockerfile.fedora index da3537327f..9248c209d7 100644 --- a/deployment/Dockerfile.fedora +++ b/deployment/Dockerfile.fedora @@ -1,4 +1,4 @@ -FROM fedora:37 +FROM fedora:38 # Docker build arguments ARG SOURCE_DIR=/jellyfin diff --git a/deployment/build.portable b/deployment/build.portable index 18f7a8d1e6..8bf8a0d2af 100755 --- a/deployment/build.portable +++ b/deployment/build.portable @@ -15,6 +15,7 @@ fi # build archives npm ci --no-audit --unsafe-perm +npm run build:production mv dist jellyfin-web_${version} tar -czf jellyfin-web_${version}_portable.tar.gz jellyfin-web_${version} rm -rf dist diff --git a/fedora/jellyfin-web.spec b/fedora/jellyfin-web.spec index 28407b8bcd..595ef33f2c 100644 --- a/fedora/jellyfin-web.spec +++ b/fedora/jellyfin-web.spec @@ -35,6 +35,7 @@ chown root:root -R . %build npm ci --no-audit --unsafe-perm +npm run build:production %install diff --git a/package-lock.json b/package-lock.json index dcc6fb05f6..631774f6f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "classnames": "2.3.2", "core-js": "3.29.0", "date-fns": "2.29.3", - "dompurify": "2.4.4", + "dompurify": "3.0.1", "epubjs": "0.4.2", "escape-html": "1.0.3", "fast-text-encoding": "1.0.6", @@ -68,8 +68,8 @@ "@types/lodash-es": "4.17.6", "@types/react": "17.0.53", "@types/react-dom": "17.0.19", - "@typescript-eslint/eslint-plugin": "5.54.0", - "@typescript-eslint/parser": "5.54.0", + "@typescript-eslint/eslint-plugin": "5.54.1", + "@typescript-eslint/parser": "5.54.1", "@uupaa/dynamic-import-polyfill": "1.0.2", "autoprefixer": "10.4.13", "babel-loader": "9.1.2", @@ -105,7 +105,7 @@ "stylelint": "15.2.0", "stylelint-config-rational-order": "0.1.2", "stylelint-no-browser-hacks": "1.2.1", - "stylelint-order": "6.0.2", + "stylelint-order": "6.0.3", "stylelint-scss": "4.4.0", "ts-loader": "9.4.2", "typescript": "4.9.5", @@ -2611,11 +2611,11 @@ "integrity": "sha512-xQVJw+lZUg4U1TmLS80reBECfPtpCgRF8hhUSvUUQM9g68OvINyUU3K2yqRH+8tomGpghiRaIcr/bUJ83e0veA==" }, "node_modules/@jellyfin/sdk": { - "version": "0.0.0-unstable.202302070552", - "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202302070552.tgz", - "integrity": "sha512-hwrHLLFPTCEcrMywpLWwgGKEDKBjgu3o+ruMV3qCG7uAmKAQq48kuaZ818rJD+LjWBjBIUixnLJq1qUlHsgc+A==", + "version": "0.0.0-unstable.202303130502", + "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202303130502.tgz", + "integrity": "sha512-j3ntDjTnZlU511J0CpuPVSSSYrx9so4Y3q6qYOVsB6/evH4/2BNkWYRbKgCnUtCULIV90T6KGc2EcS4GGxojCg==", "dependencies": { - "axios": "1.2.6", + "axios": "1.3.4", "compare-versions": "5.0.3" } }, @@ -3200,14 +3200,14 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.0.tgz", - "integrity": "sha512-+hSN9BdSr629RF02d7mMtXhAJvDTyCbprNYJKrXETlul/Aml6YZwd90XioVbjejQeHbb3R8Dg0CkRgoJDxo8aw==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.1.tgz", + "integrity": "sha512-a2RQAkosH3d3ZIV08s3DcL/mcGc2M/UC528VkPULFxR9VnVPT8pBu0IyBAJJmVsCmhVfwQX1v6q+QGnmSe1bew==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.54.0", - "@typescript-eslint/type-utils": "5.54.0", - "@typescript-eslint/utils": "5.54.0", + "@typescript-eslint/scope-manager": "5.54.1", + "@typescript-eslint/type-utils": "5.54.1", + "@typescript-eslint/utils": "5.54.1", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -3249,14 +3249,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.54.0.tgz", - "integrity": "sha512-aAVL3Mu2qTi+h/r04WI/5PfNWvO6pdhpeMRWk9R7rEV4mwJNzoWf5CCU5vDKBsPIFQFjEq1xg7XBI2rjiMXQbQ==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.54.1.tgz", + "integrity": "sha512-8zaIXJp/nG9Ff9vQNh7TI+C3nA6q6iIsGJ4B4L6MhZ7mHnTMR4YP5vp2xydmFXIy8rpyIVbNAG44871LMt6ujg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.54.0", - "@typescript-eslint/types": "5.54.0", - "@typescript-eslint/typescript-estree": "5.54.0", + "@typescript-eslint/scope-manager": "5.54.1", + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/typescript-estree": "5.54.1", "debug": "^4.3.4" }, "engines": { @@ -3276,13 +3276,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.0.tgz", - "integrity": "sha512-VTPYNZ7vaWtYna9M4oD42zENOBrb+ZYyCNdFs949GcN8Miwn37b8b7eMj+EZaq7VK9fx0Jd+JhmkhjFhvnovhg==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.1.tgz", + "integrity": "sha512-zWKuGliXxvuxyM71UA/EcPxaviw39dB2504LqAmFDjmkpO8qNLHcmzlh6pbHs1h/7YQ9bnsO8CCcYCSA8sykUg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.54.0", - "@typescript-eslint/visitor-keys": "5.54.0" + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/visitor-keys": "5.54.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3293,13 +3293,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.54.0.tgz", - "integrity": "sha512-WI+WMJ8+oS+LyflqsD4nlXMsVdzTMYTxl16myXPaCXnSgc7LWwMsjxQFZCK/rVmTZ3FN71Ct78ehO9bRC7erYQ==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.54.1.tgz", + "integrity": "sha512-WREHsTz0GqVYLIbzIZYbmUUr95DKEKIXZNH57W3s+4bVnuF1TKe2jH8ZNH8rO1CeMY3U4j4UQeqPNkHMiGem3g==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.54.0", - "@typescript-eslint/utils": "5.54.0", + "@typescript-eslint/typescript-estree": "5.54.1", + "@typescript-eslint/utils": "5.54.1", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -3320,9 +3320,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.54.0.tgz", - "integrity": "sha512-nExy+fDCBEgqblasfeE3aQ3NuafBUxZxgxXcYfzYRZFHdVvk5q60KhCSkG0noHgHRo/xQ/BOzURLZAafFpTkmQ==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.54.1.tgz", + "integrity": "sha512-G9+1vVazrfAfbtmCapJX8jRo2E4MDXxgm/IMOF4oGh3kq7XuK3JRkOg6y2Qu1VsTRmWETyTkWt1wxy7X7/yLkw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3333,13 +3333,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.0.tgz", - "integrity": "sha512-X2rJG97Wj/VRo5YxJ8Qx26Zqf0RRKsVHd4sav8NElhbZzhpBI8jU54i6hfo9eheumj4oO4dcRN1B/zIVEqR/MQ==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.1.tgz", + "integrity": "sha512-bjK5t+S6ffHnVwA0qRPTZrxKSaFYocwFIkZx5k7pvWfsB1I57pO/0M0Skatzzw1sCkjJ83AfGTL0oFIFiDX3bg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.54.0", - "@typescript-eslint/visitor-keys": "5.54.0", + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/visitor-keys": "5.54.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3404,16 +3404,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.54.0.tgz", - "integrity": "sha512-cuwm8D/Z/7AuyAeJ+T0r4WZmlnlxQ8wt7C7fLpFlKMR+dY6QO79Cq1WpJhvZbMA4ZeZGHiRWnht7ZJ8qkdAunw==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.54.1.tgz", + "integrity": "sha512-IY5dyQM8XD1zfDe5X8jegX6r2EVU5o/WJnLu/znLPWCBF7KNGC+adacXnt5jEYS9JixDcoccI6CvE4RCjHMzCQ==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.54.0", - "@typescript-eslint/types": "5.54.0", - "@typescript-eslint/typescript-estree": "5.54.0", + "@typescript-eslint/scope-manager": "5.54.1", + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/typescript-estree": "5.54.1", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -3445,12 +3445,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.0.tgz", - "integrity": "sha512-xu4wT7aRCakGINTLGeyGqDn+78BwFlggwBjnHa1ar/KaGagnmwLYmlrXIrgAaQ3AE1Vd6nLfKASm7LrFHNbKGA==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.1.tgz", + "integrity": "sha512-q8iSoHTgwCfgcRJ2l2x+xCbu8nBlRAlsQ33k24Adj8eoVBE0f8dUeI+bAa8F84Mv05UGbAx57g2zrRsYIooqQg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.54.0", + "@typescript-eslint/types": "5.54.1", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -4111,9 +4111,9 @@ } }, "node_modules/axios": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.6.tgz", - "integrity": "sha512-rC/7F08XxZwjMV4iuWv+JpD3E0Ksqg9nac4IIg6RwNuF0JTeWoCo/mBNG54+tNhhI11G3/VDRbdDQTs9hGp4pQ==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -5965,9 +5965,9 @@ "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==" }, "node_modules/dompurify": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.4.tgz", - "integrity": "sha512-1e2SpqHiRx4DPvmRuXU5J0di3iQACwJM+mFGE2HAkkK7Tbnfk9WcghcAmyWc9CRrjyRRUpmuhPUH6LphQQR3EQ==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.1.tgz", + "integrity": "sha512-60tsgvPKwItxZZdfLmamp0MTcecCta3avOhsLgPZ0qcWt96OasFfhkeIRbJ6br5i0fQawT1/RBGB5L58/Jpwuw==" }, "node_modules/domutils": { "version": "1.7.0", @@ -17063,22 +17063,22 @@ } }, "node_modules/stylelint-order": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.2.tgz", - "integrity": "sha512-yuac0BE6toHd27wUPvYVVQicAJthKFIv1HPQFH3Q0dExiO3Z6Uam7geoO0tUd5Z9ddsATYK++1qWNDX4RxMH5Q==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.3.tgz", + "integrity": "sha512-1j1lOb4EU/6w49qZeT2SQVJXm0Ht+Qnq9GMfUa3pMwoyojIWfuA+JUDmoR97Bht1RLn4ei0xtLGy87M7d29B1w==", "dev": true, "dependencies": { "postcss": "^8.4.21", - "postcss-sorting": "^8.0.1" + "postcss-sorting": "^8.0.2" }, "peerDependencies": { "stylelint": "^14.0.0 || ^15.0.0" } }, "node_modules/stylelint-order/node_modules/postcss-sorting": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.1.tgz", - "integrity": "sha512-go9Zoxx7KQH+uLrJ9xa5wRErFeXu01ydA6O8m7koPXkmAN7Ts//eRcIqjo0stBR4+Nir2gMYDOWAOx7O5EPUZA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.2.tgz", + "integrity": "sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==", "dev": true, "peerDependencies": { "postcss": "^8.4.20" @@ -20912,11 +20912,11 @@ "integrity": "sha512-xQVJw+lZUg4U1TmLS80reBECfPtpCgRF8hhUSvUUQM9g68OvINyUU3K2yqRH+8tomGpghiRaIcr/bUJ83e0veA==" }, "@jellyfin/sdk": { - "version": "0.0.0-unstable.202302070552", - "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202302070552.tgz", - "integrity": "sha512-hwrHLLFPTCEcrMywpLWwgGKEDKBjgu3o+ruMV3qCG7uAmKAQq48kuaZ818rJD+LjWBjBIUixnLJq1qUlHsgc+A==", + "version": "0.0.0-unstable.202303130502", + "resolved": "https://registry.npmjs.org/@jellyfin/sdk/-/sdk-0.0.0-unstable.202303130502.tgz", + "integrity": "sha512-j3ntDjTnZlU511J0CpuPVSSSYrx9so4Y3q6qYOVsB6/evH4/2BNkWYRbKgCnUtCULIV90T6KGc2EcS4GGxojCg==", "requires": { - "axios": "1.2.6", + "axios": "1.3.4", "compare-versions": "5.0.3" } }, @@ -21437,14 +21437,14 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.0.tgz", - "integrity": "sha512-+hSN9BdSr629RF02d7mMtXhAJvDTyCbprNYJKrXETlul/Aml6YZwd90XioVbjejQeHbb3R8Dg0CkRgoJDxo8aw==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.54.1.tgz", + "integrity": "sha512-a2RQAkosH3d3ZIV08s3DcL/mcGc2M/UC528VkPULFxR9VnVPT8pBu0IyBAJJmVsCmhVfwQX1v6q+QGnmSe1bew==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.54.0", - "@typescript-eslint/type-utils": "5.54.0", - "@typescript-eslint/utils": "5.54.0", + "@typescript-eslint/scope-manager": "5.54.1", + "@typescript-eslint/type-utils": "5.54.1", + "@typescript-eslint/utils": "5.54.1", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -21466,53 +21466,53 @@ } }, "@typescript-eslint/parser": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.54.0.tgz", - "integrity": "sha512-aAVL3Mu2qTi+h/r04WI/5PfNWvO6pdhpeMRWk9R7rEV4mwJNzoWf5CCU5vDKBsPIFQFjEq1xg7XBI2rjiMXQbQ==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.54.1.tgz", + "integrity": "sha512-8zaIXJp/nG9Ff9vQNh7TI+C3nA6q6iIsGJ4B4L6MhZ7mHnTMR4YP5vp2xydmFXIy8rpyIVbNAG44871LMt6ujg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.54.0", - "@typescript-eslint/types": "5.54.0", - "@typescript-eslint/typescript-estree": "5.54.0", + "@typescript-eslint/scope-manager": "5.54.1", + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/typescript-estree": "5.54.1", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.0.tgz", - "integrity": "sha512-VTPYNZ7vaWtYna9M4oD42zENOBrb+ZYyCNdFs949GcN8Miwn37b8b7eMj+EZaq7VK9fx0Jd+JhmkhjFhvnovhg==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.54.1.tgz", + "integrity": "sha512-zWKuGliXxvuxyM71UA/EcPxaviw39dB2504LqAmFDjmkpO8qNLHcmzlh6pbHs1h/7YQ9bnsO8CCcYCSA8sykUg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.54.0", - "@typescript-eslint/visitor-keys": "5.54.0" + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/visitor-keys": "5.54.1" } }, "@typescript-eslint/type-utils": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.54.0.tgz", - "integrity": "sha512-WI+WMJ8+oS+LyflqsD4nlXMsVdzTMYTxl16myXPaCXnSgc7LWwMsjxQFZCK/rVmTZ3FN71Ct78ehO9bRC7erYQ==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.54.1.tgz", + "integrity": "sha512-WREHsTz0GqVYLIbzIZYbmUUr95DKEKIXZNH57W3s+4bVnuF1TKe2jH8ZNH8rO1CeMY3U4j4UQeqPNkHMiGem3g==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.54.0", - "@typescript-eslint/utils": "5.54.0", + "@typescript-eslint/typescript-estree": "5.54.1", + "@typescript-eslint/utils": "5.54.1", "debug": "^4.3.4", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.54.0.tgz", - "integrity": "sha512-nExy+fDCBEgqblasfeE3aQ3NuafBUxZxgxXcYfzYRZFHdVvk5q60KhCSkG0noHgHRo/xQ/BOzURLZAafFpTkmQ==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.54.1.tgz", + "integrity": "sha512-G9+1vVazrfAfbtmCapJX8jRo2E4MDXxgm/IMOF4oGh3kq7XuK3JRkOg6y2Qu1VsTRmWETyTkWt1wxy7X7/yLkw==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.0.tgz", - "integrity": "sha512-X2rJG97Wj/VRo5YxJ8Qx26Zqf0RRKsVHd4sav8NElhbZzhpBI8jU54i6hfo9eheumj4oO4dcRN1B/zIVEqR/MQ==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.54.1.tgz", + "integrity": "sha512-bjK5t+S6ffHnVwA0qRPTZrxKSaFYocwFIkZx5k7pvWfsB1I57pO/0M0Skatzzw1sCkjJ83AfGTL0oFIFiDX3bg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.54.0", - "@typescript-eslint/visitor-keys": "5.54.0", + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/visitor-keys": "5.54.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -21552,16 +21552,16 @@ } }, "@typescript-eslint/utils": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.54.0.tgz", - "integrity": "sha512-cuwm8D/Z/7AuyAeJ+T0r4WZmlnlxQ8wt7C7fLpFlKMR+dY6QO79Cq1WpJhvZbMA4ZeZGHiRWnht7ZJ8qkdAunw==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.54.1.tgz", + "integrity": "sha512-IY5dyQM8XD1zfDe5X8jegX6r2EVU5o/WJnLu/znLPWCBF7KNGC+adacXnt5jEYS9JixDcoccI6CvE4RCjHMzCQ==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.54.0", - "@typescript-eslint/types": "5.54.0", - "@typescript-eslint/typescript-estree": "5.54.0", + "@typescript-eslint/scope-manager": "5.54.1", + "@typescript-eslint/types": "5.54.1", + "@typescript-eslint/typescript-estree": "5.54.1", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -21579,12 +21579,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.0.tgz", - "integrity": "sha512-xu4wT7aRCakGINTLGeyGqDn+78BwFlggwBjnHa1ar/KaGagnmwLYmlrXIrgAaQ3AE1Vd6nLfKASm7LrFHNbKGA==", + "version": "5.54.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.54.1.tgz", + "integrity": "sha512-q8iSoHTgwCfgcRJ2l2x+xCbu8nBlRAlsQ33k24Adj8eoVBE0f8dUeI+bAa8F84Mv05UGbAx57g2zrRsYIooqQg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.54.0", + "@typescript-eslint/types": "5.54.1", "eslint-visitor-keys": "^3.3.0" }, "dependencies": { @@ -22088,9 +22088,9 @@ "dev": true }, "axios": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.6.tgz", - "integrity": "sha512-rC/7F08XxZwjMV4iuWv+JpD3E0Ksqg9nac4IIg6RwNuF0JTeWoCo/mBNG54+tNhhI11G3/VDRbdDQTs9hGp4pQ==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", "requires": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -23480,9 +23480,9 @@ "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==" }, "dompurify": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.4.tgz", - "integrity": "sha512-1e2SpqHiRx4DPvmRuXU5J0di3iQACwJM+mFGE2HAkkK7Tbnfk9WcghcAmyWc9CRrjyRRUpmuhPUH6LphQQR3EQ==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.1.tgz", + "integrity": "sha512-60tsgvPKwItxZZdfLmamp0MTcecCta3avOhsLgPZ0qcWt96OasFfhkeIRbJ6br5i0fQawT1/RBGB5L58/Jpwuw==" }, "domutils": { "version": "1.7.0", @@ -31951,19 +31951,19 @@ } }, "stylelint-order": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.2.tgz", - "integrity": "sha512-yuac0BE6toHd27wUPvYVVQicAJthKFIv1HPQFH3Q0dExiO3Z6Uam7geoO0tUd5Z9ddsATYK++1qWNDX4RxMH5Q==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-6.0.3.tgz", + "integrity": "sha512-1j1lOb4EU/6w49qZeT2SQVJXm0Ht+Qnq9GMfUa3pMwoyojIWfuA+JUDmoR97Bht1RLn4ei0xtLGy87M7d29B1w==", "dev": true, "requires": { "postcss": "^8.4.21", - "postcss-sorting": "^8.0.1" + "postcss-sorting": "^8.0.2" }, "dependencies": { "postcss-sorting": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.1.tgz", - "integrity": "sha512-go9Zoxx7KQH+uLrJ9xa5wRErFeXu01ydA6O8m7koPXkmAN7Ts//eRcIqjo0stBR4+Nir2gMYDOWAOx7O5EPUZA==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-8.0.2.tgz", + "integrity": "sha512-M9dkSrmU00t/jK7rF6BZSZauA5MAaBW4i5EnJXspMwt4iqTh/L9j6fgMnbElEOfyRyfLfVbIHj/R52zHzAPe1Q==", "dev": true, "requires": {} } diff --git a/package.json b/package.json index bc19c04a9e..685445a010 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "@types/lodash-es": "4.17.6", "@types/react": "17.0.53", "@types/react-dom": "17.0.19", - "@typescript-eslint/eslint-plugin": "5.54.0", - "@typescript-eslint/parser": "5.54.0", + "@typescript-eslint/eslint-plugin": "5.54.1", + "@typescript-eslint/parser": "5.54.1", "@uupaa/dynamic-import-polyfill": "1.0.2", "autoprefixer": "10.4.13", "babel-loader": "9.1.2", @@ -56,7 +56,7 @@ "stylelint": "15.2.0", "stylelint-config-rational-order": "0.1.2", "stylelint-no-browser-hacks": "1.2.1", - "stylelint-order": "6.0.2", + "stylelint-order": "6.0.3", "stylelint-scss": "4.4.0", "ts-loader": "9.4.2", "typescript": "4.9.5", @@ -82,7 +82,7 @@ "classnames": "2.3.2", "core-js": "3.29.0", "date-fns": "2.29.3", - "dompurify": "2.4.4", + "dompurify": "3.0.1", "epubjs": "0.4.2", "escape-html": "1.0.3", "fast-text-encoding": "1.0.6", @@ -131,7 +131,6 @@ "scripts": { "start": "npm run serve", "serve": "webpack serve --config webpack.dev.js", - "prepare": "node ./scripts/prepare.js", "build:development": "webpack --config webpack.dev.js", "build:production": "cross-env NODE_ENV=\"production\" webpack --config webpack.prod.js", "build:check": "tsc --noEmit", diff --git a/scripts/prepare.js b/scripts/prepare.js deleted file mode 100755 index 898b105e2f..0000000000 --- a/scripts/prepare.js +++ /dev/null @@ -1,12 +0,0 @@ -const { execSync } = require('child_process'); - -/** - * The npm `prepare` script needs to run a build to support installing - * a package from git repositories (this is dumb but a limitation of how - * npm behaves). We don't want to run these in CI though because - * building is slow so this script will skip the build when the - * `SKIP_PREPARE` environment variable has been set. - */ -if (!process.env.SKIP_PREPARE) { - execSync('webpack --config webpack.prod.js', { stdio: 'inherit' }); -} diff --git a/src/components/accessSchedule/accessSchedule.js b/src/components/accessSchedule/accessSchedule.js index 4db600ecef..33df97532e 100644 --- a/src/components/accessSchedule/accessSchedule.js +++ b/src/components/accessSchedule/accessSchedule.js @@ -19,7 +19,7 @@ import template from './accessSchedule.template.html'; const pct = hours % 1; if (pct) { - minutes = parseInt(60 * pct); + minutes = parseInt(60 * pct, 10); } return datetime.getDisplayTime(new Date(2000, 1, 1, hours, minutes, 0, 0)); diff --git a/src/components/activitylog.js b/src/components/activitylog.js index 82f58e1879..54de02b97c 100644 --- a/src/components/activitylog.js +++ b/src/components/activitylog.js @@ -64,10 +64,10 @@ import { toBoolean } from '../utils/string.ts'; function reloadData(instance, elem, apiClient, startIndex, limit) { if (startIndex == null) { - startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0'); + startIndex = parseInt(elem.getAttribute('data-activitystartindex') || '0', 10); } - limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7'); + limit = limit || parseInt(elem.getAttribute('data-activitylimit') || '7', 10); const minDate = new Date(); const hasUserId = toBoolean(elem.getAttribute('data-useractivity'), true); diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index a0c4aaf09b..dfbc0a6768 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -18,6 +18,7 @@ import browser from '../../scripts/browser'; import { playbackManager } from '../playback/playbackmanager'; import itemShortcuts from '../shortcuts'; import imageHelper from '../../scripts/imagehelper'; +import { randomInt } from '../../utils/number.ts'; import './card.scss'; import '../../elements/emby-button/paper-icon-button-light'; import '../guide/programs.scss'; @@ -640,16 +641,6 @@ import { appRouter } from '../appRouter'; }; } - /** - * Generates a random integer in a given range. - * @param {number} min - Minimum of the range. - * @param {number} max - Maximum of the range. - * @returns {number} Randomly generated number. - */ - function getRandomInt(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; - } - /** * Generates an index used to select the default color of a card based on a string. * @param {?string} [str] - String to use for generating the index. @@ -663,13 +654,13 @@ import { appRouter } from '../appRouter'; const character = String(str.slice(charIndex, charIndex + 1).charCodeAt()); let sum = 0; for (let i = 0; i < character.length; i++) { - sum += parseInt(character.charAt(i)); + sum += parseInt(character.charAt(i), 10); } const index = String(sum).slice(-1); return (index % numRandomColors) + 1; } else { - return getRandomInt(1, numRandomColors); + return randomInt(1, numRandomColors); } } diff --git a/src/components/displaySettings/displaySettings.js b/src/components/displaySettings/displaySettings.js index f351368a9a..195c919e9b 100644 --- a/src/components/displaySettings/displaySettings.js +++ b/src/components/displaySettings/displaySettings.js @@ -8,6 +8,7 @@ import datetime from '../../scripts/datetime'; import globalize from '../../scripts/globalize'; import loading from '../loading/loading'; import skinManager from '../../scripts/themeManager'; +import { PluginType } from '../../types/plugin.ts'; import Events from '../../utils/events.ts'; import '../../elements/emby-select/emby-select'; import '../../elements/emby-checkbox/emby-checkbox'; @@ -35,7 +36,7 @@ import template from './displaySettings.template.html'; function loadScreensavers(context, userSettings) { const selectScreensaver = context.querySelector('.selectScreensaver'); - const options = pluginManager.ofType('screensaver').map(plugin => { + const options = pluginManager.ofType(PluginType.Screensaver).map(plugin => { return { name: plugin.name, value: plugin.id diff --git a/src/components/groupedcards.js b/src/components/groupedcards.js index a90423a6b0..295c3170cc 100644 --- a/src/components/groupedcards.js +++ b/src/components/groupedcards.js @@ -13,7 +13,7 @@ import ServerConnections from './ServerConnections'; const playedIndicator = card.querySelector('.playedIndicator'); const playedIndicatorHtml = playedIndicator ? playedIndicator.innerHTML : null; const options = { - Limit: parseInt(playedIndicatorHtml || '10'), + Limit: parseInt(playedIndicatorHtml || '10', 10), Fields: 'PrimaryImageAspectRatio,DateCreated', ParentId: itemId, GroupItems: false diff --git a/src/components/guide/guide.js b/src/components/guide/guide.js index 63a682ee65..36137af21d 100644 --- a/src/components/guide/guide.js +++ b/src/components/guide/guide.js @@ -1149,12 +1149,12 @@ function Guide(options) { guideContext.querySelector('.guideDateTabs').addEventListener('tabchange', function (e) { const allTabButtons = e.target.querySelectorAll('.guide-date-tab-button'); - const tabButton = allTabButtons[parseInt(e.detail.selectedTabIndex)]; + const tabButton = allTabButtons[parseInt(e.detail.selectedTabIndex, 10)]; if (tabButton) { - const previousButton = e.detail.previousIndex == null ? null : allTabButtons[parseInt(e.detail.previousIndex)]; + const previousButton = e.detail.previousIndex == null ? null : allTabButtons[parseInt(e.detail.previousIndex, 10)]; const date = new Date(); - date.setTime(parseInt(tabButton.getAttribute('data-date'))); + date.setTime(parseInt(tabButton.getAttribute('data-date'), 10)); const scrollWidth = programGrid.scrollWidth; let scrollToTimeMs; @@ -1166,7 +1166,7 @@ function Guide(options) { if (previousButton) { const previousDate = new Date(); - previousDate.setTime(parseInt(previousButton.getAttribute('data-date'))); + previousDate.setTime(parseInt(previousButton.getAttribute('data-date'), 10)); scrollToTimeMs += (previousDate.getHours() * 60 * 60 * 1000); scrollToTimeMs += (previousDate.getMinutes() * 60 * 1000); diff --git a/src/components/imageeditor/imageeditor.js b/src/components/imageeditor/imageeditor.js index 9130bd311c..1711169dbc 100644 --- a/src/components/imageeditor/imageeditor.js +++ b/src/components/imageeditor/imageeditor.js @@ -278,9 +278,9 @@ import template from './imageeditor.template.html'; const apiClient = ServerConnections.getApiClient(serverId); const type = imageCard.getAttribute('data-imagetype'); - const index = parseInt(imageCard.getAttribute('data-index')); - const providerCount = parseInt(imageCard.getAttribute('data-providers')); - const numImages = parseInt(imageCard.getAttribute('data-numimages')); + const index = parseInt(imageCard.getAttribute('data-index'), 10); + const providerCount = parseInt(imageCard.getAttribute('data-providers'), 10); + const numImages = parseInt(imageCard.getAttribute('data-numimages'), 10); import('../actionSheet/actionSheet').then(({default: actionSheet}) => { const commands = []; @@ -385,7 +385,7 @@ import template from './imageeditor.template.html'; addListeners(context, 'btnDeleteImage', 'click', function () { const type = this.getAttribute('data-imagetype'); let index = this.getAttribute('data-index'); - index = index === 'null' ? null : parseInt(index); + index = index === 'null' ? null : parseInt(index, 10); const apiClient = ServerConnections.getApiClient(currentItem.ServerId); deleteImage(context, currentItem.Id, type, index, apiClient, true); }); diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index e1c1545b69..e0bcb1562b 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -15,7 +15,6 @@ import toast from './toast/toast'; const user = options.user; const canPlay = playbackManager.canPlay(item); - const restrictOptions = (browser.operaTv || browser.web0s) && !user.Policy.IsAdministrator; const commands = []; @@ -99,8 +98,8 @@ import toast from './toast/toast'; }); } - if (!restrictOptions) { - if (itemHelper.supportsAddingToCollection(item)) { + if (!browser.tv) { + if (itemHelper.supportsAddingToCollection(item) && options.EnableCollectionManagement) { commands.push({ name: globalize.translate('AddToCollection'), id: 'addtocollection', @@ -272,7 +271,7 @@ import toast from './toast/toast'; }); } - if (!restrictOptions && options.share === true && itemHelper.canShare(item, user)) { + if (!browser.tv && options.share === true && itemHelper.canShare(item, user)) { commands.push({ name: globalize.translate('Share'), id: 'share', diff --git a/src/components/itemMediaInfo/itemMediaInfo.js b/src/components/itemMediaInfo/itemMediaInfo.js index 1a05d40849..ac04df4815 100644 --- a/src/components/itemMediaInfo/itemMediaInfo.js +++ b/src/components/itemMediaInfo/itemMediaInfo.js @@ -138,7 +138,7 @@ const attributeDelimiterHtml = layoutManager.tv ? '' : ': { diff --git a/src/components/mediaLibraryEditor/mediaLibraryEditor.js b/src/components/mediaLibraryEditor/mediaLibraryEditor.js index ed54675016..c90acc0078 100644 --- a/src/components/mediaLibraryEditor/mediaLibraryEditor.js +++ b/src/components/mediaLibraryEditor/mediaLibraryEditor.js @@ -93,7 +93,7 @@ import template from './mediaLibraryEditor.template.html'; const listItem = dom.parentWithClass(e.target, 'listItem'); if (listItem) { - const index = parseInt(listItem.getAttribute('data-index')); + const index = parseInt(listItem.getAttribute('data-index'), 10); const pathInfos = (currentOptions.library.LibraryOptions || {}).PathInfos || []; const pathInfo = index == null ? {} : pathInfos[index] || {}; const originalPath = pathInfo.Path || (index == null ? null : currentOptions.library.Locations[index]); diff --git a/src/components/metadataEditor/metadataEditor.js b/src/components/metadataEditor/metadataEditor.js index 6456c5721d..fb69d20aa2 100644 --- a/src/components/metadataEditor/metadataEditor.js +++ b/src/components/metadataEditor/metadataEditor.js @@ -357,14 +357,14 @@ import template from './metadataEditor.template.html'; let index; const btnDeletePerson = dom.parentWithClass(e.target, 'btnDeletePerson'); if (btnDeletePerson) { - index = parseInt(btnDeletePerson.getAttribute('data-index')); + index = parseInt(btnDeletePerson.getAttribute('data-index'), 10); currentItem.People.splice(index, 1); populatePeople(context, currentItem.People); } const btnEditPerson = dom.parentWithClass(e.target, 'btnEditPerson'); if (btnEditPerson) { - index = parseInt(btnEditPerson.getAttribute('data-index')); + index = parseInt(btnEditPerson.getAttribute('data-index'), 10); editPerson(context, currentItem.People[index], index); } }); diff --git a/src/components/playback/mediasession.js b/src/components/playback/mediasession.js index 089820fb47..d16fe7756f 100644 --- a/src/components/playback/mediasession.js +++ b/src/components/playback/mediasession.js @@ -114,8 +114,8 @@ import shell from '../../scripts/shell'; const itemId = item.Id; // Convert to ms - const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0); - const currentTime = parseInt(playState.PositionTicks ? (playState.PositionTicks / 10000) : 0); + const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10); + const currentTime = parseInt(playState.PositionTicks ? (playState.PositionTicks / 10000) : 0, 10); const isPaused = playState.IsPaused || false; const canSeek = playState.CanSeek || false; @@ -247,7 +247,7 @@ import shell from '../../scripts/shell'; navigator.mediaSession.setActionHandler('seekto', function (object) { const item = playbackManager.getPlayerState(currentPlayer).NowPlayingItem; // Convert to ms - const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0); + const duration = parseInt(item.RunTimeTicks ? (item.RunTimeTicks / 10000) : 0, 10); const wantedTime = object.seekTime * 1000; playbackManager.seekPercent(wantedTime / duration * 100, currentPlayer); }); diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 01501923f3..0d0434c972 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -11,6 +11,7 @@ import { appHost } from '../apphost'; import Screenfull from 'screenfull'; import ServerConnections from '../ServerConnections'; import alert from '../alert'; +import { PluginType } from '../../types/plugin.ts'; import { includesAny } from '../../utils/container.ts'; import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts'; @@ -1693,7 +1694,7 @@ class PlaybackManager { function changeStream(player, ticks, params) { if (canPlayerSeek(player) && params == null) { - player.currentTime(parseInt(ticks / 10000)); + player.currentTime(parseInt(ticks / 10000, 10)); return; } @@ -1717,7 +1718,7 @@ class PlaybackManager { const apiClient = ServerConnections.getApiClient(currentItem.ServerId); if (ticks) { - ticks = parseInt(ticks); + ticks = parseInt(ticks, 10); } const maxBitrate = params.MaxStreamingBitrate || self.getMaxStreamingBitrate(player); @@ -2268,7 +2269,7 @@ class PlaybackManager { function runInterceptors(item, playOptions) { return new Promise(function (resolve, reject) { - const interceptors = pluginManager.ofType('preplayintercept'); + const interceptors = pluginManager.ofType(PluginType.PreplayIntercept); interceptors.sort(function (a, b) { return (a.order || 0) - (b.order || 0); @@ -3429,12 +3430,12 @@ class PlaybackManager { } Events.on(pluginManager, 'registered', function (e, plugin) { - if (plugin.type === 'mediaplayer') { + if (plugin.type === PluginType.MediaPlayer) { initMediaPlayer(plugin); } }); - pluginManager.ofType('mediaplayer').forEach(initMediaPlayer); + pluginManager.ofType(PluginType.MediaPlayer).forEach(initMediaPlayer); function sendProgressUpdate(player, progressEventName, reportPlaylist) { if (!player) { @@ -3649,7 +3650,7 @@ class PlaybackManager { percent /= 100; ticks *= percent; - this.seek(parseInt(ticks), player); + this.seek(parseInt(ticks, 10), player); } seekMs(ms, player = this._currentPlayer) { @@ -4036,13 +4037,13 @@ class PlaybackManager { this.setBrightness(cmd.Arguments.Brightness, player); break; case 'SetAudioStreamIndex': - this.setAudioStreamIndex(parseInt(cmd.Arguments.Index), player); + this.setAudioStreamIndex(parseInt(cmd.Arguments.Index, 10), player); break; case 'SetSubtitleStreamIndex': - this.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index), player); + this.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index, 10), player); break; case 'SetMaxStreamingBitrate': - this.setMaxStreamingBitrate(parseInt(cmd.Arguments.Bitrate), player); + this.setMaxStreamingBitrate(parseInt(cmd.Arguments.Bitrate, 10), player); break; case 'ToggleFullscreen': this.toggleFullscreen(player); diff --git a/src/components/playback/playersettingsmenu.js b/src/components/playback/playersettingsmenu.js index c7f8f5a6da..da3be20970 100644 --- a/src/components/playback/playersettingsmenu.js +++ b/src/components/playback/playersettingsmenu.js @@ -43,7 +43,7 @@ function showQualityMenu(player, btn) { items: menuItems, positionTo: btn }).then(function (id) { - const bitrate = parseInt(id); + const bitrate = parseInt(id, 10); if (bitrate !== selectedBitrate) { playbackManager.setMaxStreamingBitrate({ enableAutomaticBitrateDetection: bitrate ? false : true, diff --git a/src/components/playback/playqueuemanager.js b/src/components/playback/playqueuemanager.js index a1ed503372..65f20a71a2 100644 --- a/src/components/playback/playqueuemanager.js +++ b/src/components/playback/playqueuemanager.js @@ -1,4 +1,4 @@ -/*eslint prefer-const: "error"*/ +import { randomInt } from '../../utils/number.ts'; let currentId = 0; function addUniquePlaylistItemId(item) { @@ -58,7 +58,7 @@ class PlayQueueManager { const currentPlaylistItem = this._playlist.splice(this.getCurrentPlaylistIndex(), 1)[0]; for (let i = this._playlist.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * i); + const j = randomInt(0, i - 1); const temp = this._playlist[i]; this._playlist[i] = this._playlist[j]; this._playlist[j] = temp; diff --git a/src/components/recordingcreator/recordingfields.js b/src/components/recordingcreator/recordingfields.js index a666a49562..742a77df4b 100644 --- a/src/components/recordingcreator/recordingfields.js +++ b/src/components/recordingcreator/recordingfields.js @@ -12,8 +12,6 @@ import ServerConnections from '../ServerConnections'; import toast from '../toast/toast'; import template from './recordingfields.template.html'; -/*eslint prefer-const: "error"*/ - function loadData(parent, program) { if (program.IsSeries) { parent.querySelector('.recordSeriesContainer').classList.remove('hide'); diff --git a/src/components/recordingcreator/recordinghelper.js b/src/components/recordingcreator/recordinghelper.js index 1780668afd..a65ae006dc 100644 --- a/src/components/recordingcreator/recordinghelper.js +++ b/src/components/recordingcreator/recordinghelper.js @@ -5,8 +5,6 @@ import toast from '../toast/toast'; import confirm from '../confirm/confirm'; import dialog from '../dialog/dialog'; -/*eslint prefer-const: "error"*/ - function changeRecordingToSeries(apiClient, timerId, programId, confirmTimerCancellation) { loading.show(); diff --git a/src/components/recordingcreator/seriesrecordingeditor.js b/src/components/recordingcreator/seriesrecordingeditor.js index 590c600e74..79d35b9fe1 100644 --- a/src/components/recordingcreator/seriesrecordingeditor.js +++ b/src/components/recordingcreator/seriesrecordingeditor.js @@ -17,8 +17,6 @@ import '../../styles/flexstyles.scss'; import ServerConnections from '../ServerConnections'; import template from './seriesrecordingeditor.template.html'; -/*eslint prefer-const: "error"*/ - let currentDialog; let recordingUpdated = false; let recordingDeleted = false; diff --git a/src/components/refreshdialog/refreshdialog.js b/src/components/refreshdialog/refreshdialog.js index 27ce6b97cf..eac336389f 100644 --- a/src/components/refreshdialog/refreshdialog.js +++ b/src/components/refreshdialog/refreshdialog.js @@ -13,8 +13,6 @@ import '../formdialog.scss'; import ServerConnections from '../ServerConnections'; import toast from '../toast/toast'; -/*eslint prefer-const: "error"*/ - function getEditorHtml() { let html = ''; diff --git a/src/components/remotecontrol/remotecontrol.js b/src/components/remotecontrol/remotecontrol.js index cce2cf7b43..26b7220bce 100644 --- a/src/components/remotecontrol/remotecontrol.js +++ b/src/components/remotecontrol/remotecontrol.js @@ -20,8 +20,6 @@ import ServerConnections from '../ServerConnections'; import toast from '../toast/toast'; import { appRouter } from '../appRouter'; -/*eslint prefer-const: "error"*/ - let showMuteButton = true; let showVolumeSlider = true; @@ -46,7 +44,7 @@ function showAudioMenu(context, player, button) { items: menuItems, positionTo: button, callback: function (id) { - playbackManager.setAudioStreamIndex(parseInt(id), player); + playbackManager.setAudioStreamIndex(parseInt(id, 10), player); } }); }); @@ -78,7 +76,7 @@ function showSubtitleMenu(context, player, button) { items: menuItems, positionTo: button, callback: function (id) { - playbackManager.setSubtitleStreamIndex(parseInt(id), player); + playbackManager.setSubtitleStreamIndex(parseInt(id, 10), player); } }); }); diff --git a/src/components/shortcuts.js b/src/components/shortcuts.js index bf8d1b74fe..60e51c784c 100644 --- a/src/components/shortcuts.js +++ b/src/components/shortcuts.js @@ -150,7 +150,7 @@ import toast from './toast/toast'; StartDate: card.getAttribute('data-startdate'), EndDate: card.getAttribute('data-enddate'), UserData: { - PlaybackPositionTicks: parseInt(card.getAttribute('data-positionticks') || '0') + PlaybackPositionTicks: parseInt(card.getAttribute('data-positionticks') || '0', 10) } }; } @@ -201,7 +201,7 @@ import toast from './toast/toast'; ServerId: serverId }); } else if (action === 'play' || action === 'resume') { - const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0'); + const startPositionTicks = parseInt(card.getAttribute('data-positionticks') || '0', 10); if (playbackManager.canPlay(item)) { playbackManager.play({ diff --git a/src/controllers/dashboard/dlna/profile.js b/src/controllers/dashboard/dlna/profile.js index 3f5d6c365e..4d942f7cdc 100644 --- a/src/controllers/dashboard/dlna/profile.js +++ b/src/controllers/dashboard/dlna/profile.js @@ -100,7 +100,7 @@ import { getParameterByName } from '../../../utils/url.ts'; }).join('') + ''; const elem = $('.httpHeaderIdentificationList', page).html(html).trigger('create'); $('.btnDeleteIdentificationHeader', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index')); + const itemIndex = parseInt(this.getAttribute('data-index'), 10); currentProfile.Identification.Headers.splice(itemIndex, 1); renderIdentificationHeaders(page, currentProfile.Identification.Headers); }); @@ -154,7 +154,7 @@ import { getParameterByName } from '../../../utils/url.ts'; }).join('') + ''; const elem = $('.xmlDocumentAttributeList', page).html(html).trigger('create'); $('.btnDeleteXmlAttribute', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index')); + const itemIndex = parseInt(this.getAttribute('data-index'), 10); currentProfile.XmlRootAttributes.splice(itemIndex, 1); renderXmlDocumentAttributes(page, currentProfile.XmlRootAttributes); }); @@ -198,12 +198,12 @@ import { getParameterByName } from '../../../utils/url.ts'; }).join('') + ''; const elem = $('.subtitleProfileList', page).html(html).trigger('create'); $('.btnDeleteProfile', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index')); + const itemIndex = parseInt(this.getAttribute('data-index'), 10); currentProfile.SubtitleProfiles.splice(itemIndex, 1); renderSubtitleProfiles(page, currentProfile.SubtitleProfiles); }); $('.lnkEditSubProfile', elem).on('click', function () { - const itemIndex = parseInt(this.getAttribute('data-index')); + const itemIndex = parseInt(this.getAttribute('data-index'), 10); editSubtitleProfile(page, currentProfile.SubtitleProfiles[itemIndex]); }); } @@ -292,7 +292,7 @@ import { getParameterByName } from '../../../utils/url.ts'; deleteDirectPlayProfile(page, index); }); $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex')); + const index = parseInt(this.getAttribute('data-profileindex'), 10); editDirectPlayProfile(page, currentProfile.DirectPlayProfiles[index]); }); } @@ -353,7 +353,7 @@ import { getParameterByName } from '../../../utils/url.ts'; deleteTranscodingProfile(page, index); }); $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex')); + const index = parseInt(this.getAttribute('data-profileindex'), 10); editTranscodingProfile(page, currentProfile.TranscodingProfiles[index]); }); } @@ -437,7 +437,7 @@ import { getParameterByName } from '../../../utils/url.ts'; deleteContainerProfile(page, index); }); $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex')); + const index = parseInt(this.getAttribute('data-profileindex'), 10); editContainerProfile(page, currentProfile.ContainerProfiles[index]); }); } @@ -509,7 +509,7 @@ import { getParameterByName } from '../../../utils/url.ts'; deleteCodecProfile(page, index); }); $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex')); + const index = parseInt(this.getAttribute('data-profileindex'), 10); editCodecProfile(page, currentProfile.CodecProfiles[index]); }); } @@ -589,7 +589,7 @@ import { getParameterByName } from '../../../utils/url.ts'; deleteResponseProfile(page, index); }); $('.lnkEditSubProfile', elem).on('click', function () { - const index = parseInt(this.getAttribute('data-profileindex')); + const index = parseInt(this.getAttribute('data-profileindex'), 10); editResponseProfile(page, currentProfile.ResponseProfiles[index]); }); } diff --git a/src/controllers/dashboard/encodingsettings.js b/src/controllers/dashboard/encodingsettings.js index 67001ccb82..b8c89726cd 100644 --- a/src/controllers/dashboard/encodingsettings.js +++ b/src/controllers/dashboard/encodingsettings.js @@ -98,8 +98,8 @@ import alert from '../../components/alert'; config.VppTonemappingBrightness = form.querySelector('#txtVppTonemappingBrightness').value; config.VppTonemappingContrast = form.querySelector('#txtVppTonemappingContrast').value; config.EncoderPreset = form.querySelector('#selectEncoderPreset').value; - config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0'); - config.H265Crf = parseInt(form.querySelector('#txtH265Crf').value || '0'); + config.H264Crf = parseInt(form.querySelector('#txtH264Crf').value || '0', 10); + config.H265Crf = parseInt(form.querySelector('#txtH265Crf').value || '0', 10); config.DeinterlaceMethod = form.querySelector('#selectDeinterlaceMethod').value; config.DeinterlaceDoubleRate = form.querySelector('#chkDoubleRateDeinterlacing').checked; config.EnableSubtitleExtraction = form.querySelector('#chkEnableSubtitleExtraction').checked; diff --git a/src/controllers/dashboard/library.js b/src/controllers/dashboard/library.js index 24e7a477d1..49ff203902 100644 --- a/src/controllers/dashboard/library.js +++ b/src/controllers/dashboard/library.js @@ -92,7 +92,7 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder'; function showCardMenu(page, elem, virtualFolders) { const card = dom.parentWithClass(elem, 'card'); - const index = parseInt(card.getAttribute('data-index')); + const index = parseInt(card.getAttribute('data-index'), 10); const virtualFolder = virtualFolders[index]; const menuItems = []; menuItems.push({ @@ -192,7 +192,7 @@ import cardBuilder from '../../components/cardbuilder/cardBuilder'; }); $('.editLibrary', divVirtualFolders).on('click', function () { const card = $(this).parents('.card')[0]; - const index = parseInt(card.getAttribute('data-index')); + const index = parseInt(card.getAttribute('data-index'), 10); const virtualFolder = virtualFolders[index]; if (virtualFolder.ItemId) { diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtask.js b/src/controllers/dashboard/scheduledtasks/scheduledtask.js index c326a689fc..64db2e4292 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtask.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtask.js @@ -235,7 +235,7 @@ import { getParameterByName } from '../../../utils/url.ts'; const btnDeleteTrigger = dom.parentWithClass(e.target, 'btnDeleteTrigger'); if (btnDeleteTrigger) { - ScheduledTaskPage.confirmDeleteTrigger(view, parseInt(btnDeleteTrigger.getAttribute('data-index'))); + ScheduledTaskPage.confirmDeleteTrigger(view, parseInt(btnDeleteTrigger.getAttribute('data-index'), 10)); } }); view.addEventListener('viewshow', function () { diff --git a/src/controllers/dashboard/streaming.js b/src/controllers/dashboard/streaming.js index bc640a1f68..8832e4bff4 100644 --- a/src/controllers/dashboard/streaming.js +++ b/src/controllers/dashboard/streaming.js @@ -15,7 +15,7 @@ import Dashboard from '../../utils/dashboard'; loading.show(); const form = this; ApiClient.getServerConfiguration().then(function (config) { - config.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', form).val() || '0')); + config.RemoteClientBitrateLimit = parseInt(1e6 * parseFloat($('#txtRemoteClientBitrateLimit', form).val() || '0'), 10); ApiClient.updateServerConfiguration(config).then(Dashboard.processServerConfigurationUpdateResult); }); diff --git a/src/controllers/livetv/livetvsuggested.js b/src/controllers/livetv/livetvsuggested.js index 4deb2b713a..8b97984d0a 100644 --- a/src/controllers/livetv/livetvsuggested.js +++ b/src/controllers/livetv/livetvsuggested.js @@ -222,17 +222,17 @@ export default function (view, params) { } function onBeforeTabChange(evt) { - preLoadTab(view, parseInt(evt.detail.selectedTabIndex)); + preLoadTab(view, parseInt(evt.detail.selectedTabIndex, 10)); } function onTabChange(evt) { - const previousTabController = tabControllers[parseInt(evt.detail.previousIndex)]; + const previousTabController = tabControllers[parseInt(evt.detail.previousIndex, 10)]; if (previousTabController && previousTabController.onHide) { previousTabController.onHide(); } - loadTab(view, parseInt(evt.detail.selectedTabIndex)); + loadTab(view, parseInt(evt.detail.selectedTabIndex, 10)); } function getTabContainers() { @@ -339,7 +339,7 @@ export default function (view, params) { let isViewRestored; const self = this; - let currentTabIndex = parseInt(params.tab || getDefaultTabIndex('livetv')); + let currentTabIndex = parseInt(params.tab || getDefaultTabIndex('livetv'), 10); let initialTabIndex = currentTabIndex; let lastFullRender = 0; [].forEach.call(view.querySelectorAll('.sectionTitleTextButton-programs'), function (link) { diff --git a/src/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js index 3ea00a9a8c..bbb154a4e1 100644 --- a/src/controllers/music/musicrecommended.js +++ b/src/controllers/music/musicrecommended.js @@ -245,11 +245,11 @@ import Dashboard from '../../utils/dashboard'; } function onBeforeTabChange(e) { - preLoadTab(view, parseInt(e.detail.selectedTabIndex)); + preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10)); } function onTabChange(e) { - loadTab(view, parseInt(e.detail.selectedTabIndex)); + loadTab(view, parseInt(e.detail.selectedTabIndex, 10)); } function getTabContainers() { @@ -350,7 +350,7 @@ import Dashboard from '../../utils/dashboard'; } } - let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId)); + let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10); const suggestionsTabIndex = 1; this.initTab = function () { diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index cacea1a304..d7bbab581d 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -975,7 +975,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components title: globalize.translate('Audio'), positionTo: positionTo }).then(function (id) { - const index = parseInt(id); + const index = parseInt(id, 10); if (index !== currentIndex) { playbackManager.setAudioStreamIndex(index, player); @@ -1022,7 +1022,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components positionTo }).then(function (id) { if (id) { - const index = parseInt(id); + const index = parseInt(id, 10); if (index !== currentIndex) { playbackManager.setSecondarySubtitleStreamIndex(index, player); } @@ -1099,7 +1099,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components console.error(e); } } else { - const index = parseInt(id); + const index = parseInt(id, 10); if (index !== currentIndex) { playbackManager.setSubtitleStreamIndex(index, player); @@ -1633,7 +1633,7 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components ms /= 100; ms *= value; ms += programStartDateMs; - return '

' + getDisplayTimeWithoutAmPm(new Date(parseInt(ms)), true) + '

'; + return '

' + getDisplayTimeWithoutAmPm(new Date(parseInt(ms, 10)), true) + '

'; } return '--:--'; diff --git a/src/controllers/shows/tvrecommended.js b/src/controllers/shows/tvrecommended.js index 5d9b3e2e0b..d1e53b38de 100644 --- a/src/controllers/shows/tvrecommended.js +++ b/src/controllers/shows/tvrecommended.js @@ -224,11 +224,11 @@ import autoFocuser from '../../components/autoFocuser'; export default function (view, params) { function onBeforeTabChange(e) { - preLoadTab(view, parseInt(e.detail.selectedTabIndex)); + preLoadTab(view, parseInt(e.detail.selectedTabIndex, 10)); } function onTabChange(e) { - const newIndex = parseInt(e.detail.selectedTabIndex); + const newIndex = parseInt(e.detail.selectedTabIndex, 10); loadTab(view, newIndex); } @@ -340,7 +340,7 @@ import autoFocuser from '../../components/autoFocuser'; } const self = this; - let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId)); + let currentTabIndex = parseInt(params.tab || getDefaultTabIndex(params.topParentId), 10); const suggestionsTabIndex = 1; self.initTab = function () { diff --git a/src/elements/emby-itemscontainer/emby-itemscontainer.js b/src/elements/emby-itemscontainer/emby-itemscontainer.js index 00e2e94d3a..ae0f0fef54 100644 --- a/src/elements/emby-itemscontainer/emby-itemscontainer.js +++ b/src/elements/emby-itemscontainer/emby-itemscontainer.js @@ -414,7 +414,7 @@ import Sortable from 'sortablejs'; clearRefreshInterval(itemsContainer); if (!intervalMs) { - intervalMs = parseInt(itemsContainer.getAttribute('data-refreshinterval') || '0'); + intervalMs = parseInt(itemsContainer.getAttribute('data-refreshinterval') || '0', 10); } if (intervalMs) { diff --git a/src/elements/emby-progressbar/emby-progressbar.js b/src/elements/emby-progressbar/emby-progressbar.js index e232bbcde0..cad2f4bbfa 100644 --- a/src/elements/emby-progressbar/emby-progressbar.js +++ b/src/elements/emby-progressbar/emby-progressbar.js @@ -3,8 +3,8 @@ const ProgressBarPrototype = Object.create(HTMLDivElement.prototype); function onAutoTimeProgress() { - const start = parseInt(this.getAttribute('data-starttime')); - const end = parseInt(this.getAttribute('data-endtime')); + const start = parseInt(this.getAttribute('data-starttime'), 10); + const end = parseInt(this.getAttribute('data-endtime'), 10); const now = new Date().getTime(); const total = end - start; diff --git a/src/elements/emby-scrollbuttons/emby-scrollbuttons.js b/src/elements/emby-scrollbuttons/emby-scrollbuttons.js index e7fa1df29f..3d98918d7a 100644 --- a/src/elements/emby-scrollbuttons/emby-scrollbuttons.js +++ b/src/elements/emby-scrollbuttons/emby-scrollbuttons.js @@ -91,7 +91,7 @@ const EmbyScrollButtonsPrototype = Object.create(HTMLDivElement.prototype); return 0; } - value = parseInt(value); + value = parseInt(value, 10); if (isNaN(value)) { return 0; } diff --git a/src/elements/emby-scroller/Scroller.tsx b/src/elements/emby-scroller/Scroller.tsx index 6cd83fc784..14313318a6 100644 --- a/src/elements/emby-scroller/Scroller.tsx +++ b/src/elements/emby-scroller/Scroller.tsx @@ -75,7 +75,7 @@ const Scroller: FC = ({ return 0; } - if (isNaN(parseInt(value))) { + if (isNaN(parseInt(value, 10))) { return 0; } diff --git a/src/elements/emby-tabs/emby-tabs.js b/src/elements/emby-tabs/emby-tabs.js index 9455b5e1db..7c07796f74 100644 --- a/src/elements/emby-tabs/emby-tabs.js +++ b/src/elements/emby-tabs/emby-tabs.js @@ -75,11 +75,11 @@ import '../../styles/scrollstyles.scss'; current.classList.remove(activeButtonClass); } - const previousIndex = current ? parseInt(current.getAttribute('data-index')) : null; + const previousIndex = current ? parseInt(current.getAttribute('data-index'), 10) : null; setActiveTabButton(tabButton); - const index = parseInt(tabButton.getAttribute('data-index')); + const index = parseInt(tabButton.getAttribute('data-index'), 10); triggerBeforeTabChange(tabs, index, previousIndex); @@ -194,7 +194,7 @@ import '../../styles/scrollstyles.scss'; initScroller(this); const current = this.querySelector('.' + activeButtonClass); - const currentIndex = current ? parseInt(current.getAttribute('data-index')) : parseInt(this.getAttribute('data-index') || '0'); + const currentIndex = current ? parseInt(current.getAttribute('data-index'), 10) : parseInt(this.getAttribute('data-index') || '0', 10); if (currentIndex !== -1) { this.selectedTabIndex = currentIndex; diff --git a/src/elements/emby-textarea/emby-textarea.js b/src/elements/emby-textarea/emby-textarea.js index 649578ce16..ed48f415ff 100644 --- a/src/elements/emby-textarea/emby-textarea.js +++ b/src/elements/emby-textarea/emby-textarea.js @@ -14,7 +14,7 @@ function calculateOffset(textarea) { let offset = 0; for (let i = 0; i < props.length; i++) { - offset += parseInt(style[props[i]]); + offset += parseInt(style[props[i]], 10); } return offset; } diff --git a/src/plugins/backdropScreensaver/plugin.js b/src/plugins/backdropScreensaver/plugin.js index 7db319a34e..823ab7d7e0 100644 --- a/src/plugins/backdropScreensaver/plugin.js +++ b/src/plugins/backdropScreensaver/plugin.js @@ -1,10 +1,11 @@ /* eslint-disable indent */ import ServerConnections from '../../components/ServerConnections'; +import { PluginType } from '../../types/plugin.ts'; class BackdropScreensaver { constructor() { this.name = 'Backdrop ScreenSaver'; - this.type = 'screensaver'; + this.type = PluginType.Screensaver; this.id = 'backdropscreensaver'; this.supportsAnonymous = false; } diff --git a/src/plugins/bookPlayer/plugin.js b/src/plugins/bookPlayer/plugin.js index e95c8657b1..df5e213b72 100644 --- a/src/plugins/bookPlayer/plugin.js +++ b/src/plugins/bookPlayer/plugin.js @@ -9,6 +9,7 @@ import TableOfContents from './tableOfContents'; import dom from '../../scripts/dom'; import { translateHtml } from '../../scripts/globalize'; import * as userSettings from '../../scripts/settings/userSettings'; +import { PluginType } from '../../types/plugin.ts'; import Events from '../../utils/events.ts'; import '../../elements/emby-button/paper-icon-button-light'; @@ -19,7 +20,7 @@ import './style.scss'; export class BookPlayer { constructor() { this.name = 'Book Player'; - this.type = 'mediaplayer'; + this.type = PluginType.MediaPlayer; this.id = 'bookplayer'; this.priority = 1; diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index ebc682cbb1..d4aca76a3d 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -5,6 +5,7 @@ import globalize from '../../scripts/globalize'; import castSenderApiLoader from './castSenderApi'; import ServerConnections from '../../components/ServerConnections'; import alert from '../../components/alert'; +import { PluginType } from '../../types/plugin.ts'; import Events from '../../utils/events.ts'; import { getItems } from '../../utils/jellyfin-apiclient/getItems.ts'; @@ -572,7 +573,7 @@ class ChromecastPlayer { constructor() { // playbackManager needs this this.name = PlayerName; - this.type = 'mediaplayer'; + this.type = PluginType.MediaPlayer; this.id = 'chromecast'; this.isLocalPlayer = false; this.lastPlayerData = {}; @@ -686,7 +687,7 @@ class ChromecastPlayer { } seek(position) { - position = parseInt(position); + position = parseInt(position, 10); position = position / 10000000; diff --git a/src/plugins/comicsPlayer/plugin.js b/src/plugins/comicsPlayer/plugin.js index d209069ad3..eb6d3cb11f 100644 --- a/src/plugins/comicsPlayer/plugin.js +++ b/src/plugins/comicsPlayer/plugin.js @@ -6,13 +6,14 @@ import keyboardnavigation from '../../scripts/keyboardNavigation'; import { appRouter } from '../../components/appRouter'; import ServerConnections from '../../components/ServerConnections'; import * as userSettings from '../../scripts/settings/userSettings'; +import { PluginType } from '../../types/plugin.ts'; import './style.scss'; export class ComicsPlayer { constructor() { this.name = 'Comics Player'; - this.type = 'mediaplayer'; + this.type = PluginType.MediaPlayer; this.id = 'comicsplayer'; this.priority = 1; this.imageMap = new Map(); diff --git a/src/plugins/experimentalWarnings/plugin.js b/src/plugins/experimentalWarnings/plugin.js index 563e6264f2..395f906890 100644 --- a/src/plugins/experimentalWarnings/plugin.js +++ b/src/plugins/experimentalWarnings/plugin.js @@ -2,6 +2,7 @@ import globalize from '../../scripts/globalize'; import * as userSettings from '../../scripts/settings/userSettings'; import { appHost } from '../../components/apphost'; import alert from '../../components/alert'; +import { PluginType } from '../../types/plugin.ts'; // TODO: Replace with date-fns // https://stackoverflow.com/questions/6117814/get-week-of-year-in-javascript-like-in-php @@ -46,7 +47,7 @@ function showIsoMessage() { class ExpirementalPlaybackWarnings { constructor() { this.name = 'Experimental playback warnings'; - this.type = 'preplayintercept'; + this.type = PluginType.PreplayIntercept; this.id = 'expirementalplaybackwarnings'; } diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index a1cad660fe..585d935f05 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -3,6 +3,7 @@ import { appHost } from '../../components/apphost'; import * as htmlMediaHelper from '../../components/htmlMediaHelper'; import profileBuilder from '../../scripts/browserDeviceProfile'; import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings'; +import { PluginType } from '../../types/plugin.ts'; import Events from '../../utils/events.ts'; function getDefaultProfile() { @@ -88,7 +89,7 @@ class HtmlAudioPlayer { const self = this; self.name = 'Html Audio Player'; - self.type = 'mediaplayer'; + self.type = PluginType.MediaPlayer; self.id = 'htmlaudioplayer'; // Let any players created by plugins take priority diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index a4f512768f..e624fcd941 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -30,6 +30,7 @@ import ServerConnections from '../../components/ServerConnections'; import profileBuilder, { canPlaySecondaryAudio } from '../../scripts/browserDeviceProfile'; import { getIncludeCorsCredentials } from '../../scripts/settings/webSettings'; import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop'; +import { PluginType } from '../../types/plugin.ts'; import Events from '../../utils/events.ts'; import { includesAny } from '../../utils/container.ts'; import debounce from 'lodash-es/debounce'; @@ -170,7 +171,7 @@ function tryRemoveElement(elem) { /** * @type {string} */ - type = 'mediaplayer'; + type = PluginType.MediaPlayer; /** * @type {string} */ diff --git a/src/plugins/logoScreensaver/plugin.js b/src/plugins/logoScreensaver/plugin.js index 8d5f7031a5..fc999ccccf 100644 --- a/src/plugins/logoScreensaver/plugin.js +++ b/src/plugins/logoScreensaver/plugin.js @@ -1,8 +1,11 @@ +import { PluginType } from '../../types/plugin.ts'; +import { randomInt } from '../../utils/number.ts'; + export default function () { const self = this; self.name = 'Logo ScreenSaver'; - self.type = 'screensaver'; + self.type = PluginType.Screensaver; self.id = 'logoscreensaver'; self.supportsAnonymous = true; @@ -23,16 +26,12 @@ export default function () { const elem = document.querySelector('.logoScreenSaverImage'); if (elem && elem.animate) { - const random = getRandomInt(0, animations.length - 1); + const random = randomInt(0, animations.length - 1); animations[random](elem, 1); } } - function getRandomInt(min, max) { - return Math.floor(Math.random() * (max - min + 1)) + min; - } - function bounceInLeft(elem, iterations) { const keyframes = [ { transform: 'translate3d(-3000px, 0, 0)', opacity: '0', offset: 0 }, diff --git a/src/plugins/pdfPlayer/plugin.js b/src/plugins/pdfPlayer/plugin.js index 5f43d03fc9..1dc59ebdf8 100644 --- a/src/plugins/pdfPlayer/plugin.js +++ b/src/plugins/pdfPlayer/plugin.js @@ -4,6 +4,7 @@ import keyboardnavigation from '../../scripts/keyboardNavigation'; import dialogHelper from '../../components/dialogHelper/dialogHelper'; import dom from '../../scripts/dom'; import { appRouter } from '../../components/appRouter'; +import { PluginType } from '../../types/plugin.ts'; import Events from '../../utils/events.ts'; import './style.scss'; @@ -12,7 +13,7 @@ import '../../elements/emby-button/paper-icon-button-light'; export class PdfPlayer { constructor() { this.name = 'PDF Player'; - this.type = 'mediaplayer'; + this.type = PluginType.MediaPlayer; this.id = 'pdfplayer'; this.priority = 1; @@ -261,7 +262,7 @@ export class PdfPlayer { for (const page of pages) { if (!this.pages[page]) { this.pages[page] = document.createElement('canvas'); - this.renderPage(this.pages[page], parseInt(page.slice(4))); + this.renderPage(this.pages[page], parseInt(page.slice(4), 10)); } } diff --git a/src/plugins/photoPlayer/plugin.js b/src/plugins/photoPlayer/plugin.js index aa3ba5f364..7c41c53188 100644 --- a/src/plugins/photoPlayer/plugin.js +++ b/src/plugins/photoPlayer/plugin.js @@ -1,9 +1,10 @@ import ServerConnections from '../../components/ServerConnections'; +import { PluginType } from '../../types/plugin.ts'; export default class PhotoPlayer { constructor() { this.name = 'Photo Player'; - this.type = 'mediaplayer'; + this.type = PluginType.MediaPlayer; this.id = 'photoplayer'; this.priority = 1; } diff --git a/src/plugins/playAccessValidation/plugin.js b/src/plugins/playAccessValidation/plugin.js index c353ea9096..3081f8cf49 100644 --- a/src/plugins/playAccessValidation/plugin.js +++ b/src/plugins/playAccessValidation/plugin.js @@ -1,6 +1,7 @@ import globalize from '../../scripts/globalize'; import ServerConnections from '../../components/ServerConnections'; import alert from '../../components/alert'; +import { PluginType } from '../../types/plugin.ts'; function showErrorMessage() { return alert(globalize.translate('MessagePlayAccessRestricted')); @@ -9,7 +10,7 @@ function showErrorMessage() { class PlayAccessValidation { constructor() { this.name = 'Playback validation'; - this.type = 'preplayintercept'; + this.type = PluginType.PreplayIntercept; this.id = 'playaccessvalidation'; this.order = -2; } diff --git a/src/plugins/sessionPlayer/plugin.js b/src/plugins/sessionPlayer/plugin.js index a8ad32f791..98e0e843ab 100644 --- a/src/plugins/sessionPlayer/plugin.js +++ b/src/plugins/sessionPlayer/plugin.js @@ -1,6 +1,7 @@ import { playbackManager } from '../../components/playback/playbackmanager'; import serverNotifications from '../../scripts/serverNotifications'; import ServerConnections from '../../components/ServerConnections'; +import { PluginType } from '../../types/plugin.ts'; import Events from '../../utils/events.ts'; function getActivePlayerId() { @@ -181,7 +182,7 @@ class SessionPlayer { const self = this; this.name = 'Remote Control'; - this.type = 'mediaplayer'; + this.type = PluginType.MediaPlayer; this.isLocalPlayer = false; this.id = 'remoteplayer'; diff --git a/src/plugins/syncPlay/core/Manager.js b/src/plugins/syncPlay/core/Manager.js index 740e0709d9..ee1e3e3ae7 100644 --- a/src/plugins/syncPlay/core/Manager.js +++ b/src/plugins/syncPlay/core/Manager.js @@ -254,7 +254,7 @@ class Manager { if (typeof cmd.When === 'string') { cmd.When = new Date(cmd.When); cmd.EmittedAt = new Date(cmd.EmittedAt); - cmd.PositionTicks = cmd.PositionTicks ? parseInt(cmd.PositionTicks) : null; + cmd.PositionTicks = cmd.PositionTicks ? parseInt(cmd.PositionTicks, 10) : null; } if (!this.isSyncPlayEnabled()) { diff --git a/src/plugins/syncPlay/plugin.ts b/src/plugins/syncPlay/plugin.ts index e4ef965a0d..59c066aaf7 100644 --- a/src/plugins/syncPlay/plugin.ts +++ b/src/plugins/syncPlay/plugin.ts @@ -5,8 +5,9 @@ import SyncPlay from './core'; import SyncPlayNoActivePlayer from './ui/players/NoActivePlayer'; import SyncPlayHtmlVideoPlayer from './ui/players/HtmlVideoPlayer'; import SyncPlayHtmlAudioPlayer from './ui/players/HtmlAudioPlayer'; +import { Plugin, PluginType } from '../../types/plugin'; -class SyncPlayPlugin { +class SyncPlayPlugin implements Plugin { name: string; id: string; type: string; @@ -17,7 +18,7 @@ class SyncPlayPlugin { this.id = 'syncplay'; // NOTE: This should probably be a "mediaplayer" so the playback manager can handle playback logic, but // SyncPlay needs refactored so it does not have an independent playback manager. - this.type = 'syncplay'; + this.type = PluginType.SyncPlay; this.priority = 1; this.init(); diff --git a/src/plugins/youtubePlayer/plugin.js b/src/plugins/youtubePlayer/plugin.js index a9dd8e9beb..2a10f8fcdc 100644 --- a/src/plugins/youtubePlayer/plugin.js +++ b/src/plugins/youtubePlayer/plugin.js @@ -2,6 +2,7 @@ import browser from '../../scripts/browser'; import { appRouter } from '../../components/appRouter'; import loading from '../../components/loading/loading'; import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../components/backdrop/backdrop'; +import { PluginType } from '../../types/plugin.ts'; import Events from '../../utils/events.ts'; /* globals YT */ @@ -197,7 +198,7 @@ function setCurrentSrc(instance, elem, options) { class YoutubePlayer { constructor() { this.name = 'Youtube Player'; - this.type = 'mediaplayer'; + this.type = PluginType.MediaPlayer; this.id = 'youtubeplayer'; // Let any players created by plugins take priority diff --git a/src/routes/movies/index.tsx b/src/routes/movies/index.tsx index 3b74b464d5..9b1405c9e5 100644 --- a/src/routes/movies/index.tsx +++ b/src/routes/movies/index.tsx @@ -55,7 +55,7 @@ const getTabs = () => { const Movies: FC = () => { const [ searchParams ] = useSearchParams(); - const currentTabIndex = parseInt(searchParams.get('tab') || getDefaultTabIndex(searchParams.get('topParentId')).toString()); + const currentTabIndex = parseInt(searchParams.get('tab') || getDefaultTabIndex(searchParams.get('topParentId')).toString(), 10); const [ selectedIndex, setSelectedIndex ] = useState(currentTabIndex); const element = useRef(null); @@ -95,7 +95,7 @@ const Movies: FC = () => { }; const onTabChange = useCallback((e: { detail: { selectedTabIndex: string; }; }) => { - const newIndex = parseInt(e.detail.selectedTabIndex); + const newIndex = parseInt(e.detail.selectedTabIndex, 10); setSelectedIndex(newIndex); }, []); diff --git a/src/routes/user/useredit.tsx b/src/routes/user/useredit.tsx index 5afa0c4fd8..b2eef63c76 100644 --- a/src/routes/user/useredit.tsx +++ b/src/routes/user/useredit.tsx @@ -159,6 +159,7 @@ const UserEdit: FunctionComponent = () => { (page.querySelector('.chkIsAdmin') as HTMLInputElement).checked = user.Policy.IsAdministrator; (page.querySelector('.chkDisabled') as HTMLInputElement).checked = user.Policy.IsDisabled; (page.querySelector('.chkIsHidden') as HTMLInputElement).checked = user.Policy.IsHidden; + (page.querySelector('.chkEnableCollectionManagement') as HTMLInputElement).checked = user.Policy.EnableCollectionManagement; (page.querySelector('.chkRemoteControlSharedDevices') as HTMLInputElement).checked = user.Policy.EnableSharedDeviceControl; (page.querySelector('.chkEnableRemoteControlOtherUsers') as HTMLInputElement).checked = user.Policy.EnableRemoteControlOfOtherUsers; (page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked = user.Policy.EnableContentDownloading; @@ -224,12 +225,13 @@ const UserEdit: FunctionComponent = () => { user.Policy.EnableAudioPlaybackTranscoding = (page.querySelector('.chkEnableAudioPlaybackTranscoding') as HTMLInputElement).checked; user.Policy.EnableVideoPlaybackTranscoding = (page.querySelector('.chkEnableVideoPlaybackTranscoding') as HTMLInputElement).checked; user.Policy.EnablePlaybackRemuxing = (page.querySelector('.chkEnableVideoPlaybackRemuxing') as HTMLInputElement).checked; + user.Policy.EnableCollectionManagement = (page.querySelector('.chkEnableCollectionManagement') as HTMLInputElement).checked; user.Policy.ForceRemoteSourceTranscoding = (page.querySelector('.chkForceRemoteSourceTranscoding') as HTMLInputElement).checked; user.Policy.EnableContentDownloading = (page.querySelector('.chkEnableDownloading') as HTMLInputElement).checked; user.Policy.EnableRemoteAccess = (page.querySelector('.chkRemoteAccess') as HTMLInputElement).checked; user.Policy.RemoteClientBitrateLimit = Math.floor(1e6 * parseFloat((page.querySelector('#txtRemoteClientBitrateLimit') as HTMLInputElement).value || '0')); - user.Policy.LoginAttemptsBeforeLockout = parseInt((page.querySelector('#txtLoginAttemptsBeforeLockout') as HTMLInputElement).value || '0'); - user.Policy.MaxActiveSessions = parseInt((page.querySelector('#txtMaxActiveSessions') as HTMLInputElement).value || '0'); + user.Policy.LoginAttemptsBeforeLockout = parseInt((page.querySelector('#txtLoginAttemptsBeforeLockout') as HTMLInputElement).value || '0', 10); + user.Policy.MaxActiveSessions = parseInt((page.querySelector('#txtMaxActiveSessions') as HTMLInputElement).value || '0', 10); user.Policy.AuthenticationProviderId = (page.querySelector('#selectLoginProvider') as HTMLSelectElement).value; user.Policy.PasswordResetProviderId = (page.querySelector('#selectPasswordResetProvider') as HTMLSelectElement).value; user.Policy.EnableContentDeletion = (page.querySelector('.chkEnableDeleteAllFolders') as HTMLInputElement).checked; @@ -375,6 +377,11 @@ const UserEdit: FunctionComponent = () => { className='chkIsAdmin' title='OptionAllowUserToManageServer' /> +

{globalize.translate('HeaderFeatureAccess')} diff --git a/src/scripts/browser.js b/src/scripts/browser.js index 2a28130150..a6042189dc 100644 --- a/src/scripts/browser.js +++ b/src/scripts/browser.js @@ -224,7 +224,7 @@ const uaMatch = function (ua) { version = version || match[2] || '0'; - let versionMajor = parseInt(version.split('.')[0]); + let versionMajor = parseInt(version.split('.')[0], 10); if (isNaN(versionMajor)) { versionMajor = 0; @@ -295,7 +295,7 @@ if (browser.web0s) { delete browser.safari; const v = (navigator.appVersion).match(/Tizen (\d+).(\d+)/); - browser.tizenVersion = parseInt(v[1]); + browser.tizenVersion = parseInt(v[1], 10); } else { browser.orsay = userAgent.toLowerCase().indexOf('smarthub') !== -1; } diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index 92405f8fe3..77b38ab46a 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -16,6 +16,7 @@ import imageHelper from './imagehelper'; import { getMenuLinks } from '../scripts/settings/webSettings'; import Dashboard, { pageClassOn } from '../utils/dashboard'; import ServerConnections from '../components/ServerConnections'; +import { PluginType } from '../types/plugin.ts'; import Events from '../utils/events.ts'; import { getParameterByName } from '../utils/url.ts'; @@ -159,7 +160,7 @@ import '../styles/flexstyles.scss'; // Button is present headerSyncButton // SyncPlay plugin is loaded - && pluginManager.plugins.filter(plugin => plugin.id === 'syncplay').length > 0 + && pluginManager.ofType(PluginType.SyncPlay).length > 0 // SyncPlay enabled for user && policy?.SyncPlayAccess !== 'None' ) { diff --git a/src/scripts/screensavermanager.js b/src/scripts/screensavermanager.js index 4b0eedaa51..c63d530685 100644 --- a/src/scripts/screensavermanager.js +++ b/src/scripts/screensavermanager.js @@ -3,6 +3,7 @@ import { pluginManager } from '../components/pluginManager'; import inputManager from './inputManager'; import * as userSettings from './settings/userSettings'; import ServerConnections from '../components/ServerConnections'; +import { PluginType } from '../types/plugin.ts'; import Events from '../utils/events.ts'; import './screensavermanager.scss'; @@ -34,7 +35,7 @@ function getScreensaverPlugin(isLoggedIn) { option = isLoggedIn ? 'backdropscreensaver' : 'logoscreensaver'; } - const plugins = pluginManager.ofType('screensaver'); + const plugins = pluginManager.ofType(PluginType.Screensaver); for (const plugin of plugins) { if (plugin.id === option) { diff --git a/src/scripts/serverNotifications.js b/src/scripts/serverNotifications.js index df902bb286..de8c0c483c 100644 --- a/src/scripts/serverNotifications.js +++ b/src/scripts/serverNotifications.js @@ -98,11 +98,11 @@ function processGeneralCommand(cmd, apiClient) { break; case 'SetAudioStreamIndex': notifyApp(); - playbackManager.setAudioStreamIndex(parseInt(cmd.Arguments.Index)); + playbackManager.setAudioStreamIndex(parseInt(cmd.Arguments.Index, 10)); break; case 'SetSubtitleStreamIndex': notifyApp(); - playbackManager.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index)); + playbackManager.setSubtitleStreamIndex(parseInt(cmd.Arguments.Index, 10)); break; case 'ToggleFullscreen': inputManager.handleCommand('togglefullscreen'); diff --git a/src/scripts/settings/appSettings.js b/src/scripts/settings/appSettings.js index 7a48943954..df18b0ddff 100644 --- a/src/scripts/settings/appSettings.js +++ b/src/scripts/settings/appSettings.js @@ -70,7 +70,7 @@ class AppSettings { // return a huge number so that it always direct plays return 150000000; } else { - return parseInt(this.get(key) || '0') || 1500000; + return parseInt(this.get(key) || '0', 10) || 1500000; } } @@ -80,7 +80,7 @@ class AppSettings { } const defaultValue = 320000; - return parseInt(this.get('maxStaticMusicBitrate') || defaultValue.toString()) || defaultValue; + return parseInt(this.get('maxStaticMusicBitrate') || defaultValue.toString(), 10) || defaultValue; } maxChromecastBitrate(val) { @@ -89,7 +89,7 @@ class AppSettings { } val = this.get('chromecastBitrate1'); - return val ? parseInt(val) : null; + return val ? parseInt(val, 10) : null; } /** diff --git a/src/scripts/settings/userSettings.js b/src/scripts/settings/userSettings.js index 658236414f..916e159394 100644 --- a/src/scripts/settings/userSettings.js +++ b/src/scripts/settings/userSettings.js @@ -348,7 +348,7 @@ export class UserSettings { return this.set('skipBackLength', val.toString()); } - return parseInt(this.get('skipBackLength') || '10000'); + return parseInt(this.get('skipBackLength') || '10000', 10); } /** @@ -361,7 +361,7 @@ export class UserSettings { return this.set('skipForwardLength', val.toString()); } - return parseInt(this.get('skipForwardLength') || '30000'); + return parseInt(this.get('skipForwardLength') || '30000', 10); } /** diff --git a/src/strings/be-by.json b/src/strings/be-by.json index fab979dd48..3e64e8e09b 100644 --- a/src/strings/be-by.json +++ b/src/strings/be-by.json @@ -1380,7 +1380,7 @@ "UnknownAudioStreamInfo": "Інфармацыя аб аўдыяплыні невядомая", "DirectPlayError": "Узнікла памылка пры запуску прамога прайгравання", "SelectAll": "Абраць усё", - "Clip": "Художнік", + "Clip": "Кліп", "Sample": "Прыклад", "LabelVppTonemappingBrightness": "Узмацненне яркасці танальнага адлюстравання VPP:", "LabelVppTonemappingBrightnessHelp": "Прымяніць узмацненне яркасці ў танальным адлюстраванні VPP. І рэкамендаванае, і стандартнае значэнне роўна 0.", @@ -1700,5 +1700,7 @@ "SubtitleMagenta": "Пурпурны", "SubtitleRed": "Чырвоны", "SubtitleWhite": "Белы", - "SubtitleYellow": "Жоўты" + "SubtitleYellow": "Жоўты", + "Featurette": "Кароткаметражка", + "Short": "Кароткаметражка" } diff --git a/src/strings/cs.json b/src/strings/cs.json index 369b0c17ca..a8d7ae5656 100644 --- a/src/strings/cs.json +++ b/src/strings/cs.json @@ -1622,7 +1622,7 @@ "DeletedScene": "Vymazaná scéna", "BehindTheScenes": "Z natáčení", "Trailer": "Upoutávka", - "Clip": "Krátký film", + "Clip": "Filmový klip", "AllowEmbeddedSubtitlesHelp": "Zakázat titulky, které jsou vložené v kontejneru média. Vyžaduje kompletní přeskenování knihovny.", "AllowEmbeddedSubtitlesAllowTextOption": "Povolit textové titulky", "AllowEmbeddedSubtitlesAllowImageOption": "Povolit grafické titulky", @@ -1714,5 +1714,7 @@ "SubtitleMagenta": "Fialová", "SubtitleRed": "Červená", "SubtitleWhite": "Bílá", - "SubtitleYellow": "Žlutá" + "SubtitleYellow": "Žlutá", + "Featurette": "Středně dlouhý film", + "Short": "Krátký film" } diff --git a/src/strings/en-gb.json b/src/strings/en-gb.json index 3dfb6b8d0d..c52fb52d48 100644 --- a/src/strings/en-gb.json +++ b/src/strings/en-gb.json @@ -1629,7 +1629,7 @@ "DeletedScene": "Deleted Scene", "BehindTheScenes": "Behind the Scenes", "Trailer": "Trailer", - "Clip": "Featurette", + "Clip": "Clip", "ShowParentImages": "Show programme images", "NextUpRewatching": "Rewatching", "MixedMoviesShows": "Mixed Films and Programmes", @@ -1714,5 +1714,7 @@ "SubtitleMagenta": "Magenta", "SubtitleRed": "Red", "SubtitleWhite": "White", - "SubtitleYellow": "Yellow" + "SubtitleYellow": "Yellow", + "Featurette": "Featurette", + "Short": "Short" } diff --git a/src/strings/en-us.json b/src/strings/en-us.json index 61ed683ad1..75d84bf7f5 100644 --- a/src/strings/en-us.json +++ b/src/strings/en-us.json @@ -1671,7 +1671,9 @@ "UnknownAudioStreamInfo": "The audio stream info is unknown", "DirectPlayError": "There was an error starting direct playback", "SelectAll": "Select All", - "Clip": "Featurette", + "Featurette": "Featurette", + "Short": "Short", + "Clip": "Clip", "Trailer": "Trailer", "BehindTheScenes": "Behind the Scenes", "DeletedScene": "Deleted Scene", diff --git a/src/strings/fi.json b/src/strings/fi.json index e35b229682..9d5e968ff0 100644 --- a/src/strings/fi.json +++ b/src/strings/fi.json @@ -1634,7 +1634,7 @@ "ButtonSpace": "Välilyönti", "ThemeVideo": "Tunnusvideo", "ThemeSong": "Tunnusmusiikki", - "Clip": "Lyhytfilmi", + "Clip": "Klippi", "Scene": "Kohtaus", "Interview": "Haastattelu", "UnknownAudioStreamInfo": "Äänivirran tiedot ovat tuntemattomia", @@ -1677,7 +1677,7 @@ "MessageNoFavoritesAvailable": "Suosikkeja ei ole tällä hetkellä käytettävissä.", "EnableCardLayout": "Näytä visuaalinen KorttiLaatikko", "Unreleased": "Ei vielä julkaistu", - "MediaInfoDvVersionMajor": "", + "MediaInfoDvVersionMajor": "DV-pääversio", "DownloadAll": "Lataa kaikki", "Experimental": "Kokeellinen", "LabelStereoDownmixAlgorithm": "Stereoäänen alasmiksausalgoritmi:", @@ -1697,5 +1697,22 @@ "ResolutionMatchSource": "Vastaa lähdettä", "PreferEmbeddedExtrasTitlesOverFileNames": "Suosi lisämateriaaleille upotettuja otsikoita tiedostonimien sijaan", "PreferEmbeddedExtrasTitlesOverFileNamesHelp": "Lisämateriaaleilla on usein sama otsikko kuin niiden isännällä. Valitse tämä käyttääksesi silti upotettuja otsikoita.", - "SecondarySubtitles": "Toissijaiset tekstitykset" + "SecondarySubtitles": "Toissijaiset tekstitykset", + "MediaInfoElPresentFlag": "DV EL havaittu", + "SubtitleBlack": "Musta", + "SubtitleCyan": "Syaani", + "SubtitleGray": "Harmaa", + "SubtitleGreen": "Vihreä", + "SubtitleLightGray": "Vaaleanharmaa", + "SubtitleMagenta": "Magenta", + "SubtitleRed": "Punainen", + "SubtitleWhite": "Valkoinen", + "SubtitleYellow": "Keltainen", + "MediaInfoDvBlSignalCompatibilityId": "DV BL -signaaliyhteensopivuuden ID", + "SubtitleBlue": "Sininen", + "Featurette": "Oheisfilmi", + "Short": "Lyhytfilmi", + "MediaInfoBlPresentFlag": "DV BL havaittu", + "MediaInfoRpuPresentFlag": "DV RPU havaittu", + "MediaInfoDvVersionMinor": "DV-väliversio" } diff --git a/src/strings/fr.json b/src/strings/fr.json index e26cc1a0c0..b53c9fcbc4 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -234,7 +234,7 @@ "HeaderApiKeys": "Clés API", "HeaderApiKeysHelp": "Les applications externes ont besoin d'une clé d'API pour communiquer avec le serveur. Les clés sont distribuées lors d'une connexion avec un compte normal ou en accordant manuellement une clé à une application.", "HeaderApp": "Application", - "HeaderAppearsOn": "Apparait dans", + "HeaderAppearsOn": "Apparaît dans", "HeaderAudioBooks": "Livres audio", "HeaderAudioSettings": "Réglages audio", "HeaderBlockItemsWithNoRating": "Bloquer les éléments avec des informations de classification inconnues ou n'en disposant pas :", @@ -979,7 +979,7 @@ "Rate": "Débit", "RecentlyWatched": "Vu récemment", "RecommendationBecauseYouLike": "Parce que vous aimez {0}", - "RecommendationBecauseYouWatched": "Parce que vous avez regardé {0}", + "RecommendationBecauseYouWatched": "Parce-que vous avez regardé {0}", "RecommendationDirectedBy": "Réalisé par {0}", "RecommendationStarring": "Avec {0}", "Record": "Enregistrer", @@ -1614,7 +1614,7 @@ "AudioIsExternal": "Le flux audio est externe", "SelectAll": "Tout sélectionner", "ButtonExitApp": "Quitter l'application", - "Clip": "Court-métrage", + "Clip": "Clip", "ThemeVideo": "Générique", "ThemeSong": "Thème musical", "Sample": "Échantillon", @@ -1714,5 +1714,7 @@ "SubtitleMagenta": "Magenta", "SubtitleRed": "Rouge", "SubtitleWhite": "Blanc", - "SubtitleYellow": "Jaune" + "SubtitleYellow": "Jaune", + "Featurette": "Featurette", + "Short": "Court-métrage" } diff --git a/src/strings/nl.json b/src/strings/nl.json index 7d493b7a6f..540dbf752b 100644 --- a/src/strings/nl.json +++ b/src/strings/nl.json @@ -787,7 +787,7 @@ "NewEpisodesOnly": "Alleen nieuwe afleveringen", "News": "Nieuws", "Next": "Volgende", - "NextUp": "Hierna", + "NextUp": "Volgende", "No": "Nee", "NoNewDevicesFound": "Er zijn geen nieuwe apparaten gevonden. Sluit deze melding en voer handmatig de apparaat gegevens in om een nieuwe tuner toe te voegen.", "MessageNoNextUpItems": "Niets gevonden. Start met kijken!", @@ -1544,7 +1544,7 @@ "LabelSyncPlaySettingsSkipToSyncHelp": "Synchronisatie correctiemethode die bestaat uit het zoeken naar de geschatte positie. Synchronisatie Correctie moet ingeschakeld zijn.", "MessageSent": "Bericht verzonden.", "Mixer": "Mixer", - "UseEpisodeImagesInNextUpHelp": "'Hierna'- en 'Verderkijken'-secties zullen afleveringsafbeeldingen gebruiken als thumbnails in plaats van de primaire thumbnail van de serie.", + "UseEpisodeImagesInNextUpHelp": "Secties 'Volgende' en 'Verderkijken' zullen afleveringsafbeeldingen gebruiken als miniaturen in plaats van de primaire miniatuur van de serie.", "SetUsingLastTracks": "Ondertitel/Audio-sporen instellen met vorig item", "SetUsingLastTracksHelp": "Probeer de ondertiteling/het audiospoor in te stellen op de video die het meest overeenkomt met de laatste video.", "TextSent": "Tekst verzonden.", @@ -1556,11 +1556,11 @@ "VideoProfileNotSupported": "Het profiel van de videocodec wordt niet ondersteund", "Lyricist": "Tekstschrijver", "NextChapter": "Volgend hoofdstuk", - "LabelMaxDaysForNextUp": "Maximaal dagen in 'Hierna':", - "LabelMaxDaysForNextUpHelp": "Zet het maximaal aantal dagen dat een serie in de 'Hierna'-lijst staat zonder het te kijken.", + "LabelMaxDaysForNextUp": "Maximaal dagen in 'Volgende':", + "LabelMaxDaysForNextUpHelp": "Stel het maximaal aantal dagen in dat een serie in de 'Volgende'-lijst staat zonder het te kijken.", "PreviousChapter": "Vorig hoofdstuk", "Remixer": "Remixer", - "UseEpisodeImagesInNextUp": "Gebruik afleveringscovers in de secties 'Hierna' en 'Verderkijken'", + "UseEpisodeImagesInNextUp": "Gebruik afleveringscovers in de secties 'Volgende' en 'Verderkijken'", "EnableGamepadHelp": "Luister naar input van alle aangesloten controllers. (Vereist weergavemodus 'Tv')", "VideoCodecNotSupported": "De videocodec wordt niet ondersteund", "AudioBitrateNotSupported": "De bitrate van de audio wordt niet ondersteund", @@ -1625,11 +1625,11 @@ "ButtonBackspace": "Backspace", "StoryArc": "Verhaallijn", "ItemDetails": "Itemdetails", - "EnableRewatchingNextUp": "Opnieuw kijken inschakelen in Hierna", + "EnableRewatchingNextUp": "Opnieuw kijken inschakelen in Volgende", "Bold": "Vetgedrukt", "LabelTextWeight": "Tekstdikte:", "HomeVideosPhotos": "Homevideo's en foto's", - "EnableRewatchingNextUpHelp": "Laat reeds gekeken afleveringen zien in 'Hierna'-secties.", + "EnableRewatchingNextUpHelp": "Laat reeds gekeken afleveringen zien in sectie 'Volgende'.", "ContainerBitrateExceedsLimit": "De bitrate van de video overschrijdt de limiet", "LabelMaxVideoResolution": "Maximaal toegestane resolutie voor transcoderingen", "UnknownAudioStreamInfo": "De audio stream info is onbekend", @@ -1674,7 +1674,7 @@ "DeletedScene": "Verwijderde scene", "BehindTheScenes": "Achter de scenes", "Trailer": "Trailer", - "Clip": "Korte film", + "Clip": "Clip", "SelectAll": "Selecteer alles", "DirectPlayError": "Er is een fout opgetreden tijdens het starten van direct afspelen", "OptionDateShowAdded": "Datum Serie Toegevoegd", @@ -1713,5 +1713,7 @@ "SubtitleMagenta": "Magenta", "SubtitleRed": "Rood", "SubtitleWhite": "Wit", - "SubtitleYellow": "Geel" + "SubtitleYellow": "Geel", + "Featurette": "Featurette", + "Short": "Korte film" } diff --git a/src/strings/zh-cn.json b/src/strings/zh-cn.json index d6e77b18ef..f05bb4d988 100644 --- a/src/strings/zh-cn.json +++ b/src/strings/zh-cn.json @@ -1621,7 +1621,7 @@ "DeletedScene": "删减场景", "BehindTheScenes": "幕后花絮", "Trailer": "预告片", - "Clip": "花絮", + "Clip": "片段", "ButtonExitApp": "退出应用", "ShowParentImages": "显示系列图片", "AllowEmbeddedSubtitlesAllowTextOption": "允许文本", @@ -1714,5 +1714,7 @@ "SubtitleGreen": "绿色", "SubtitleMagenta": "品红色", "SubtitleRed": "红色", - "SubtitleYellow": "黄色" + "SubtitleYellow": "黄色", + "Featurette": "花絮", + "Short": "短片" } diff --git a/src/types/plugin.ts b/src/types/plugin.ts new file mode 100644 index 0000000000..4b4b946e90 --- /dev/null +++ b/src/types/plugin.ts @@ -0,0 +1,13 @@ +export enum PluginType { + MediaPlayer = 'mediaplayer', + PreplayIntercept = 'preplayintercept', + Screensaver = 'screensaver', + SyncPlay = 'syncplay' +} + +export interface Plugin { + name: string + id: string + type: PluginType | string + priority: number +} diff --git a/src/utils/number.ts b/src/utils/number.ts index 71d1129883..16797d7d51 100644 --- a/src/utils/number.ts +++ b/src/utils/number.ts @@ -2,6 +2,16 @@ function toLocaleStringSupportsOptions() { return !!(typeof Intl === 'object' && Intl && typeof Intl.NumberFormat === 'function'); } +/** + * Generates a random integer in a given range. + * @param {number} min - Minimum of the range. + * @param {number} max - Maximum of the range. + * @returns {number} Randomly generated number. + */ +export function randomInt(min: number, max: number): number { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + /** * Gets the value of a number formatted as a perentage. * @param {number} value The value as a number.