diff --git a/.eslintrc.js b/.eslintrc.js index a3594d87a..8f0e4bd8c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -34,7 +34,7 @@ module.exports = { 'plugin:sonarjs/recommended' ], rules: { - 'array-callback-return': ['error'], + 'array-callback-return': ['error', { 'checkForEach': true }], 'block-spacing': ['error'], 'brace-style': ['error', '1tbs', { 'allowSingleLine': true }], 'comma-dangle': ['error', 'never'], @@ -51,19 +51,22 @@ module.exports = { 'no-floating-decimal': ['error'], 'no-multi-spaces': ['error'], 'no-multiple-empty-lines': ['error', { 'max': 1 }], + 'no-nested-ternary': ['error'], 'no-restricted-globals': ['error'].concat(restrictedGlobals), + 'no-return-assign': ['error'], 'no-return-await': ['error'], 'no-sequences': ['error', { 'allowInParentheses': false }], 'no-trailing-spaces': ['error'], '@babel/no-unused-expressions': ['error', { 'allowShortCircuit': true, 'allowTernary': true, 'allowTaggedTemplates': true }], + 'no-useless-constructor': ['error'], + 'no-var': ['error'], 'no-void': ['error', { 'allowAsStatement': true }], - 'no-nested-ternary': ['error'], + 'no-warning-comments': ['warn', { 'terms': ['fixme', 'hack', 'xxx'] }], 'one-var': ['error', 'never'], 'padded-blocks': ['error', 'never'], 'prefer-const': ['error', { 'destructuring': 'all' }], 'quotes': ['error', 'single', { 'avoidEscape': true, 'allowTemplateLiterals': false }], '@babel/semi': ['error'], - 'no-var': ['error'], 'space-before-blocks': ['error'], 'space-infix-ops': 'error', 'yoda': 'error', @@ -72,18 +75,8 @@ module.exports = { 'sonarjs/cognitive-complexity': ['warn'], // TODO: Enable the following rules and fix issues - 'sonarjs/max-switch-cases': ['off'], - 'sonarjs/no-collapsible-if': ['off'], 'sonarjs/no-duplicate-string': ['off'], - 'sonarjs/no-duplicated-branches': ['off'], - 'sonarjs/no-gratuitous-expressions': ['off'], - 'sonarjs/no-identical-functions': ['off'], - 'sonarjs/no-nested-switch': ['off'], - 'sonarjs/no-redundant-jump': ['off'], - 'sonarjs/no-small-switch': ['off'], - 'sonarjs/no-unused-collection': ['off'], - 'sonarjs/prefer-object-literal': ['off'], - 'sonarjs/prefer-single-boolean-return': ['off'] + 'sonarjs/prefer-object-literal': ['off'] }, settings: { react: { @@ -261,7 +254,11 @@ module.exports = { 'plugin:react/recommended', 'plugin:react-hooks/recommended', 'plugin:jsx-a11y/recommended' - ] + ], + rules: { + 'no-useless-constructor': ['off'], + '@typescript-eslint/no-useless-constructor': ['error'] + } } ] }; diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e9ce3a619..235a6221a 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -21,11 +21,11 @@ jobs: - name: Checkout repository uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3 - name: Initialize CodeQL - uses: github/codeql-action/init@e0e5ded33cabb451ae0a9768fc7b0410bad9ad44 # tag=v2 + uses: github/codeql-action/init@807578363a7869ca324a79039e6db9c843e0e100 # tag=v2 with: languages: ${{ matrix.language }} queries: +security-extended - name: Autobuild - uses: github/codeql-action/autobuild@e0e5ded33cabb451ae0a9768fc7b0410bad9ad44 # tag=v2 + uses: github/codeql-action/autobuild@807578363a7869ca324a79039e6db9c843e0e100 # tag=v2 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@e0e5ded33cabb451ae0a9768fc7b0410bad9ad44 # tag=v2 + uses: github/codeql-action/analyze@807578363a7869ca324a79039e6db9c843e0e100 # tag=v2 diff --git a/.github/workflows/commands.yml b/.github/workflows/commands.yml index 12effcdfc..d1d061417 100644 --- a/.github/workflows/commands.yml +++ b/.github/workflows/commands.yml @@ -18,7 +18,7 @@ jobs: comment-id: ${{ github.event.comment.id }} reactions: '+1' - name: Checkout the latest code - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # tag=v3.0.2 + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # tag=v3.1.0 with: token: ${{ secrets.JF_BOT_TOKEN }} fetch-depth: 0 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 20111001c..7bf01805d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -20,19 +20,7 @@ jobs: with: node-version: 16 check-latest: true - - - name: Get npm cache directory path - id: npm-cache-dir-path - run: echo "::set-output name=dir::$(npm config get cache)" - - - name: Cache node_modules - uses: actions/cache@56461b9eb0f8438fd15c7a9968e3c9ebb18ceff1 # tag=v3.0.10 - id: npm-cache - with: - path: ${{ steps.npm-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-npm- + cache: npm - name: Install Node.js dependencies run: npm ci --no-audit @@ -55,19 +43,7 @@ jobs: with: node-version: 16 check-latest: true - - - name: Get npm cache directory path - id: npm-cache-dir-path - run: echo "::set-output name=dir::$(npm config get cache)" - - - name: Cache node_modules - uses: actions/cache@56461b9eb0f8438fd15c7a9968e3c9ebb18ceff1 # tag=v3.0.10 - id: npm-cache - with: - path: ${{ steps.npm-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-npm- + cache: npm - name: Set up stylelint matcher uses: xt0rted/stylelint-problem-matcher@34db1b874c0452909f0696aedef70b723870a583 # tag=v1 @@ -93,19 +69,7 @@ jobs: with: node-version: 16 check-latest: true - - - name: Get npm cache directory path - id: npm-cache-dir-path - run: echo "::set-output name=dir::$(npm config get cache)" - - - name: Cache node_modules - uses: actions/cache@56461b9eb0f8438fd15c7a9968e3c9ebb18ceff1 # tag=v3.0.10 - id: npm-cache - with: - path: ${{ steps.npm-cache-dir-path.outputs.dir }} - key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-npm- + cache: npm - name: Set up stylelint matcher uses: xt0rted/stylelint-problem-matcher@34db1b874c0452909f0696aedef70b723870a583 # tag=v1 diff --git a/.github/workflows/repo-stale.yaml b/.github/workflows/repo-stale.yaml index 3eb1fd92a..e332619ee 100644 --- a/.github/workflows/repo-stale.yaml +++ b/.github/workflows/repo-stale.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest if: ${{ contains(github.repository, 'jellyfin/') }} steps: - - uses: actions/stale@3de2653986ebd134983c79fe2be5d45cc3d9f4e1 # tag=v6.0.0 + - uses: actions/stale@5ebf00ea0e4c1561e9b43a292ed34424fb1d4578 # tag=v6.0.1 with: repo-token: ${{ secrets.JF_BOT_TOKEN }} days-before-stale: 120 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 9261538ff..fd4f0eb2d 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -52,6 +52,7 @@ - [MinecraftPlaye](https://github.com/MinecraftPlaye) - [Matthew Jones](https://github.com/matthew-jones-uk) - [taku0](https://github.com/taku0) + - [Viperinius](https://github.com/Viperinius) - [is343](https://github.com/is343) - [Meet Pandya](https://github.com/meet-k-pandya) diff --git a/cssnano.config.js b/cssnano.config.js new file mode 100644 index 000000000..376b8f011 --- /dev/null +++ b/cssnano.config.js @@ -0,0 +1,10 @@ +module.exports = { + preset: [ + 'default', + // Turn off `mergeLonghand` because it combines `padding-*` and `margin-*`, + // breaking fallback styles. + // https://github.com/cssnano/cssnano/issues/1163 + // https://github.com/cssnano/cssnano/issues/1192 + { mergeLonghand: false } + ] +}; diff --git a/package-lock.json b/package-lock.json index b20ae7206..7a7577bf6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,20 +10,20 @@ "license": "GPL-2.0-or-later", "dependencies": { "@fontsource/noto-sans": "4.5.11", - "@fontsource/noto-sans-hk": "4.5.11", - "@fontsource/noto-sans-jp": "4.5.11", - "@fontsource/noto-sans-kr": "4.5.11", - "@fontsource/noto-sans-sc": "4.5.11", - "@fontsource/noto-sans-tc": "4.5.11", + "@fontsource/noto-sans-hk": "4.5.12", + "@fontsource/noto-sans-jp": "4.5.12", + "@fontsource/noto-sans-kr": "4.5.12", + "@fontsource/noto-sans-sc": "4.5.12", + "@fontsource/noto-sans-tc": "4.5.12", "@jellyfin/libass-wasm": "4.1.1", "@jellyfin/sdk": "0.7.0", - "blurhash": "2.0.0", + "blurhash": "2.0.3", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "classnames": "2.3.2", - "core-js": "3.25.3", + "core-js": "3.25.5", "date-fns": "2.29.3", "dompurify": "2.4.0", - "epubjs": "0.3.93", + "epubjs": "0.4.2", "escape-html": "1.0.3", "fast-text-encoding": "1.0.6", "flv.js": "1.6.2", @@ -36,7 +36,7 @@ "jstree": "3.3.12", "libarchive.js": "1.3.0", "lodash-es": "4.17.21", - "marked": "4.1.0", + "marked": "4.1.1", "material-design-icons-iconfont": "6.7.0", "native-promise-only": "0.8.1", "pdfjs-dist": "2.16.105", @@ -53,21 +53,21 @@ "workbox-precaching": "6.5.4" }, "devDependencies": { - "@babel/core": "7.19.1", + "@babel/core": "7.19.3", "@babel/eslint-parser": "7.19.1", "@babel/eslint-plugin": "7.19.1", "@babel/plugin-proposal-class-properties": "7.18.6", "@babel/plugin-proposal-private-methods": "7.18.6", "@babel/plugin-transform-modules-umd": "7.18.6", - "@babel/preset-env": "7.19.1", + "@babel/preset-env": "7.19.3", "@babel/preset-react": "7.18.6", "@babel/preset-typescript": "7.18.6", "@types/escape-html": "1.0.2", "@types/lodash-es": "4.17.6", "@types/react": "17.0.50", "@types/react-dom": "17.0.17", - "@typescript-eslint/eslint-plugin": "5.38.0", - "@typescript-eslint/parser": "5.38.0", + "@typescript-eslint/eslint-plugin": "5.39.0", + "@typescript-eslint/parser": "5.39.0", "@uupaa/dynamic-import-polyfill": "1.0.2", "autoprefixer": "10.4.12", "babel-loader": "8.2.5", @@ -90,21 +90,21 @@ "html-loader": "4.2.0", "html-webpack-plugin": "5.5.0", "mini-css-extract-plugin": "2.6.1", - "postcss": "8.4.16", + "postcss": "8.4.17", "postcss-loader": "7.0.1", "postcss-preset-env": "7.8.2", "postcss-scss": "4.0.5", "sass": "1.55.0", "sass-loader": "13.0.2", - "source-map-loader": "3.0.1", + "source-map-loader": "4.0.0", "style-loader": "3.3.1", - "stylelint": "14.12.1", + "stylelint": "14.13.0", "stylelint-config-rational-order": "0.1.2", "stylelint-no-browser-hacks": "1.2.1", "stylelint-order": "5.0.0", "stylelint-scss": "4.3.0", "ts-loader": "9.4.1", - "typescript": "4.8.3", + "typescript": "4.8.4", "webpack": "5.74.0", "webpack-cli": "4.10.0", "webpack-dev-server": "4.11.1", @@ -178,21 +178,21 @@ } }, "node_modules/@babel/core": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.1.tgz", - "integrity": "sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw==", + "version": "7.19.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz", + "integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-compilation-targets": "^7.19.1", + "@babel/generator": "^7.19.3", + "@babel/helper-compilation-targets": "^7.19.3", "@babel/helper-module-transforms": "^7.19.0", "@babel/helpers": "^7.19.0", - "@babel/parser": "^7.19.1", + "@babel/parser": "^7.19.3", "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0", + "@babel/traverse": "^7.19.3", + "@babel/types": "^7.19.3", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -1709,13 +1709,13 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.1.tgz", - "integrity": "sha512-c8B2c6D16Lp+Nt6HcD+nHl0VbPKVnNPTpszahuxJJnurfMtKeZ80A+qUv48Y7wqvS+dTFuLuaM9oYxyNHbCLWA==", + "version": "7.19.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.3.tgz", + "integrity": "sha512-ziye1OTc9dGFOAXSWKUqQblYHNlBOaDl8wzqf2iKXJAltYiR3hKHUKmkt+S9PppW7RQpq4fFCrwwpIDj/f5P4w==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.19.1", - "@babel/helper-compilation-targets": "^7.19.1", + "@babel/compat-data": "^7.19.3", + "@babel/helper-compilation-targets": "^7.19.3", "@babel/helper-plugin-utils": "^7.19.0", "@babel/helper-validator-option": "^7.18.6", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", @@ -1783,7 +1783,7 @@ "@babel/plugin-transform-unicode-escapes": "^7.18.10", "@babel/plugin-transform-unicode-regex": "^7.18.6", "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.19.0", + "@babel/types": "^7.19.3", "babel-plugin-polyfill-corejs2": "^0.3.3", "babel-plugin-polyfill-corejs3": "^0.6.0", "babel-plugin-polyfill-regenerator": "^0.4.1", @@ -2322,29 +2322,29 @@ "integrity": "sha512-lBX7FCjIjSrQ+iMuXUuO+mbjbnUsJyZANg/04PgkeeAYe+cwnX81ibbgrAk1F56M6/btIrWQoDjxsK6Sz8KoVQ==" }, "node_modules/@fontsource/noto-sans-hk": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-hk/-/noto-sans-hk-4.5.11.tgz", - "integrity": "sha512-fL1oc/vlgrotdKo1aPI0SJaB/dU4v0iHP0t2bTvxqBo/2kjz4w4y/gu03sUk4gHujwnYjARhjtwVe73xph7P9w==" + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-hk/-/noto-sans-hk-4.5.12.tgz", + "integrity": "sha512-AWDAqUQpSXlraTZXCdwV9FOVuAVHmhed4bvqOjRqksfeMWZAAMiGOi1QfP4XWnGOzjLEZdV+4aNWc4RYw4suuQ==" }, "node_modules/@fontsource/noto-sans-jp": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-jp/-/noto-sans-jp-4.5.11.tgz", - "integrity": "sha512-fNt9a07uf7YUPo4qEzIz8IseKn2Gg7zQwX29IKJ5pwClQNEUsVaqyP+PkuClo3EZi1eZUBYMJHjyVtsa8oSmsw==" + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-jp/-/noto-sans-jp-4.5.12.tgz", + "integrity": "sha512-tdJAUEyuyyCRD4Ot4ZE8+3uyGeSJQD/soSHr7LoltEQr22IotDDWOlqQKF7CpTaJPwt4iOBoXWtAoSN0huVW6A==" }, "node_modules/@fontsource/noto-sans-kr": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-kr/-/noto-sans-kr-4.5.11.tgz", - "integrity": "sha512-yqUVOeV2eoHivVknPImSltQsZvLDrKjyf7Xl4XuBE02Gs5bGPkl/o72mUK+N+oMswF413pyxL+jQmI0PAYQubA==" + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-kr/-/noto-sans-kr-4.5.12.tgz", + "integrity": "sha512-6LBOzXw/V4guB/nATaiUjCcPxwJNGw+ky5DK7/wsgUZvfk8etH9IlKdvjFVidpeCuMVjOdlka2+MEw4hwEHaHA==" }, "node_modules/@fontsource/noto-sans-sc": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-sc/-/noto-sans-sc-4.5.11.tgz", - "integrity": "sha512-tufdVgKpP2PqhPpk8Wu6nKF8RCe7nKU0lpKm2uWtqmFE0GN5n+Xq/H6UL7ssRNUvDTHffBHIlGB4T7iYXAutgw==" + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-sc/-/noto-sans-sc-4.5.12.tgz", + "integrity": "sha512-KQ8Bn8KCGVS6RKrEw7fp65GJYpKyKEJ/TkePoKGxvNy7smSaHoKV7vrmpIiA46hkqQyYdENix/NKSprxs4Soyw==" }, "node_modules/@fontsource/noto-sans-tc": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-tc/-/noto-sans-tc-4.5.11.tgz", - "integrity": "sha512-Ns7V+lhInWpcNiHNlK5+9aSvn29ea/qQlNbHjG8+X4SKQ2fMK+rMdYaZCx4eYgQiKajQFTXke+uTroKgsuY/MQ==" + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-tc/-/noto-sans-tc-4.5.12.tgz", + "integrity": "sha512-c4OUY5Wq03ZLI4Ds9IEHEiPqpjmEXvDpANXufCTlk+bMb8MnweXPVWJSrSHydXnz7r+qstZl3HvWDl2RI5o/1A==" }, "node_modules/@humanwhocodes/config-array": { "version": "0.10.7", @@ -2782,15 +2782,6 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, - "node_modules/@types/localforage": { - "version": "0.0.34", - "resolved": "https://registry.npmjs.org/@types/localforage/-/localforage-0.0.34.tgz", - "integrity": "sha1-XjHDLdh5HsS5/z70fJy1Wy0NlDg=", - "deprecated": "This is a stub types definition for localforage (https://github.com/localForage/localForage). localforage provides its own type definitions, so you don't need @types/localforage installed!", - "dependencies": { - "localforage": "*" - } - }, "node_modules/@types/lodash": { "version": "4.14.178", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", @@ -2972,14 +2963,14 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.0.tgz", - "integrity": "sha512-GgHi/GNuUbTOeoJiEANi0oI6fF3gBQc3bGFYj40nnAPCbhrtEDf2rjBmefFadweBmO1Du1YovHeDP2h5JLhtTQ==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.39.0.tgz", + "integrity": "sha512-xVfKOkBm5iWMNGKQ2fwX5GVgBuHmZBO1tCRwXmY5oAIsPscfwm2UADDuNB8ZVYCtpQvJK4xpjrK7jEhcJ0zY9A==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.38.0", - "@typescript-eslint/type-utils": "5.38.0", - "@typescript-eslint/utils": "5.38.0", + "@typescript-eslint/scope-manager": "5.39.0", + "@typescript-eslint/type-utils": "5.39.0", + "@typescript-eslint/utils": "5.39.0", "debug": "^4.3.4", "ignore": "^5.2.0", "regexpp": "^3.2.0", @@ -3004,9 +2995,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3019,14 +3010,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.0.tgz", - "integrity": "sha512-/F63giJGLDr0ms1Cr8utDAxP2SPiglaD6V+pCOcG35P2jCqdfR7uuEhz1GIC3oy4hkUF8xA1XSXmd9hOh/a5EA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.39.0.tgz", + "integrity": "sha512-PhxLjrZnHShe431sBAGHaNe6BDdxAASDySgsBCGxcBecVCi8NQWxQZMcizNA4g0pN51bBAn/FUfkWG3SDVcGlA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.38.0", - "@typescript-eslint/types": "5.38.0", - "@typescript-eslint/typescript-estree": "5.38.0", + "@typescript-eslint/scope-manager": "5.39.0", + "@typescript-eslint/types": "5.39.0", + "@typescript-eslint/typescript-estree": "5.39.0", "debug": "^4.3.4" }, "engines": { @@ -3046,13 +3037,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.38.0.tgz", - "integrity": "sha512-ByhHIuNyKD9giwkkLqzezZ9y5bALW8VNY6xXcP+VxoH4JBDKjU5WNnsiD4HJdglHECdV+lyaxhvQjTUbRboiTA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.39.0.tgz", + "integrity": "sha512-/I13vAqmG3dyqMVSZPjsbuNQlYS082Y7OMkwhCfLXYsmlI0ca4nkL7wJ/4gjX70LD4P8Hnw1JywUVVAwepURBw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.38.0", - "@typescript-eslint/visitor-keys": "5.38.0" + "@typescript-eslint/types": "5.39.0", + "@typescript-eslint/visitor-keys": "5.39.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3063,13 +3054,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.38.0.tgz", - "integrity": "sha512-iZq5USgybUcj/lfnbuelJ0j3K9dbs1I3RICAJY9NZZpDgBYXmuUlYQGzftpQA9wC8cKgtS6DASTvF3HrXwwozA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.39.0.tgz", + "integrity": "sha512-KJHJkOothljQWzR3t/GunL0TPKY+fGJtnpl+pX+sJ0YiKTz3q2Zr87SGTmFqsCMFrLt5E0+o+S6eQY0FAXj9uA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.38.0", - "@typescript-eslint/utils": "5.38.0", + "@typescript-eslint/typescript-estree": "5.39.0", + "@typescript-eslint/utils": "5.39.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -3090,9 +3081,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.38.0.tgz", - "integrity": "sha512-HHu4yMjJ7i3Cb+8NUuRCdOGu2VMkfmKyIJsOr9PfkBVYLYrtMCK/Ap50Rpov+iKpxDTfnqvDbuPLgBE5FwUNfA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.39.0.tgz", + "integrity": "sha512-gQMZrnfEBFXK38hYqt8Lkwt8f4U6yq+2H5VDSgP/qiTzC8Nw8JO3OuSUOQ2qW37S/dlwdkHDntkZM6SQhKyPhw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -3103,13 +3094,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.0.tgz", - "integrity": "sha512-6P0RuphkR+UuV7Avv7MU3hFoWaGcrgOdi8eTe1NwhMp2/GjUJoODBTRWzlHpZh6lFOaPmSvgxGlROa0Sg5Zbyg==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.39.0.tgz", + "integrity": "sha512-qLFQP0f398sdnogJoLtd43pUgB18Q50QSA+BTE5h3sUxySzbWDpTSdgt4UyxNSozY/oDK2ta6HVAzvGgq8JYnA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.38.0", - "@typescript-eslint/visitor-keys": "5.38.0", + "@typescript-eslint/types": "5.39.0", + "@typescript-eslint/visitor-keys": "5.39.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -3159,9 +3150,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "dependencies": { "lru-cache": "^6.0.0" @@ -3174,15 +3165,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.38.0.tgz", - "integrity": "sha512-6sdeYaBgk9Fh7N2unEXGz+D+som2QCQGPAf1SxrkEr+Z32gMreQ0rparXTNGRRfYUWk/JzbGdcM8NSSd6oqnTA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.39.0.tgz", + "integrity": "sha512-+DnY5jkpOpgj+EBtYPyHRjXampJfC0yUZZzfzLuUWVZvCuKqSdJVC8UhdWipIw7VKNTfwfAPiOWzYkAwuIhiAg==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.38.0", - "@typescript-eslint/types": "5.38.0", - "@typescript-eslint/typescript-estree": "5.38.0", + "@typescript-eslint/scope-manager": "5.39.0", + "@typescript-eslint/types": "5.39.0", + "@typescript-eslint/typescript-estree": "5.39.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" }, @@ -3198,12 +3189,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.0.tgz", - "integrity": "sha512-MxnrdIyArnTi+XyFLR+kt/uNAcdOnmT+879os7qDRI+EYySR4crXJq9BXPfRzzLGq0wgxkwidrCJ9WCAoacm1w==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.39.0.tgz", + "integrity": "sha512-yyE3RPwOG+XJBLrhvsxAidUgybJVQ/hG8BhiJo0k8JSAYfk/CshVcxf0HwP4Jt7WZZ6vLmxdo1p6EyN3tzFTkg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.38.0", + "@typescript-eslint/types": "5.39.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -3411,14 +3402,6 @@ } } }, - "node_modules/@xmldom/xmldom": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz", - "integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -3432,9 +3415,9 @@ "devOptional": true }, "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, "node_modules/accepts": { @@ -4026,9 +4009,9 @@ } }, "node_modules/blurhash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.0.tgz", - "integrity": "sha512-fdEZnyJZ5E5s9neCfZUMSMkKfMtdKz1fG53t/iYvMjUFUsDnyZ1YnRRayKBK/B8cilNwe5gaIrPF8QlLrukEZQ==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.3.tgz", + "integrity": "sha512-nTnJTOheiaV3b189f7rH5AbbrnQB2r3CcOZBg47GUDaE9DrxyBPD2w0HYp4ME2UBlTP7LMIa6nMWqg/58oyIzA==" }, "node_modules/body-parser": { "version": "1.20.0", @@ -4873,9 +4856,9 @@ } }, "node_modules/core-js": { - "version": "3.25.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.25.3.tgz", - "integrity": "sha512-y1hvKXmPHvm5B7w4ln1S4uc9eV/O5+iFExSRUimnvIph11uaizFR8LFMdONN8hG3P2pipUfX4Y/fR8rAEtcHcQ==", + "version": "3.25.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.25.5.tgz", + "integrity": "sha512-nbm6eZSjm+ZuBQxCUPQKQCoUEfFOXjUZ8dTTyikyKaWrTYmAVbykQfwsKE5dBK88u3QCkCrzsx/PPlKfhsvgpw==", "hasInstallScript": true, "funding": { "type": "opencollective", @@ -5843,19 +5826,17 @@ } }, "node_modules/epubjs": { - "version": "0.3.93", - "resolved": "https://registry.npmjs.org/epubjs/-/epubjs-0.3.93.tgz", - "integrity": "sha512-c06pNSdBxcXv3dZSbXAVLE1/pmleRhOT6mXNZo6INKmvuKpYB65MwU/lO7830czCtjIiK9i+KR+3S+p0wtljrw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/epubjs/-/epubjs-0.4.2.tgz", + "integrity": "sha512-ex+ntja2AmPeq++qgjYfwrEzrO8UUBbTch1RJcRftShUmn8no6qi4Cax75FH0QopLA+6L8HM6iR94M0/I8V3GQ==", "dependencies": { - "@types/localforage": "0.0.34", - "@xmldom/xmldom": "^0.7.5", - "core-js": "^3.18.3", "event-emitter": "^0.3.5", - "jszip": "^3.7.1", - "localforage": "^1.10.0", - "lodash": "^4.17.21", - "marks-pane": "^1.0.9", - "path-webpack": "0.0.3" + "jszip": "^3.1.5", + "lodash": "^4.17.5", + "marks-pane": "^1.0.7", + "path-webpack": "0.0.3", + "stream-browserify": "^2.0.1", + "xmldom": "^0.1.27" } }, "node_modules/error-ex": { @@ -9381,14 +9362,6 @@ "resolved": "https://registry.npmjs.org/libarchive.js/-/libarchive.js-1.3.0.tgz", "integrity": "sha512-EkQfRXt9DhWwj6BnEA2TNpOf4jTnzSTUPGgE+iFxcdNqjktY8GitbDeHnx8qZA0/IukNyyBUR3oQKRdYkO+HFg==" }, - "node_modules/lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/lilconfig": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", @@ -9427,14 +9400,6 @@ "node": ">=8.9.0" } }, - "node_modules/localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "dependencies": { - "lie": "3.1.1" - } - }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -9622,9 +9587,9 @@ "dev": true }, "node_modules/marked": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.0.tgz", - "integrity": "sha512-+Z6KDjSPa6/723PQYyc1axYZpYYpDnECDaU6hkaf5gqBieBkMKYReL5hteF2QizhlMbgbo8umXl/clZ67+GlsA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.1.tgz", + "integrity": "sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==", "bin": { "marked": "bin/marked.js" }, @@ -10735,9 +10700,9 @@ } }, "node_modules/postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "version": "8.4.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.17.tgz", + "integrity": "sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==", "dev": true, "funding": [ { @@ -13747,24 +13712,24 @@ } }, "node_modules/source-map-loader": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.1.tgz", - "integrity": "sha512-Vp1UsfyPvgujKQzi4pyDiTOnE3E4H+yHvkVRN3c/9PJmQS4CQJExvcDvaX/D+RV+xQben9HJ56jMJS3CgUeWyA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.0.tgz", + "integrity": "sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw==", "dev": true, "dependencies": { - "abab": "^2.0.5", + "abab": "^2.0.6", "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.1" + "source-map-js": "^1.0.2" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 14.15.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^5.0.0" + "webpack": "^5.72.1" } }, "node_modules/source-map-resolve": { @@ -13972,6 +13937,15 @@ "node": ">= 0.6" } }, + "node_modules/stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -14187,9 +14161,9 @@ } }, "node_modules/stylelint": { - "version": "14.12.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.12.1.tgz", - "integrity": "sha512-ZEM4TuksChMBfuPadQsHUkbOoRySAT9QMfDvvYxdAchOJl0p+csTMBXOu6ORAAxKhwBmxqJiep8V88bXfNs3EQ==", + "version": "14.13.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.13.0.tgz", + "integrity": "sha512-NJSAdloiAB/jgVJKxMR90mWlctvmeBFGFVUvyKngi9+j/qPSJ5ZB+u8jOmGbLTnS7OHrII9NFGehPRyar8U5vg==", "dev": true, "dependencies": { "@csstools/selector-specificity": "^2.0.2", @@ -17287,9 +17261,9 @@ } }, "node_modules/typescript": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", - "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -18494,6 +18468,15 @@ "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", "dev": true }, + "node_modules/xmldom": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz", + "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==", + "deprecated": "Deprecated due to CVE-2021-21366 resolved in 0.5.0", + "engines": { + "node": ">=0.1" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -18585,21 +18568,21 @@ "dev": true }, "@babel/core": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.1.tgz", - "integrity": "sha512-1H8VgqXme4UXCRv7/Wa1bq7RVymKOzC7znjyFM8KiEzwFqcKUKYNoQef4GhdklgNvoBXyW4gYhuBNCM5o1zImw==", + "version": "7.19.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.19.3.tgz", + "integrity": "sha512-WneDJxdsjEvyKtXKsaBGbDeiyOjR5vYq4HcShxnIbG0qixpoHjI3MqeZM9NDvsojNCEBItQE4juOo/bU6e72gQ==", "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-compilation-targets": "^7.19.1", + "@babel/generator": "^7.19.3", + "@babel/helper-compilation-targets": "^7.19.3", "@babel/helper-module-transforms": "^7.19.0", "@babel/helpers": "^7.19.0", - "@babel/parser": "^7.19.1", + "@babel/parser": "^7.19.3", "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0", + "@babel/traverse": "^7.19.3", + "@babel/types": "^7.19.3", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -19603,13 +19586,13 @@ } }, "@babel/preset-env": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.1.tgz", - "integrity": "sha512-c8B2c6D16Lp+Nt6HcD+nHl0VbPKVnNPTpszahuxJJnurfMtKeZ80A+qUv48Y7wqvS+dTFuLuaM9oYxyNHbCLWA==", + "version": "7.19.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.3.tgz", + "integrity": "sha512-ziye1OTc9dGFOAXSWKUqQblYHNlBOaDl8wzqf2iKXJAltYiR3hKHUKmkt+S9PppW7RQpq4fFCrwwpIDj/f5P4w==", "dev": true, "requires": { - "@babel/compat-data": "^7.19.1", - "@babel/helper-compilation-targets": "^7.19.1", + "@babel/compat-data": "^7.19.3", + "@babel/helper-compilation-targets": "^7.19.3", "@babel/helper-plugin-utils": "^7.19.0", "@babel/helper-validator-option": "^7.18.6", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", @@ -19677,7 +19660,7 @@ "@babel/plugin-transform-unicode-escapes": "^7.18.10", "@babel/plugin-transform-unicode-regex": "^7.18.6", "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.19.0", + "@babel/types": "^7.19.3", "babel-plugin-polyfill-corejs2": "^0.3.3", "babel-plugin-polyfill-corejs3": "^0.6.0", "babel-plugin-polyfill-regenerator": "^0.4.1", @@ -20006,29 +19989,29 @@ "integrity": "sha512-lBX7FCjIjSrQ+iMuXUuO+mbjbnUsJyZANg/04PgkeeAYe+cwnX81ibbgrAk1F56M6/btIrWQoDjxsK6Sz8KoVQ==" }, "@fontsource/noto-sans-hk": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-hk/-/noto-sans-hk-4.5.11.tgz", - "integrity": "sha512-fL1oc/vlgrotdKo1aPI0SJaB/dU4v0iHP0t2bTvxqBo/2kjz4w4y/gu03sUk4gHujwnYjARhjtwVe73xph7P9w==" + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-hk/-/noto-sans-hk-4.5.12.tgz", + "integrity": "sha512-AWDAqUQpSXlraTZXCdwV9FOVuAVHmhed4bvqOjRqksfeMWZAAMiGOi1QfP4XWnGOzjLEZdV+4aNWc4RYw4suuQ==" }, "@fontsource/noto-sans-jp": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-jp/-/noto-sans-jp-4.5.11.tgz", - "integrity": "sha512-fNt9a07uf7YUPo4qEzIz8IseKn2Gg7zQwX29IKJ5pwClQNEUsVaqyP+PkuClo3EZi1eZUBYMJHjyVtsa8oSmsw==" + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-jp/-/noto-sans-jp-4.5.12.tgz", + "integrity": "sha512-tdJAUEyuyyCRD4Ot4ZE8+3uyGeSJQD/soSHr7LoltEQr22IotDDWOlqQKF7CpTaJPwt4iOBoXWtAoSN0huVW6A==" }, "@fontsource/noto-sans-kr": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-kr/-/noto-sans-kr-4.5.11.tgz", - "integrity": "sha512-yqUVOeV2eoHivVknPImSltQsZvLDrKjyf7Xl4XuBE02Gs5bGPkl/o72mUK+N+oMswF413pyxL+jQmI0PAYQubA==" + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-kr/-/noto-sans-kr-4.5.12.tgz", + "integrity": "sha512-6LBOzXw/V4guB/nATaiUjCcPxwJNGw+ky5DK7/wsgUZvfk8etH9IlKdvjFVidpeCuMVjOdlka2+MEw4hwEHaHA==" }, "@fontsource/noto-sans-sc": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-sc/-/noto-sans-sc-4.5.11.tgz", - "integrity": "sha512-tufdVgKpP2PqhPpk8Wu6nKF8RCe7nKU0lpKm2uWtqmFE0GN5n+Xq/H6UL7ssRNUvDTHffBHIlGB4T7iYXAutgw==" + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-sc/-/noto-sans-sc-4.5.12.tgz", + "integrity": "sha512-KQ8Bn8KCGVS6RKrEw7fp65GJYpKyKEJ/TkePoKGxvNy7smSaHoKV7vrmpIiA46hkqQyYdENix/NKSprxs4Soyw==" }, "@fontsource/noto-sans-tc": { - "version": "4.5.11", - "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-tc/-/noto-sans-tc-4.5.11.tgz", - "integrity": "sha512-Ns7V+lhInWpcNiHNlK5+9aSvn29ea/qQlNbHjG8+X4SKQ2fMK+rMdYaZCx4eYgQiKajQFTXke+uTroKgsuY/MQ==" + "version": "4.5.12", + "resolved": "https://registry.npmjs.org/@fontsource/noto-sans-tc/-/noto-sans-tc-4.5.12.tgz", + "integrity": "sha512-c4OUY5Wq03ZLI4Ds9IEHEiPqpjmEXvDpANXufCTlk+bMb8MnweXPVWJSrSHydXnz7r+qstZl3HvWDl2RI5o/1A==" }, "@humanwhocodes/config-array": { "version": "0.10.7", @@ -20401,14 +20384,6 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, - "@types/localforage": { - "version": "0.0.34", - "resolved": "https://registry.npmjs.org/@types/localforage/-/localforage-0.0.34.tgz", - "integrity": "sha1-XjHDLdh5HsS5/z70fJy1Wy0NlDg=", - "requires": { - "localforage": "*" - } - }, "@types/lodash": { "version": "4.14.178", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", @@ -20589,14 +20564,14 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.0.tgz", - "integrity": "sha512-GgHi/GNuUbTOeoJiEANi0oI6fF3gBQc3bGFYj40nnAPCbhrtEDf2rjBmefFadweBmO1Du1YovHeDP2h5JLhtTQ==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.39.0.tgz", + "integrity": "sha512-xVfKOkBm5iWMNGKQ2fwX5GVgBuHmZBO1tCRwXmY5oAIsPscfwm2UADDuNB8ZVYCtpQvJK4xpjrK7jEhcJ0zY9A==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.38.0", - "@typescript-eslint/type-utils": "5.38.0", - "@typescript-eslint/utils": "5.38.0", + "@typescript-eslint/scope-manager": "5.39.0", + "@typescript-eslint/type-utils": "5.39.0", + "@typescript-eslint/utils": "5.39.0", "debug": "^4.3.4", "ignore": "^5.2.0", "regexpp": "^3.2.0", @@ -20605,9 +20580,9 @@ }, "dependencies": { "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -20616,53 +20591,53 @@ } }, "@typescript-eslint/parser": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.38.0.tgz", - "integrity": "sha512-/F63giJGLDr0ms1Cr8utDAxP2SPiglaD6V+pCOcG35P2jCqdfR7uuEhz1GIC3oy4hkUF8xA1XSXmd9hOh/a5EA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.39.0.tgz", + "integrity": "sha512-PhxLjrZnHShe431sBAGHaNe6BDdxAASDySgsBCGxcBecVCi8NQWxQZMcizNA4g0pN51bBAn/FUfkWG3SDVcGlA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.38.0", - "@typescript-eslint/types": "5.38.0", - "@typescript-eslint/typescript-estree": "5.38.0", + "@typescript-eslint/scope-manager": "5.39.0", + "@typescript-eslint/types": "5.39.0", + "@typescript-eslint/typescript-estree": "5.39.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.38.0.tgz", - "integrity": "sha512-ByhHIuNyKD9giwkkLqzezZ9y5bALW8VNY6xXcP+VxoH4JBDKjU5WNnsiD4HJdglHECdV+lyaxhvQjTUbRboiTA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.39.0.tgz", + "integrity": "sha512-/I13vAqmG3dyqMVSZPjsbuNQlYS082Y7OMkwhCfLXYsmlI0ca4nkL7wJ/4gjX70LD4P8Hnw1JywUVVAwepURBw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.38.0", - "@typescript-eslint/visitor-keys": "5.38.0" + "@typescript-eslint/types": "5.39.0", + "@typescript-eslint/visitor-keys": "5.39.0" } }, "@typescript-eslint/type-utils": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.38.0.tgz", - "integrity": "sha512-iZq5USgybUcj/lfnbuelJ0j3K9dbs1I3RICAJY9NZZpDgBYXmuUlYQGzftpQA9wC8cKgtS6DASTvF3HrXwwozA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.39.0.tgz", + "integrity": "sha512-KJHJkOothljQWzR3t/GunL0TPKY+fGJtnpl+pX+sJ0YiKTz3q2Zr87SGTmFqsCMFrLt5E0+o+S6eQY0FAXj9uA==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.38.0", - "@typescript-eslint/utils": "5.38.0", + "@typescript-eslint/typescript-estree": "5.39.0", + "@typescript-eslint/utils": "5.39.0", "debug": "^4.3.4", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.38.0.tgz", - "integrity": "sha512-HHu4yMjJ7i3Cb+8NUuRCdOGu2VMkfmKyIJsOr9PfkBVYLYrtMCK/Ap50Rpov+iKpxDTfnqvDbuPLgBE5FwUNfA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.39.0.tgz", + "integrity": "sha512-gQMZrnfEBFXK38hYqt8Lkwt8f4U6yq+2H5VDSgP/qiTzC8Nw8JO3OuSUOQ2qW37S/dlwdkHDntkZM6SQhKyPhw==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.38.0.tgz", - "integrity": "sha512-6P0RuphkR+UuV7Avv7MU3hFoWaGcrgOdi8eTe1NwhMp2/GjUJoODBTRWzlHpZh6lFOaPmSvgxGlROa0Sg5Zbyg==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.39.0.tgz", + "integrity": "sha512-qLFQP0f398sdnogJoLtd43pUgB18Q50QSA+BTE5h3sUxySzbWDpTSdgt4UyxNSozY/oDK2ta6HVAzvGgq8JYnA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.38.0", - "@typescript-eslint/visitor-keys": "5.38.0", + "@typescript-eslint/types": "5.39.0", + "@typescript-eslint/visitor-keys": "5.39.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -20691,9 +20666,9 @@ } }, "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", "dev": true, "requires": { "lru-cache": "^6.0.0" @@ -20702,26 +20677,26 @@ } }, "@typescript-eslint/utils": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.38.0.tgz", - "integrity": "sha512-6sdeYaBgk9Fh7N2unEXGz+D+som2QCQGPAf1SxrkEr+Z32gMreQ0rparXTNGRRfYUWk/JzbGdcM8NSSd6oqnTA==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.39.0.tgz", + "integrity": "sha512-+DnY5jkpOpgj+EBtYPyHRjXampJfC0yUZZzfzLuUWVZvCuKqSdJVC8UhdWipIw7VKNTfwfAPiOWzYkAwuIhiAg==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.38.0", - "@typescript-eslint/types": "5.38.0", - "@typescript-eslint/typescript-estree": "5.38.0", + "@typescript-eslint/scope-manager": "5.39.0", + "@typescript-eslint/types": "5.39.0", + "@typescript-eslint/typescript-estree": "5.39.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" } }, "@typescript-eslint/visitor-keys": { - "version": "5.38.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.38.0.tgz", - "integrity": "sha512-MxnrdIyArnTi+XyFLR+kt/uNAcdOnmT+879os7qDRI+EYySR4crXJq9BXPfRzzLGq0wgxkwidrCJ9WCAoacm1w==", + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.39.0.tgz", + "integrity": "sha512-yyE3RPwOG+XJBLrhvsxAidUgybJVQ/hG8BhiJo0k8JSAYfk/CshVcxf0HwP4Jt7WZZ6vLmxdo1p6EyN3tzFTkg==", "dev": true, "requires": { - "@typescript-eslint/types": "5.38.0", + "@typescript-eslint/types": "5.39.0", "eslint-visitor-keys": "^3.3.0" }, "dependencies": { @@ -20908,11 +20883,6 @@ "dev": true, "requires": {} }, - "@xmldom/xmldom": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz", - "integrity": "sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A==" - }, "@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -20926,9 +20896,9 @@ "devOptional": true }, "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, "accepts": { @@ -21358,9 +21328,9 @@ "dev": true }, "blurhash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.0.tgz", - "integrity": "sha512-fdEZnyJZ5E5s9neCfZUMSMkKfMtdKz1fG53t/iYvMjUFUsDnyZ1YnRRayKBK/B8cilNwe5gaIrPF8QlLrukEZQ==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.3.tgz", + "integrity": "sha512-nTnJTOheiaV3b189f7rH5AbbrnQB2r3CcOZBg47GUDaE9DrxyBPD2w0HYp4ME2UBlTP7LMIa6nMWqg/58oyIzA==" }, "body-parser": { "version": "1.20.0", @@ -21997,9 +21967,9 @@ } }, "core-js": { - "version": "3.25.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.25.3.tgz", - "integrity": "sha512-y1hvKXmPHvm5B7w4ln1S4uc9eV/O5+iFExSRUimnvIph11uaizFR8LFMdONN8hG3P2pipUfX4Y/fR8rAEtcHcQ==" + "version": "3.25.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.25.5.tgz", + "integrity": "sha512-nbm6eZSjm+ZuBQxCUPQKQCoUEfFOXjUZ8dTTyikyKaWrTYmAVbykQfwsKE5dBK88u3QCkCrzsx/PPlKfhsvgpw==" }, "core-js-compat": { "version": "3.25.3", @@ -22700,19 +22670,17 @@ "dev": true }, "epubjs": { - "version": "0.3.93", - "resolved": "https://registry.npmjs.org/epubjs/-/epubjs-0.3.93.tgz", - "integrity": "sha512-c06pNSdBxcXv3dZSbXAVLE1/pmleRhOT6mXNZo6INKmvuKpYB65MwU/lO7830czCtjIiK9i+KR+3S+p0wtljrw==", + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/epubjs/-/epubjs-0.4.2.tgz", + "integrity": "sha512-ex+ntja2AmPeq++qgjYfwrEzrO8UUBbTch1RJcRftShUmn8no6qi4Cax75FH0QopLA+6L8HM6iR94M0/I8V3GQ==", "requires": { - "@types/localforage": "0.0.34", - "@xmldom/xmldom": "^0.7.5", - "core-js": "^3.18.3", "event-emitter": "^0.3.5", - "jszip": "^3.7.1", - "localforage": "^1.10.0", - "lodash": "^4.17.21", - "marks-pane": "^1.0.9", - "path-webpack": "0.0.3" + "jszip": "^3.1.5", + "lodash": "^4.17.5", + "marks-pane": "^1.0.7", + "path-webpack": "0.0.3", + "stream-browserify": "^2.0.1", + "xmldom": "^0.1.27" } }, "error-ex": { @@ -25352,14 +25320,6 @@ "resolved": "https://registry.npmjs.org/libarchive.js/-/libarchive.js-1.3.0.tgz", "integrity": "sha512-EkQfRXt9DhWwj6BnEA2TNpOf4jTnzSTUPGgE+iFxcdNqjktY8GitbDeHnx8qZA0/IukNyyBUR3oQKRdYkO+HFg==" }, - "lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", - "requires": { - "immediate": "~3.0.5" - } - }, "lilconfig": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", @@ -25389,14 +25349,6 @@ "json5": "^2.1.2" } }, - "localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "requires": { - "lie": "3.1.1" - } - }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -25546,9 +25498,9 @@ "dev": true }, "marked": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.0.tgz", - "integrity": "sha512-+Z6KDjSPa6/723PQYyc1axYZpYYpDnECDaU6hkaf5gqBieBkMKYReL5hteF2QizhlMbgbo8umXl/clZ67+GlsA==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.1.tgz", + "integrity": "sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==" }, "marks-pane": { "version": "1.0.9", @@ -26379,9 +26331,9 @@ "dev": true }, "postcss": { - "version": "8.4.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", - "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "version": "8.4.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.17.tgz", + "integrity": "sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==", "dev": true, "requires": { "nanoid": "^3.3.4", @@ -28518,14 +28470,14 @@ "dev": true }, "source-map-loader": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-3.0.1.tgz", - "integrity": "sha512-Vp1UsfyPvgujKQzi4pyDiTOnE3E4H+yHvkVRN3c/9PJmQS4CQJExvcDvaX/D+RV+xQben9HJ56jMJS3CgUeWyA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.0.tgz", + "integrity": "sha512-i3KVgM3+QPAHNbGavK+VBq03YoJl24m9JWNbLgsjTj8aJzXG9M61bantBTNBt7CNwY2FYf+RJRYJ3pzalKjIrw==", "dev": true, "requires": { - "abab": "^2.0.5", + "abab": "^2.0.6", "iconv-lite": "^0.6.3", - "source-map-js": "^1.0.1" + "source-map-js": "^1.0.2" } }, "source-map-resolve": { @@ -28708,6 +28660,15 @@ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", "dev": true }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -28868,9 +28829,9 @@ } }, "stylelint": { - "version": "14.12.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.12.1.tgz", - "integrity": "sha512-ZEM4TuksChMBfuPadQsHUkbOoRySAT9QMfDvvYxdAchOJl0p+csTMBXOu6ORAAxKhwBmxqJiep8V88bXfNs3EQ==", + "version": "14.13.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.13.0.tgz", + "integrity": "sha512-NJSAdloiAB/jgVJKxMR90mWlctvmeBFGFVUvyKngi9+j/qPSJ5ZB+u8jOmGbLTnS7OHrII9NFGehPRyar8U5vg==", "dev": true, "requires": { "@csstools/selector-specificity": "^2.0.2", @@ -31280,9 +31241,9 @@ } }, "typescript": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz", - "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==", + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "dev": true }, "unbox-primitive": { @@ -32221,6 +32182,11 @@ "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", "dev": true }, + "xmldom": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz", + "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 319aca2c9..e575ef961 100644 --- a/package.json +++ b/package.json @@ -5,21 +5,21 @@ "repository": "https://github.com/jellyfin/jellyfin-web", "license": "GPL-2.0-or-later", "devDependencies": { - "@babel/core": "7.19.1", + "@babel/core": "7.19.3", "@babel/eslint-parser": "7.19.1", "@babel/eslint-plugin": "7.19.1", "@babel/plugin-proposal-class-properties": "7.18.6", "@babel/plugin-proposal-private-methods": "7.18.6", "@babel/plugin-transform-modules-umd": "7.18.6", - "@babel/preset-env": "7.19.1", + "@babel/preset-env": "7.19.3", "@babel/preset-react": "7.18.6", "@babel/preset-typescript": "7.18.6", "@types/escape-html": "1.0.2", "@types/lodash-es": "4.17.6", "@types/react": "17.0.50", "@types/react-dom": "17.0.17", - "@typescript-eslint/eslint-plugin": "5.38.0", - "@typescript-eslint/parser": "5.38.0", + "@typescript-eslint/eslint-plugin": "5.39.0", + "@typescript-eslint/parser": "5.39.0", "@uupaa/dynamic-import-polyfill": "1.0.2", "autoprefixer": "10.4.12", "babel-loader": "8.2.5", @@ -42,21 +42,21 @@ "html-loader": "4.2.0", "html-webpack-plugin": "5.5.0", "mini-css-extract-plugin": "2.6.1", - "postcss": "8.4.16", + "postcss": "8.4.17", "postcss-loader": "7.0.1", "postcss-preset-env": "7.8.2", "postcss-scss": "4.0.5", "sass": "1.55.0", "sass-loader": "13.0.2", - "source-map-loader": "3.0.1", + "source-map-loader": "4.0.0", "style-loader": "3.3.1", - "stylelint": "14.12.1", + "stylelint": "14.13.0", "stylelint-config-rational-order": "0.1.2", "stylelint-no-browser-hacks": "1.2.1", "stylelint-order": "5.0.0", "stylelint-scss": "4.3.0", "ts-loader": "9.4.1", - "typescript": "4.8.3", + "typescript": "4.8.4", "webpack": "5.74.0", "webpack-cli": "4.10.0", "webpack-dev-server": "4.11.1", @@ -66,20 +66,20 @@ }, "dependencies": { "@fontsource/noto-sans": "4.5.11", - "@fontsource/noto-sans-hk": "4.5.11", - "@fontsource/noto-sans-jp": "4.5.11", - "@fontsource/noto-sans-kr": "4.5.11", - "@fontsource/noto-sans-sc": "4.5.11", - "@fontsource/noto-sans-tc": "4.5.11", + "@fontsource/noto-sans-hk": "4.5.12", + "@fontsource/noto-sans-jp": "4.5.12", + "@fontsource/noto-sans-kr": "4.5.12", + "@fontsource/noto-sans-sc": "4.5.12", + "@fontsource/noto-sans-tc": "4.5.12", "@jellyfin/libass-wasm": "4.1.1", "@jellyfin/sdk": "0.7.0", - "blurhash": "2.0.0", + "blurhash": "2.0.3", "classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz", "classnames": "2.3.2", - "core-js": "3.25.3", + "core-js": "3.25.5", "date-fns": "2.29.3", "dompurify": "2.4.0", - "epubjs": "0.3.93", + "epubjs": "0.4.2", "escape-html": "1.0.3", "fast-text-encoding": "1.0.6", "flv.js": "1.6.2", @@ -92,7 +92,7 @@ "jstree": "3.3.12", "libarchive.js": "1.3.0", "lodash-es": "4.17.21", - "marked": "4.1.0", + "marked": "4.1.1", "material-design-icons-iconfont": "6.7.0", "native-promise-only": "0.8.1", "pdfjs-dist": "2.16.105", diff --git a/src/assets/img/devices/home-assistant.svg b/src/assets/img/devices/home-assistant.svg new file mode 100644 index 000000000..a34be98de --- /dev/null +++ b/src/assets/img/devices/home-assistant.svg @@ -0,0 +1 @@ + diff --git a/src/components/appRouter.js b/src/components/appRouter.js index f2bc0e27b..9bfdc8570 100644 --- a/src/components/appRouter.js +++ b/src/components/appRouter.js @@ -87,12 +87,10 @@ class AppRouter { path = path.replace(this.baseUrl(), ''); - if (this.currentRouteInfo && this.currentRouteInfo.path === path) { - // can't use this with home right now due to the back menu - if (this.currentRouteInfo.route.type !== 'home') { - loading.hide(); - return Promise.resolve(); - } + // can't use this with home right now due to the back menu + if (this.currentRouteInfo?.path === path && this.currentRouteInfo.route.type !== 'home') { + loading.hide(); + return Promise.resolve(); } this.promiseShow = new Promise((resolve) => { @@ -351,15 +349,13 @@ class AppRouter { onRequestFail(_e, data) { const apiClient = this; - if (data.status === 403) { - if (data.errorCode === 'ParentalControl') { - const isCurrentAllowed = appRouter.currentRouteInfo ? (appRouter.currentRouteInfo.route.anonymous || appRouter.currentRouteInfo.route.startup) : true; + if (data.status === 403 && data.errorCode === 'ParentalControl') { + const isCurrentAllowed = appRouter.currentRouteInfo ? (appRouter.currentRouteInfo.route.anonymous || appRouter.currentRouteInfo.route.startup) : true; - // Bounce to the login screen, but not if a password entry fails, obviously - if (!isCurrentAllowed) { - appRouter.showForcedLogoutMessage(globalize.translate('AccessRestrictedTryAgainLater')); - appRouter.showLocalLogin(apiClient.serverId()); - } + // Bounce to the login screen, but not if a password entry fails, obviously + if (!isCurrentAllowed) { + appRouter.showForcedLogoutMessage(globalize.translate('AccessRestrictedTryAgainLater')); + appRouter.showLocalLogin(apiClient.serverId()); } } } diff --git a/src/components/apphost.js b/src/components/apphost.js index 19b7d5233..527658215 100644 --- a/src/components/apphost.js +++ b/src/components/apphost.js @@ -166,11 +166,7 @@ function supportsHtmlMediaAutoplay() { return true; } - if (browser.mobile) { - return false; - } - - return true; + return !browser.mobile; } function supportsCue() { diff --git a/src/components/backdrop/backdrop.js b/src/components/backdrop/backdrop.js index bcb4f5401..d1af617da 100644 --- a/src/components/backdrop/backdrop.js +++ b/src/components/backdrop/backdrop.js @@ -10,24 +10,13 @@ import './backdrop.scss'; /* eslint-disable indent */ function enableAnimation() { - if (browser.slow) { - return false; - } - - return true; + return !browser.slow; } function enableRotation() { - if (browser.tv) { - return false; - } - - // Causes high cpu usage - if (browser.firefox) { - return false; - } - - return true; + return !browser.tv + // Causes high cpu usage + && !browser.firefox; } class Backdrop { diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index db811116e..658837df8 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -390,7 +390,8 @@ import { appRouter } from '../appRouter'; } else if (options.indexBy === 'ProductionYear') { newIndexValue = item.ProductionYear; } else if (options.indexBy === 'CommunityRating') { - newIndexValue = item.CommunityRating ? (Math.floor(item.CommunityRating) + (item.CommunityRating % 1 >= 0.5 ? 0.5 : 0)) + '+' : null; + const roundedRatingDecimal = item.CommunityRating % 1 >= 0.5 ? 0.5 : 0; + newIndexValue = item.CommunityRating ? (Math.floor(item.CommunityRating) + roundedRatingDecimal) + '+' : null; } if (newIndexValue !== currentIndexValue) { @@ -512,6 +513,7 @@ import { appRouter } from '../appRouter'; let imgType = null; let itemId = null; + /* eslint-disable sonarjs/no-duplicated-branches */ if (options.preferThumb && item.ImageTags && item.ImageTags.Thumb) { imgType = 'Thumb'; imgTag = item.ImageTags.Thumb; @@ -608,6 +610,7 @@ import { appRouter } from '../appRouter'; imgTag = item.ParentBackdropImageTags[0]; itemId = item.ParentBackdropItemId; } + /* eslint-enable sonarjs/no-duplicated-branches */ if (!itemId) { itemId = item.Id; @@ -790,10 +793,8 @@ import { appRouter } from '../appRouter'; const showOtherText = isOuterFooter ? !overlayText : overlayText; - if (isOuterFooter && options.cardLayout && layoutManager.mobile) { - if (options.cardFooterAside !== 'none') { - html += ``; - } + if (isOuterFooter && options.cardLayout && layoutManager.mobile && options.cardFooterAside !== 'none') { + html += ``; } const cssClass = options.centerText ? 'cardText cardTextCentered' : 'cardText'; @@ -803,33 +804,31 @@ import { appRouter } from '../appRouter'; const parentTitleUnderneath = item.Type === 'MusicAlbum' || item.Type === 'Audio' || item.Type === 'MusicVideo'; let titleAdded; - if (showOtherText) { - if ((options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) { - if (isOuterFooter && item.Type === 'Episode' && item.SeriesName) { - if (item.SeriesId) { - lines.push(getTextActionButton({ - Id: item.SeriesId, - ServerId: serverId, - Name: item.SeriesName, - Type: 'Series', - IsFolder: true - })); - } else { - lines.push(escapeHtml(item.SeriesName)); + if (showOtherText && (options.showParentTitle || options.showParentTitleOrTitle) && !parentTitleUnderneath) { + if (isOuterFooter && item.Type === 'Episode' && item.SeriesName) { + if (item.SeriesId) { + lines.push(getTextActionButton({ + Id: item.SeriesId, + ServerId: serverId, + Name: item.SeriesName, + Type: 'Series', + IsFolder: true + })); + } else { + lines.push(escapeHtml(item.SeriesName)); + } + } else { + if (isUsingLiveTvNaming(item)) { + lines.push(escapeHtml(item.Name)); + + if (!item.EpisodeTitle && !item.IndexNumber) { + titleAdded = true; } } else { - if (isUsingLiveTvNaming(item)) { - lines.push(escapeHtml(item.Name)); + const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''; - if (!item.EpisodeTitle && !item.IndexNumber) { - titleAdded = true; - } - } else { - const parentTitle = item.SeriesName || item.Series || item.Album || item.AlbumArtist || ''; - - if (parentTitle || showTitle) { - lines.push(escapeHtml(parentTitle)); - } + if (parentTitle || showTitle) { + lines.push(escapeHtml(parentTitle)); } } } @@ -987,10 +986,8 @@ import { appRouter } from '../appRouter'; } } - if (options.showPersonRoleOrType) { - if (item.Role) { - lines.push(globalize.translate('PersonRole', escapeHtml(item.Role))); - } + if (options.showPersonRoleOrType && item.Role) { + lines.push(globalize.translate('PersonRole', escapeHtml(item.Role))); } } @@ -1010,13 +1007,11 @@ import { appRouter } from '../appRouter'; html += progressHtml; } - if (html) { - if (!isOuterFooter || logoUrl || options.cardLayout) { - html = '
' + html; + if (html && (!isOuterFooter || logoUrl || options.cardLayout)) { + html = '
' + html; - //cardFooter - html += '
'; - } + //cardFooter + html += '
'; } return html; diff --git a/src/components/cardbuilder/chaptercardbuilder.js b/src/components/cardbuilder/chaptercardbuilder.js index da8de34f9..f479fb4c8 100644 --- a/src/components/cardbuilder/chaptercardbuilder.js +++ b/src/components/cardbuilder/chaptercardbuilder.js @@ -34,10 +34,8 @@ import ServerConnections from '../ServerConnections'; let shape = (options.backdropShape || 'backdrop'); - if (videoStream.Width && videoStream.Height) { - if ((videoStream.Width / videoStream.Height) <= 1.2) { - shape = (options.squareShape || 'square'); - } + if (videoStream.Width && videoStream.Height && (videoStream.Width / videoStream.Height) <= 1.2) { + shape = (options.squareShape || 'square'); } className += ` ${shape}Card`; diff --git a/src/components/channelMapper/channelMapper.js b/src/components/channelMapper/channelMapper.js index be9ff841c..840f8bfd5 100644 --- a/src/components/channelMapper/channelMapper.js +++ b/src/components/channelMapper/channelMapper.js @@ -87,7 +87,8 @@ export default class channelMapper { html += ''; html += ''; html += ``; - return html += ''; + html += ''; + return html; } function getEditorHtml() { @@ -100,7 +101,8 @@ export default class channelMapper { html += ''; html += ''; html += ''; - return html += ''; + html += ''; + return html; } function initEditor(dlg, options) { diff --git a/src/components/dashboard/users/UserCardBox.tsx b/src/components/dashboard/users/UserCardBox.tsx index adc740180..05379be5b 100644 --- a/src/components/dashboard/users/UserCardBox.tsx +++ b/src/components/dashboard/users/UserCardBox.tsx @@ -1,7 +1,7 @@ import type { UserDto } from '@jellyfin/sdk/lib/generated-client'; import React, { FunctionComponent } from 'react'; import { formatDistanceToNow } from 'date-fns'; -import { localeWithSuffix } from '../../../scripts/dfnshelper'; +import { getLocaleWithSuffix } from '../../../scripts/dfnshelper'; import globalize from '../../../scripts/globalize'; import cardBuilder from '../../cardbuilder/cardBuilder'; import IconButtonElement from '../../../elements/IconButtonElement'; @@ -23,7 +23,7 @@ type IProps = { const getLastSeenText = (lastActivityDate?: string | null) => { if (lastActivityDate) { - return globalize.translate('LastSeen', formatDistanceToNow(Date.parse(lastActivityDate), localeWithSuffix)); + return globalize.translate('LastSeen', formatDistanceToNow(Date.parse(lastActivityDate), getLocaleWithSuffix())); } return ''; diff --git a/src/components/dialogHelper/dialogHelper.js b/src/components/dialogHelper/dialogHelper.js index 80b269fed..9c1ecec62 100644 --- a/src/components/dialogHelper/dialogHelper.js +++ b/src/components/dialogHelper/dialogHelper.js @@ -57,6 +57,7 @@ import '../../assets/css/scrollstyles.scss'; if ((shouldClose || !isOpened(dlg)) && unlisten) { unlisten(); + unlisten = null; } if (shouldClose) { @@ -64,6 +65,22 @@ import '../../assets/css/scrollstyles.scss'; } } + function finishClose() { + if (unlisten) { + unlisten(); + unlisten = null; + } + + dlg.dispatchEvent(new CustomEvent('close', { + bubbles: false, + cancelable: false + })); + + resolve({ + element: dlg + }); + } + function onBackCommand(e) { if (e.detail.command === 'back') { e.preventDefault(); @@ -79,6 +96,7 @@ import '../../assets/css/scrollstyles.scss'; if (unlisten) { unlisten(); + unlisten = null; } removeBackdrop(dlg); @@ -92,9 +110,13 @@ import '../../assets/css/scrollstyles.scss'; const state = history.location.state || {}; if (state.dialogs?.length > 0) { if (state.dialogs[state.dialogs.length - 1] === hash) { + unlisten = history.listen(finishClose); history.back(); } else if (state.dialogs.includes(hash)) { console.warn('[dialogHelper] dialog "%s" was closed, but is not the last dialog opened', hash); + + unlisten = history.listen(finishClose); + // Remove the closed dialog hash from the history state history.replace( `${history.location.pathname}${history.location.search}`, @@ -123,18 +145,9 @@ import '../../assets/css/scrollstyles.scss'; } } - //resolve(); - // if we just called history.back(), then use a timeout to allow the history events to fire first - setTimeout(() => { - dlg.dispatchEvent(new CustomEvent('close', { - bubbles: false, - cancelable: false - })); - - resolve({ - element: dlg - }); - }, 1); + if (!unlisten) { + finishClose(); + } } dlg.addEventListener('_close', onDialogClosed); @@ -262,6 +275,11 @@ import '../../assets/css/scrollstyles.scss'; } } + const getAnimationEndHandler = (dlg, callback) => function handler() { + dom.removeEventListener(dlg, dom.whichAnimationEvent(), handler, { once: true }); + callback(); + }; + function animateDialogOpen(dlg) { const onAnimationFinish = () => { focusManager.pushScope(dlg); @@ -277,15 +295,11 @@ import '../../assets/css/scrollstyles.scss'; }; if (enableAnimation()) { - const onFinish = () => { - dom.removeEventListener(dlg, dom.whichAnimationEvent(), onFinish, { - once: true - }); - onAnimationFinish(); - }; - dom.addEventListener(dlg, dom.whichAnimationEvent(), onFinish, { - once: true - }); + dom.addEventListener( + dlg, + dom.whichAnimationEvent(), + getAnimationEndHandler(dlg, onAnimationFinish), + { once: true }); return; } @@ -311,15 +325,12 @@ import '../../assets/css/scrollstyles.scss'; animated = false; break; } - const onFinish = () => { - dom.removeEventListener(dlg, dom.whichAnimationEvent(), onFinish, { - once: true - }); - onAnimationFinish(); - }; - dom.addEventListener(dlg, dom.whichAnimationEvent(), onFinish, { - once: true - }); + + dom.addEventListener( + dlg, + dom.whichAnimationEvent(), + getAnimationEndHandler(dlg, onAnimationFinish), + { once: true }); if (animated) { return; diff --git a/src/components/displaySettings/displaySettings.template.html b/src/components/displaySettings/displaySettings.template.html index d55d04ce2..29b162091 100644 --- a/src/components/displaySettings/displaySettings.template.html +++ b/src/components/displaySettings/displaySettings.template.html @@ -26,6 +26,7 @@ + diff --git a/src/components/filtermenu/filtermenu.js b/src/components/filtermenu/filtermenu.js index 6f2378eca..12f535a18 100644 --- a/src/components/filtermenu/filtermenu.js +++ b/src/components/filtermenu/filtermenu.js @@ -132,6 +132,7 @@ function saveValues(context, settings, settingsKey) { seriesStatuses.push(elems[i].getAttribute('data-filter')); } } + userSettings.setFilter(`${settingsKey}-filter-SeriesStatus`, seriesStatuses.join(',')); // Genres const genres = []; diff --git a/src/components/focusManager.js b/src/components/focusManager.js index 0b0ae0621..1eacc49e7 100644 --- a/src/components/focusManager.js +++ b/src/components/focusManager.js @@ -56,15 +56,8 @@ import scrollManager from './scrollManager'; }).join(',') + ',.focusable'; function isFocusable(elem) { - if (focusableTagNames.indexOf(elem.tagName) !== -1) { - return true; - } - - if (elem.classList && elem.classList.contains('focusable')) { - return true; - } - - return false; + return focusableTagNames.indexOf(elem.tagName) !== -1 + || (elem.classList?.contains('focusable')); } function normalizeFocusable(elem, originalElement) { @@ -97,11 +90,7 @@ import scrollManager from './scrollManager'; // Determines if a focusable element can be focused at a given point in time function isCurrentlyFocusableInternal(elem) { // http://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom - if (elem.offsetParent === null) { - return false; - } - - return true; + return elem.offsetParent !== null; } // Determines if a focusable element can be focused at a given point in time @@ -384,10 +373,11 @@ import scrollManager from './scrollManager'; // See if there's a focusable container, and if so, send the focus command to that if (activeElement) { const nearestElementFocusableParent = dom.parentWithClass(nearestElement, 'focusable'); - if (nearestElementFocusableParent && nearestElementFocusableParent !== nearestElement) { - if (focusableContainer !== nearestElementFocusableParent) { - nearestElement = nearestElementFocusableParent; - } + if (nearestElementFocusableParent + && nearestElementFocusableParent !== nearestElement + && focusableContainer !== nearestElementFocusableParent + ) { + nearestElement = nearestElementFocusableParent; } } focus(nearestElement); diff --git a/src/components/htmlMediaHelper.js b/src/components/htmlMediaHelper.js index 3bb29ca91..f5eab29b9 100644 --- a/src/components/htmlMediaHelper.js +++ b/src/components/htmlMediaHelper.js @@ -26,12 +26,8 @@ import { Events } from 'jellyfin-apiclient'; function canPlayNativeHls() { const media = document.createElement('video'); - if (media.canPlayType('application/x-mpegURL').replace(/no/, '') || - media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, '')) { - return true; - } - - return false; + return !!(media.canPlayType('application/x-mpegURL').replace(/no/, '') || + media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, '')); } export function enableHlsJsPlayer(runTimeTicks, mediaType) { @@ -123,11 +119,10 @@ import { Events } from 'jellyfin-apiclient'; } export function isValidDuration(duration) { - if (duration && !isNaN(duration) && duration !== Number.POSITIVE_INFINITY && duration !== Number.NEGATIVE_INFINITY) { - return true; - } - - return false; + return duration + && !isNaN(duration) + && duration !== Number.POSITIVE_INFINITY + && duration !== Number.NEGATIVE_INFINITY; } function setCurrentTimeIfNeeded(element, seconds) { @@ -159,11 +154,15 @@ import { Events } from 'jellyfin-apiclient'; // (but rewinding cannot happen as the first event with media of non-empty duration) console.debug(`seeking to ${seconds} on ${e.type} event`); setCurrentTimeIfNeeded(element, seconds); - events.forEach(name => element.removeEventListener(name, onMediaChange)); + events.forEach(name => { + element.removeEventListener(name, onMediaChange); + }); if (onMediaReady) onMediaReady(); } }; - events.forEach(name => element.addEventListener(name, onMediaChange)); + events.forEach(name => { + element.addEventListener(name, onMediaChange); + }); } } } @@ -273,28 +272,23 @@ import { Events } from 'jellyfin-apiclient'; hls.on(Hls.Events.ERROR, function (event, data) { console.error('HLS Error: Type: ' + data.type + ' Details: ' + (data.details || '') + ' Fatal: ' + (data.fatal || false)); - switch (data.type) { - case Hls.ErrorTypes.NETWORK_ERROR: - // try to recover network error - if (data.response && data.response.code && data.response.code >= 400) { - console.debug('hls.js response error code: ' + data.response.code); + // try to recover network error + if (data.type === Hls.ErrorTypes.NETWORK_ERROR + && data.response?.code && data.response.code >= 400 + ) { + console.debug('hls.js response error code: ' + data.response.code); - // Trigger failure differently depending on whether this is prior to start of playback, or after - hls.destroy(); + // Trigger failure differently depending on whether this is prior to start of playback, or after + hls.destroy(); - if (reject) { - reject('servererror'); - reject = null; - } else { - onErrorInternal(instance, 'servererror'); - } + if (reject) { + reject('servererror'); + reject = null; + } else { + onErrorInternal(instance, 'servererror'); + } - return; - } - - break; - default: - break; + return; } if (data.fatal) { diff --git a/src/components/indicators/indicators.js b/src/components/indicators/indicators.js index ad7f14e81..9abfffd8e 100644 --- a/src/components/indicators/indicators.js +++ b/src/components/indicators/indicators.js @@ -5,15 +5,9 @@ import './indicators.scss'; import 'material-design-icons-iconfont'; export function enableProgressIndicator(item) { - if (item.MediaType === 'Video' && item.Type !== 'TvChannel') { - return true; - } - - if (item.Type === 'AudioBook' || item.Type === 'AudioPodcast') { - return true; - } - - return false; + return (item.MediaType === 'Video' && item.Type !== 'TvChannel') + || item.Type === 'AudioBook' + || item.Type === 'AudioPodcast'; } export function getProgressHtml(pct, options) { diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index 34c10ffd3..6974f609e 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -72,26 +72,25 @@ import toast from './toast/toast'; } } - if (item.IsFolder || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') { - if (item.CollectionType !== 'livetv') { - if (options.shuffle !== false) { - commands.push({ - name: globalize.translate('Shuffle'), - id: 'shuffle', - icon: 'shuffle' - }); - } - } + if ((item.IsFolder || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') + && item.CollectionType !== 'livetv' + && options.shuffle !== false + ) { + commands.push({ + name: globalize.translate('Shuffle'), + id: 'shuffle', + icon: 'shuffle' + }); } - if (item.MediaType === 'Audio' || item.Type === 'MusicAlbum' || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') { - if (options.instantMix !== false && !itemHelper.isLocalItem(item)) { - commands.push({ - name: globalize.translate('InstantMix'), - id: 'instantmix', - icon: 'explore' - }); - } + if ((item.MediaType === 'Audio' || item.Type === 'MusicAlbum' || item.Type === 'MusicArtist' || item.Type === 'MusicGenre') + && options.instantMix !== false && !itemHelper.isLocalItem(item) + ) { + commands.push({ + name: globalize.translate('InstantMix'), + id: 'instantmix', + icon: 'explore' + }); } if (commands.length) { @@ -180,57 +179,49 @@ import toast from './toast/toast'; } const canEdit = itemHelper.canEdit(user, item); - if (canEdit) { - if (options.edit !== false && item.Type !== 'SeriesTimer') { - const text = (item.Type === 'Timer' || item.Type === 'SeriesTimer') ? globalize.translate('Edit') : globalize.translate('EditMetadata'); - commands.push({ - name: text, - id: 'edit', - icon: 'edit' - }); - } + if (canEdit && options.edit !== false && item.Type !== 'SeriesTimer') { + const text = (item.Type === 'Timer' || item.Type === 'SeriesTimer') ? globalize.translate('Edit') : globalize.translate('EditMetadata'); + commands.push({ + name: text, + id: 'edit', + icon: 'edit' + }); } - if (itemHelper.canEditImages(user, item)) { - if (options.editImages !== false) { - commands.push({ - name: globalize.translate('EditImages'), - id: 'editimages', - icon: 'image' - }); - } + if (itemHelper.canEditImages(user, item) && options.editImages !== false) { + commands.push({ + name: globalize.translate('EditImages'), + id: 'editimages', + icon: 'image' + }); } - if (canEdit) { - if (item.MediaType === 'Video' && item.Type !== 'TvChannel' && item.Type !== 'Program' && item.LocationType !== 'Virtual' && !(item.Type === 'Recording' && item.Status !== 'Completed')) { - if (options.editSubtitles !== false) { - commands.push({ - name: globalize.translate('EditSubtitles'), - id: 'editsubtitles', - icon: 'closed_caption' - }); - } - } + if (canEdit && item.MediaType === 'Video' && item.Type !== 'TvChannel' && item.Type !== 'Program' + && item.LocationType !== 'Virtual' + && !(item.Type === 'Recording' && item.Status !== 'Completed') + && options.editSubtitles !== false + ) { + commands.push({ + name: globalize.translate('EditSubtitles'), + id: 'editsubtitles', + icon: 'closed_caption' + }); } - if (options.identify !== false) { - if (itemHelper.canIdentify(user, item)) { - commands.push({ - name: globalize.translate('Identify'), - id: 'identify', - icon: 'edit' - }); - } + if (options.identify !== false && itemHelper.canIdentify(user, item)) { + commands.push({ + name: globalize.translate('Identify'), + id: 'identify', + icon: 'edit' + }); } - if (item.MediaSources) { - if (options.moremediainfo !== false) { - commands.push({ - name: globalize.translate('MoreMediaInfo'), - id: 'moremediainfo', - icon: 'info' - }); - } + if (item.MediaSources && options.moremediainfo !== false) { + commands.push({ + name: globalize.translate('MoreMediaInfo'), + id: 'moremediainfo', + icon: 'info' + }); } if (item.Type === 'Program' && options.record !== false) { @@ -240,11 +231,7 @@ import toast from './toast/toast'; id: 'record', icon: 'fiber_manual_record' }); - } - } - - if (item.Type === 'Program' && options.record !== false) { - if (!item.TimerId) { + } else { commands.push({ name: globalize.translate('Record'), id: 'record', @@ -277,26 +264,20 @@ import toast from './toast/toast'; }); } - if (!restrictOptions) { - if (options.share === true) { - if (itemHelper.canShare(item, user)) { - commands.push({ - name: globalize.translate('Share'), - id: 'share', - icon: 'share' - }); - } - } + if (!restrictOptions && options.share === true && itemHelper.canShare(item, user)) { + commands.push({ + name: globalize.translate('Share'), + id: 'share', + icon: 'share' + }); } - if (options.sync !== false) { - if (itemHelper.canSync(user, item)) { - commands.push({ - name: globalize.translate('Sync'), - id: 'sync', - icon: 'sync' - }); - } + if (options.sync !== false && itemHelper.canSync(user, item)) { + commands.push({ + name: globalize.translate('Sync'), + id: 'sync', + icon: 'sync' + }); } if (options.openAlbum !== false && item.AlbumId && item.MediaType !== 'Photo') { diff --git a/src/components/itemHelper.js b/src/components/itemHelper.js index 6773a6c79..b051fc74c 100644 --- a/src/components/itemHelper.js +++ b/src/components/itemHelper.js @@ -48,10 +48,8 @@ export function getDisplayName(item, options = {}) { export function supportsAddingToCollection(item) { const invalidTypes = ['Genre', 'MusicGenre', 'Studio', 'UserView', 'CollectionFolder', 'Audio', 'Program', 'Timer', 'SeriesTimer']; - if (item.Type === 'Recording') { - if (item.Status !== 'Completed') { - return false; - } + if (item.Type === 'Recording' && item.Status !== 'Completed') { + return false; } return !item.CollectionType && invalidTypes.indexOf(item.Type) === -1 && item.MediaType !== 'Photo' && !isLocalItem(item); @@ -74,10 +72,8 @@ export function supportsAddingToPlaylist(item) { return false; } - if (item.Type === 'Recording') { - if (item.Status !== 'Completed') { - return false; - } + if (item.Type === 'Recording' && item.Status !== 'Completed') { + return false; } if (isLocalItem(item)) { @@ -109,10 +105,8 @@ export function canEdit(user, item) { return false; } - if (item.Type === 'Recording') { - if (item.Status !== 'Completed') { - return false; - } + if (item.Type === 'Recording' && item.Status !== 'Completed') { + return false; } if (isLocalItem(item)) { @@ -123,33 +117,23 @@ export function canEdit(user, item) { } export function isLocalItem(item) { - if (item && item.Id && typeof item.Id === 'string' && item.Id.indexOf('local') === 0) { - return true; - } - - return false; + return item && item.Id && typeof item.Id === 'string' && item.Id.indexOf('local') === 0; } export function canIdentify (user, item) { const itemType = item.Type; - if (itemType === 'Movie' || - itemType === 'Trailer' || - itemType === 'Series' || - itemType === 'BoxSet' || - itemType === 'Person' || - itemType === 'Book' || - itemType === 'MusicAlbum' || - itemType === 'MusicArtist' || - itemType === 'MusicVideo') { - if (user.Policy.IsAdministrator) { - if (!isLocalItem(item)) { - return true; - } - } - } - - return false; + return (itemType === 'Movie' + || itemType === 'Trailer' + || itemType === 'Series' + || itemType === 'BoxSet' + || itemType === 'Person' + || itemType === 'Book' + || itemType === 'MusicAlbum' + || itemType === 'MusicArtist' + || itemType === 'MusicVideo') + && user.Policy.IsAdministrator + && !isLocalItem(item); } export function canEditImages (user, item) { @@ -160,17 +144,11 @@ export function canEditImages (user, item) { } if (itemType === 'UserView') { - if (user.Policy.IsAdministrator) { - return true; - } - - return false; + return !!user.Policy.IsAdministrator; } - if (item.Type === 'Recording') { - if (item.Status !== 'Completed') { - return false; - } + if (item.Type === 'Recording' && item.Status !== 'Completed') { + return false; } return itemType !== 'Timer' && itemType !== 'SeriesTimer' && canEdit(user, item) && !isLocalItem(item); @@ -201,10 +179,8 @@ export function canShare (item, user) { if (item.Type === 'SeriesTimer') { return false; } - if (item.Type === 'Recording') { - if (item.Status !== 'Completed') { - return false; - } + if (item.Type === 'Recording' && item.Status !== 'Completed') { + return false; } if (isLocalItem(item)) { return false; @@ -234,29 +210,21 @@ export function canMarkPlayed (item) { } } - if (item.Type === 'Series' || - item.Type === 'Season' || - item.Type === 'BoxSet' || - item.MediaType === 'Book' || - item.MediaType === 'Recording') { - return true; - } - - return false; + return item.Type === 'Series' + || item.Type === 'Season' + || item.Type === 'BoxSet' + || item.MediaType === 'Book' + || item.MediaType === 'Recording'; } export function canRate (item) { - if (item.Type === 'Program' - || item.Type === 'Timer' - || item.Type === 'SeriesTimer' - || item.Type === 'CollectionFolder' - || item.Type === 'UserView' - || item.Type === 'Channel' - || !item.UserData) { - return false; - } - - return true; + return item.Type !== 'Program' + && item.Type !== 'Timer' + && item.Type !== 'SeriesTimer' + && item.Type !== 'CollectionFolder' + && item.Type !== 'UserView' + && item.Type !== 'Channel' + && item.UserData; } export function canConvert (item, user) { @@ -287,11 +255,7 @@ export function canConvert (item, user) { return false; } - if (item.IsPlaceHolder) { - return false; - } - - return true; + return !item.IsPlaceHolder; } export function canRefreshMetadata (item, user) { @@ -301,11 +265,10 @@ export function canRefreshMetadata (item, user) { return false; } - if (item.Type !== 'Timer' && item.Type !== 'SeriesTimer' && item.Type !== 'Program' && item.Type !== 'TvChannel' && !(item.Type === 'Recording' && item.Status !== 'Completed')) { - if (!isLocalItem(item)) { - return true; - } - } + return item.Type !== 'Timer' && item.Type !== 'SeriesTimer' && item.Type !== 'Program' + && item.Type !== 'TvChannel' + && !(item.Type === 'Recording' && item.Status !== 'Completed') + && !isLocalItem(item); } return false; @@ -321,14 +284,12 @@ export function supportsMediaSourceSelection (item) { if (!item.MediaSources || (item.MediaSources.length === 1 && item.MediaSources[0].Type === 'Placeholder')) { return false; } - if (item.EnableMediaSourceDisplay === false) { - return false; - } - if (item.EnableMediaSourceDisplay == null && item.SourceType && item.SourceType !== 'Library') { - return false; + + if (item.EnableMediaSourceDisplay != null) { + return !!item.EnableMediaSourceDisplay; } - return true; + return !item.SourceType || item.SourceType === 'Library'; } export function sortTracks (trackA, trackB) { diff --git a/src/components/itemsrefresher.js b/src/components/itemsrefresher.js index 5a5d93e15..8b71d9c92 100644 --- a/src/components/itemsrefresher.js +++ b/src/components/itemsrefresher.js @@ -4,13 +4,12 @@ import { Events } from 'jellyfin-apiclient'; function onUserDataChanged() { const instance = this; - const eventsToMonitor = getEventsToMonitor(instance); // TODO: Check user data change reason? - if (eventsToMonitor.indexOf('markfavorite') !== -1) { - instance.notifyRefreshNeeded(); - } else if (eventsToMonitor.indexOf('markplayed') !== -1) { + if (eventsToMonitor.indexOf('markfavorite') !== -1 + || eventsToMonitor.indexOf('markplayed') !== -1 + ) { instance.notifyRefreshNeeded(); } } @@ -25,37 +24,18 @@ function getEventsToMonitor(instance) { return []; } -function onTimerCreated() { +function notifyTimerRefresh() { const instance = this; if (getEventsToMonitor(instance).indexOf('timers') !== -1) { instance.notifyRefreshNeeded(); - return; } } -function onSeriesTimerCreated() { +function notifySeriesTimerRefresh() { const instance = this; if (getEventsToMonitor(instance).indexOf('seriestimers') !== -1) { instance.notifyRefreshNeeded(); - return; - } -} - -function onTimerCancelled() { - const instance = this; - - if (getEventsToMonitor(instance).indexOf('timers') !== -1) { - instance.notifyRefreshNeeded(); - return; - } -} - -function onSeriesTimerCancelled() { - const instance = this; - if (getEventsToMonitor(instance).indexOf('seriestimers') !== -1) { - instance.notifyRefreshNeeded(); - return; } } @@ -94,16 +74,14 @@ function onPlaybackStopped(e, stopInfo) { const state = stopInfo.state; const eventsToMonitor = getEventsToMonitor(instance); - if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Video') { + if (state.NowPlayingItem?.MediaType === 'Video') { if (eventsToMonitor.indexOf('videoplayback') !== -1) { instance.notifyRefreshNeeded(true); return; } - } else if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Audio') { - if (eventsToMonitor.indexOf('audioplayback') !== -1) { - instance.notifyRefreshNeeded(true); - return; - } + } else if (state.NowPlayingItem?.MediaType === 'Audio' && eventsToMonitor.indexOf('audioplayback') !== -1) { + instance.notifyRefreshNeeded(true); + return; } } @@ -128,10 +106,10 @@ class ItemsRefresher { this.options = options || {}; addNotificationEvent(this, 'UserDataChanged', onUserDataChanged); - addNotificationEvent(this, 'TimerCreated', onTimerCreated); - addNotificationEvent(this, 'SeriesTimerCreated', onSeriesTimerCreated); - addNotificationEvent(this, 'TimerCancelled', onTimerCancelled); - addNotificationEvent(this, 'SeriesTimerCancelled', onSeriesTimerCancelled); + addNotificationEvent(this, 'TimerCreated', notifyTimerRefresh); + addNotificationEvent(this, 'SeriesTimerCreated', notifySeriesTimerRefresh); + addNotificationEvent(this, 'TimerCancelled', notifyTimerRefresh); + addNotificationEvent(this, 'SeriesTimerCancelled', notifySeriesTimerRefresh); addNotificationEvent(this, 'LibraryChanged', onLibraryChanged); addNotificationEvent(this, 'playbackstop', onPlaybackStopped, playbackManager); } diff --git a/src/components/libraryoptionseditor/libraryoptionseditor.js b/src/components/libraryoptionseditor/libraryoptionseditor.js index 309e52321..522ee3058 100644 --- a/src/components/libraryoptionseditor/libraryoptionseditor.js +++ b/src/components/libraryoptionseditor/libraryoptionseditor.js @@ -383,7 +383,6 @@ import template from './libraryoptionseditor.template.html'; return setContentType(parent, contentType).then(function() { libraryOptions && setLibraryOptions(parent, libraryOptions); bindEvents(parent); - return; }); }); } diff --git a/src/components/listview/listview.js b/src/components/listview/listview.js index 9db6c78a5..d6c34172f 100644 --- a/src/components/listview/listview.js +++ b/src/components/listview/listview.js @@ -328,10 +328,8 @@ import ServerConnections from '../ServerConnections'; textlines.push(datetime.getDisplayTime(datetime.parseISO8601Date(item.StartDate))); } - if (options.showChannel) { - if (item.ChannelName) { - textlines.push(item.ChannelName); - } + if (options.showChannel && item.ChannelName) { + textlines.push(item.ChannelName); } let parentTitle = null; @@ -370,10 +368,8 @@ import ServerConnections from '../ServerConnections'; } if (item.IsFolder) { - if (options.artist !== false) { - if (item.AlbumArtist && item.Type === 'MusicAlbum') { + if (options.artist !== false && item.AlbumArtist && item.Type === 'MusicAlbum') { textlines.push(item.AlbumArtist); - } } } else { if (options.artist) { @@ -386,10 +382,8 @@ import ServerConnections from '../ServerConnections'; } } - if (item.Type === 'TvChannel') { - if (item.CurrentProgram) { - textlines.push(itemHelper.getDisplayName(item.CurrentProgram)); - } + if (item.Type === 'TvChannel' && item.CurrentProgram) { + textlines.push(itemHelper.getDisplayName(item.CurrentProgram)); } cssClass = 'listItemBody'; @@ -405,19 +399,17 @@ import ServerConnections from '../ServerConnections'; html += getTextLinesHtml(textlines, isLargeStyle); - if (options.mediaInfo !== false) { - if (!enableSideMediaInfo) { - const mediaInfoClass = 'secondary listItemMediaInfo listItemBodyText'; + if (options.mediaInfo !== false && !enableSideMediaInfo) { + const mediaInfoClass = 'secondary listItemMediaInfo listItemBodyText'; - html += `
`; - html += mediaInfo.getPrimaryMediaInfoHtml(item, { - episodeTitle: false, - originalAirDate: false, - subtitles: false + html += `
`; + html += mediaInfo.getPrimaryMediaInfoHtml(item, { + episodeTitle: false, + originalAirDate: false, + subtitles: false - }); - html += '
'; - } + }); + html += '
'; } if (enableOverview && item.Overview) { @@ -428,20 +420,18 @@ import ServerConnections from '../ServerConnections'; html += ''; - if (options.mediaInfo !== false) { - if (enableSideMediaInfo) { - html += '
'; - html += mediaInfo.getPrimaryMediaInfoHtml(item, { + if (options.mediaInfo !== false && enableSideMediaInfo) { + html += '
'; + html += mediaInfo.getPrimaryMediaInfoHtml(item, { - year: false, - container: false, - episodeTitle: false, - criticRating: false, - endsAt: false + year: false, + container: false, + episodeTitle: false, + criticRating: false, + endsAt: false - }); - html += '
'; - } + }); + html += '
'; } if (!options.recordButton && (item.Type === 'Timer' || item.Type === 'Program')) { diff --git a/src/components/mediainfo/mediainfo.js b/src/components/mediainfo/mediainfo.js index daa7ec574..b0201162f 100644 --- a/src/components/mediainfo/mediainfo.js +++ b/src/components/mediainfo/mediainfo.js @@ -129,17 +129,18 @@ import '../../elements/emby-button/emby-button'; } } - if ((item.Type === 'Episode' || item.MediaType === 'Photo') && options.originalAirDate !== false) { - if (item.PremiereDate) { - try { - //don't modify date to locale if episode. Only Dates (not times) are stored, or editable in the edit metadata dialog - date = datetime.parseISO8601Date(item.PremiereDate, item.Type !== 'Episode'); + if ((item.Type === 'Episode' || item.MediaType === 'Photo') + && options.originalAirDate !== false + && item.PremiereDate + ) { + try { + //don't modify date to locale if episode. Only Dates (not times) are stored, or editable in the edit metadata dialog + date = datetime.parseISO8601Date(item.PremiereDate, item.Type !== 'Episode'); - text = datetime.toLocaleDateString(date); - miscInfo.push(text); - } catch (e) { - console.error('error parsing date:', item.PremiereDate); - } + text = datetime.toLocaleDateString(date); + miscInfo.push(text); + } catch (e) { + console.error('error parsing date:', item.PremiereDate); } } @@ -239,17 +240,17 @@ import '../../elements/emby-button/emby-button'; } } - if (options.year !== false) { - if (item.Type !== 'Series' && item.Type !== 'Episode' && item.Type !== 'Person' && item.MediaType !== 'Photo' && item.Type !== 'Program' && item.Type !== 'Season') { - if (item.ProductionYear) { - miscInfo.push(item.ProductionYear); - } else if (item.PremiereDate) { - try { - text = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), {useGrouping: false}); - miscInfo.push(text); - } catch (e) { - console.error('error parsing date:', item.PremiereDate); - } + if (options.year !== false && item.Type !== 'Series' && item.Type !== 'Episode' && item.Type !== 'Person' + && item.MediaType !== 'Photo' && item.Type !== 'Program' && item.Type !== 'Season' + ) { + if (item.ProductionYear) { + miscInfo.push(item.ProductionYear); + } else if (item.PremiereDate) { + try { + text = datetime.toLocaleString(datetime.parseISO8601Date(item.PremiereDate).getFullYear(), {useGrouping: false}); + miscInfo.push(text); + } catch (e) { + console.error('error parsing date:', item.PremiereDate); } } } @@ -314,14 +315,12 @@ import '../../elements/emby-button/emby-button'; } export function getEndsAt(item) { - if (item.MediaType === 'Video' && item.RunTimeTicks) { - if (!item.StartDate) { - let endDate = new Date().getTime() + (item.RunTimeTicks / 10000); - endDate = new Date(endDate); + if (item.MediaType === 'Video' && item.RunTimeTicks && !item.StartDate) { + let endDate = new Date().getTime() + (item.RunTimeTicks / 10000); + endDate = new Date(endDate); - const displayTime = datetime.getDisplayTime(endDate); - return globalize.translate('EndsAtValue', displayTime); - } + const displayTime = datetime.getDisplayTime(endDate); + return globalize.translate('EndsAtValue', displayTime); } return null; diff --git a/src/components/nowPlayingBar/nowPlayingBar.js b/src/components/nowPlayingBar/nowPlayingBar.js index ca8c62ae6..fe5b112fd 100644 --- a/src/components/nowPlayingBar/nowPlayingBar.js +++ b/src/components/nowPlayingBar/nowPlayingBar.js @@ -444,12 +444,10 @@ import { appRouter } from '../appRouter'; options = options || {}; options.type = options.type || 'Primary'; - if (options.type === 'Primary') { - if (item.SeriesPrimaryImageTag) { - options.tag = item.SeriesPrimaryImageTag; + if (options.type === 'Primary' && item.SeriesPrimaryImageTag) { + options.tag = item.SeriesPrimaryImageTag; - return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); - } + return ServerConnections.getApiClient(item.ServerId).getScaledImageUrl(item.SeriesId, options); } if (options.type === 'Thumb') { diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 5d0f371f7..8b8bc6392 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -19,11 +19,7 @@ function enableLocalPlaylistManagement(player) { return false; } - if (player.isLocalPlayer) { - return true; - } - - return false; + return player.isLocalPlayer; } function bindToFullscreenChange(player) { @@ -44,10 +40,8 @@ function triggerPlayerChange(playbackManagerInstance, newPlayer, newTarget, prev return; } - if (newTarget && previousTargetInfo) { - if (newTarget.id === previousTargetInfo.id) { - return; - } + if (newTarget && previousTargetInfo && newTarget.id === previousTargetInfo.id) { + return; } Events.trigger(playbackManagerInstance, 'playerchange', [newPlayer, newTarget, previousPlayer]); @@ -227,11 +221,7 @@ function getParam(name, url) { } function isAutomaticPlayer(player) { - if (player.isLocalPlayer) { - return true; - } - - return false; + return player.isLocalPlayer; } function getAutomaticPlayers(instance, forceLocalPlayer) { @@ -246,10 +236,7 @@ function getAutomaticPlayers(instance, forceLocalPlayer) { } function isServerItem(item) { - if (!item.Id) { - return false; - } - return true; + return !!item.Id; } function enableIntros(item) { @@ -501,10 +488,10 @@ function getPlaybackInfo(player, } // lastly, enforce player overrides for special situations - if (query.EnableDirectStream !== false) { - if (player.supportsPlayMethod && !player.supportsPlayMethod('DirectStream', item)) { - query.EnableDirectStream = false; - } + if (query.EnableDirectStream !== false + && player.supportsPlayMethod && !player.supportsPlayMethod('DirectStream', item) + ) { + query.EnableDirectStream = false; } if (player.getDirectPlayProtocols) { @@ -569,10 +556,10 @@ function getLiveStream(player, apiClient, item, playSessionId, deviceProfile, ma } // lastly, enforce player overrides for special situations - if (query.EnableDirectStream !== false) { - if (player.supportsPlayMethod && !player.supportsPlayMethod('DirectStream', item)) { - query.EnableDirectStream = false; - } + if (query.EnableDirectStream !== false + && player.supportsPlayMethod && !player.supportsPlayMethod('DirectStream', item) + ) { + query.EnableDirectStream = false; } return apiClient.ajax({ @@ -963,10 +950,8 @@ class PlaybackManager { self.isPlaying = function (player) { player = player || self._currentPlayer; - if (player) { - if (player.isPlaying) { - return player.isPlaying(); - } + if (player?.isPlaying) { + return player.isPlaying(); } return player != null && player.currentSrc() != null; @@ -975,10 +960,8 @@ class PlaybackManager { self.isPlayingMediaType = function (mediaType, player) { player = player || self._currentPlayer; - if (player) { - if (player.isPlaying) { - return player.isPlaying(mediaType); - } + if (player?.isPlaying) { + return player.isPlaying(mediaType); } if (self.isPlaying(player)) { @@ -1027,10 +1010,8 @@ class PlaybackManager { return true; } - if (item.LocationType === 'Virtual') { - if (itemType !== 'Program') { - return false; - } + if (item.LocationType === 'Virtual' && itemType !== 'Program') { + return false; } if (itemType === 'Program') { @@ -2486,8 +2467,8 @@ class PlaybackManager { playMethod = 'DirectPlay'; } else if (mediaSource.StreamUrl) { // Only used for audio - playMethod = 'Transcode'; mediaUrl = mediaSource.StreamUrl; + // Use the default playMethod value of Transcode } else if (mediaSource.SupportsDirectPlay || mediaSource.SupportsDirectStream) { directOptions = { Static: true, @@ -3015,11 +2996,8 @@ class PlaybackManager { function enablePlaybackRetryWithTranscoding(streamInfo, errorType, currentlyPreventsVideoStreamCopy, currentlyPreventsAudioStreamCopy) { // mediadecodeerror, medianotsupported, network, servererror - if (streamInfo.mediaSource.SupportsTranscoding && (!currentlyPreventsVideoStreamCopy || !currentlyPreventsAudioStreamCopy)) { - return true; - } - - return false; + return streamInfo.mediaSource.SupportsTranscoding + && (!currentlyPreventsVideoStreamCopy || !currentlyPreventsAudioStreamCopy); } function onPlaybackError(e, error) { @@ -3300,10 +3278,10 @@ class PlaybackManager { reportPlayback(self, state, player, reportPlaylist, serverId, 'reportPlaybackProgress', progressEventName); } - if (streamInfo && streamInfo.liveStreamId) { - if (new Date().getTime() - (streamInfo.lastMediaInfoQuery || 0) >= 600000) { - getLiveStreamMediaInfo(player, streamInfo, self.currentMediaSource(player), streamInfo.liveStreamId, serverId); - } + if (streamInfo?.liveStreamId + && (new Date().getTime() - (streamInfo.lastMediaInfoQuery || 0) >= 600000) + ) { + getLiveStreamMediaInfo(player, streamInfo, self.currentMediaSource(player), streamInfo.liveStreamId, serverId); } } } @@ -3568,10 +3546,8 @@ class PlaybackManager { } getBufferedRanges(player = this._currentPlayer) { - if (player) { - if (player.getBufferedRanges) { - return player.getBufferedRanges(); - } + if (player?.getBufferedRanges) { + return player.getBufferedRanges(); } return []; @@ -3842,19 +3818,15 @@ class PlaybackManager { removeActivePlayer(name) { const playerInfo = this.getPlayerInfo(); - if (playerInfo) { - if (playerInfo.name === name) { - this.setDefaultPlayerActive(); - } + if (playerInfo?.name === name) { + this.setDefaultPlayerActive(); } } removeActiveTarget(id) { const playerInfo = this.getPlayerInfo(); - if (playerInfo) { - if (playerInfo.id === id) { - this.setDefaultPlayerActive(); - } + if (playerInfo?.id === id) { + this.setDefaultPlayerActive(); } } diff --git a/src/components/playback/playerSelectionMenu.js b/src/components/playback/playerSelectionMenu.js index f7eea17be..94c3c4b43 100644 --- a/src/components/playback/playerSelectionMenu.js +++ b/src/components/playback/playerSelectionMenu.js @@ -29,10 +29,8 @@ function mirrorIfEnabled(info) { if (info && playbackManager.enableDisplayMirroring()) { const getPlayerInfo = playbackManager.getPlayerInfo(); - if (getPlayerInfo) { - if (!getPlayerInfo.isLocalPlayer && getPlayerInfo.supportedCommands.indexOf('DisplayContent') !== -1) { - mirrorItem(info, playbackManager.getCurrentPlayer()); - } + if (getPlayerInfo && !getPlayerInfo.isLocalPlayer && getPlayerInfo.supportedCommands.indexOf('DisplayContent') !== -1) { + mirrorItem(info, playbackManager.getCurrentPlayer()); } } } @@ -85,11 +83,9 @@ function getIcon(target) { export function show(button) { const currentPlayerInfo = playbackManager.getPlayerInfo(); - if (currentPlayerInfo) { - if (!currentPlayerInfo.isLocalPlayer) { - showActivePlayerMenu(currentPlayerInfo); - return; - } + if (currentPlayerInfo && !currentPlayerInfo.isLocalPlayer) { + showActivePlayerMenu(currentPlayerInfo); + return; } const currentPlayerId = currentPlayerInfo ? currentPlayerInfo.id : null; @@ -299,7 +295,6 @@ document.addEventListener('viewshow', function (e) { mirrorIfEnabled({ item: item }); - return; } }); diff --git a/src/components/playback/playersettingsmenu.js b/src/components/playback/playersettingsmenu.js index efeef4f35..c7f8f5a6d 100644 --- a/src/components/playback/playersettingsmenu.js +++ b/src/components/playback/playersettingsmenu.js @@ -238,7 +238,6 @@ function showWithUser(options, player, user) { return actionsheet.show({ items: menuItems, - resolveOnClick: true, positionTo: options.positionTo }).then(function (id) { return handleSelectedOption(id, options, player); diff --git a/src/components/playerstats/playerstats.js b/src/components/playerstats/playerstats.js index df15dac19..225b0650c 100644 --- a/src/components/playerstats/playerstats.js +++ b/src/components/playerstats/playerstats.js @@ -4,7 +4,7 @@ import globalize from '../../scripts/globalize'; import layoutManager from '../layoutManager'; import { playbackManager } from '../playback/playbackmanager'; import playMethodHelper from '../playback/playmethodhelper'; -import SyncPlay from '../../components/syncPlay/core'; +import SyncPlay from '../../plugins/syncPlay/core'; import './playerstats.scss'; import ServerConnections from '../ServerConnections'; diff --git a/src/components/playlisteditor/playlisteditor.js b/src/components/playlisteditor/playlisteditor.js index 440685fd9..70ae0644d 100644 --- a/src/components/playlisteditor/playlisteditor.js +++ b/src/components/playlisteditor/playlisteditor.js @@ -4,7 +4,7 @@ import dialogHelper from '../dialogHelper/dialogHelper'; import loading from '../loading/loading'; import layoutManager from '../layoutManager'; import { playbackManager } from '../playback/playbackmanager'; -import SyncPlay from '../../components/syncPlay/core'; +import SyncPlay from '../../plugins/syncPlay/core'; import * as userSettings from '../../scripts/settings/userSettings'; import { appRouter } from '../appRouter'; import globalize from '../../scripts/globalize'; diff --git a/src/components/recordingcreator/recordingbutton.js b/src/components/recordingcreator/recordingbutton.js index a05a44b2e..f2d5376ea 100644 --- a/src/components/recordingcreator/recordingbutton.js +++ b/src/components/recordingcreator/recordingbutton.js @@ -106,10 +106,8 @@ function getIndicatorIcon(item) { return 'fiber_manual_record'; } - if (item.SeriesTimerId) { - if (status !== 'Cancelled') { - return 'fiber_smart_record'; - } + if (item.SeriesTimerId && status !== 'Cancelled') { + return 'fiber_smart_record'; } return 'fiber_manual_record'; diff --git a/src/components/recordingcreator/recordingcreator.js b/src/components/recordingcreator/recordingcreator.js index 4387b2e09..6720a4347 100644 --- a/src/components/recordingcreator/recordingcreator.js +++ b/src/components/recordingcreator/recordingcreator.js @@ -129,7 +129,6 @@ function executeCloseAction(action, programId, serverId) { serverId: serverId }); }); - return; } } diff --git a/src/components/recordingcreator/recordingfields.js b/src/components/recordingcreator/recordingfields.js index 179b472bc..64d58e904 100644 --- a/src/components/recordingcreator/recordingfields.js +++ b/src/components/recordingcreator/recordingfields.js @@ -61,40 +61,20 @@ function fetchData(instance) { function onTimerChangedExternally(e, apiClient, data) { const options = this.options; - let refresh = false; - if (data.Id) { - if (this.TimerId === data.Id) { - refresh = true; - } - } - if (data.ProgramId && options) { - if (options.programId === data.ProgramId) { - refresh = true; - } - } - - if (refresh) { + if ((data.Id && this.TimerId === data.Id) + || (data.ProgramId && options && options.programId === data.ProgramId) + ) { this.refresh(); } } function onSeriesTimerChangedExternally(e, apiClient, data) { const options = this.options; - let refresh = false; - if (data.Id) { - if (this.SeriesTimerId === data.Id) { - refresh = true; - } - } - if (data.ProgramId && options) { - if (options.programId === data.ProgramId) { - refresh = true; - } - } - - if (refresh) { + if ((data.Id && this.SeriesTimerId === data.Id) + || (data.ProgramId && options && options.programId === data.ProgramId) + ) { this.refresh(); } } diff --git a/src/components/scrollManager.js b/src/components/scrollManager.js index 53c09e37e..72f5a69fb 100644 --- a/src/components/scrollManager.js +++ b/src/components/scrollManager.js @@ -479,11 +479,7 @@ import layoutManager from './layoutManager'; * Returns true if smooth scroll must be used. */ function useSmoothScroll() { - if (browser.tizen) { - return true; - } - - return false; + return !!browser.tizen; } /** diff --git a/src/components/slideshow/slideshow.js b/src/components/slideshow/slideshow.js index 1dd575112..1112a57c3 100644 --- a/src/components/slideshow/slideshow.js +++ b/src/components/slideshow/slideshow.js @@ -46,11 +46,9 @@ function getImageUrl(item, options, apiClient) { return apiClient.getScaledImageUrl(item.Id, options); } - if (options.type === 'Primary') { - if (item.AlbumId && item.AlbumPrimaryImageTag) { - options.tag = item.AlbumPrimaryImageTag; - return apiClient.getScaledImageUrl(item.AlbumId, options); - } + if (options.type === 'Primary' && item.AlbumId && item.AlbumPrimaryImageTag) { + options.tag = item.AlbumPrimaryImageTag; + return apiClient.getScaledImageUrl(item.AlbumId, options); } return null; diff --git a/src/components/subtitleeditor/subtitleeditor.js b/src/components/subtitleeditor/subtitleeditor.js index ead71f9ce..1aa6d57fc 100644 --- a/src/components/subtitleeditor/subtitleeditor.js +++ b/src/components/subtitleeditor/subtitleeditor.js @@ -114,10 +114,8 @@ function fillSubtitleList(context, item) { itemHtml += ''; itemHtml += ''; - if (!layoutManager.tv) { - if (s.Path) { - itemHtml += ''; - } + if (!layoutManager.tv && s.Path) { + itemHtml += ''; } itemHtml += ''; @@ -336,12 +334,8 @@ function showDownloadOptions(button, context, subtitleId) { positionTo: button }).then(function (id) { - switch (id) { - case 'download': - downloadRemoteSubtitles(context, subtitleId); - break; - default: - break; + if (id === 'download') { + downloadRemoteSubtitles(context, subtitleId); } }); }); diff --git a/src/components/subtitlesync/subtitlesync.js b/src/components/subtitlesync/subtitlesync.js index b5842cf88..80da4d7b1 100644 --- a/src/components/subtitlesync/subtitlesync.js +++ b/src/components/subtitlesync/subtitlesync.js @@ -144,33 +144,32 @@ class SubtitleSync { } toggle(action) { + if (action && !['hide', 'forceToHide'].includes(action)) { + console.warn('SubtitleSync.toggle called with invalid action', action); + return; + } + if (player && playbackManager.supportSubtitleOffset(player)) { - /* eslint-disable no-fallthrough */ - switch (action) { - case undefined: - // if showing subtitle sync is enabled and if there is an external subtitle stream enabled - if (playbackManager.isShowingSubtitleOffsetEnabled(player) && playbackManager.canHandleOffsetOnCurrentSubtitle(player)) { - // if no subtitle offset is defined or element has focus (offset being defined) - if (!(playbackManager.getPlayerSubtitleOffset(player) || subtitleSyncTextField.hasFocus)) { - // set default offset to '0' = 50% - subtitleSyncSlider.value = '50'; - subtitleSyncTextField.textContent = '0s'; - playbackManager.setSubtitleOffset(0, player); - } - // show subtitle sync - subtitleSyncContainer.classList.remove('hide'); - break; // stop here - } // else continue and hide - case 'hide': - // only break if element has focus - if (subtitleSyncTextField.hasFocus) { - break; + if (!action) { + // if showing subtitle sync is enabled and if there is an external subtitle stream enabled + if (playbackManager.isShowingSubtitleOffsetEnabled(player) && playbackManager.canHandleOffsetOnCurrentSubtitle(player)) { + // if no subtitle offset is defined or element has focus (offset being defined) + if (!(playbackManager.getPlayerSubtitleOffset(player) || subtitleSyncTextField.hasFocus)) { + // set default offset to '0' = 50% + subtitleSyncSlider.value = '50'; + subtitleSyncTextField.textContent = '0s'; + playbackManager.setSubtitleOffset(0, player); } - case 'forceToHide': - subtitleSyncContainer.classList.add('hide'); - break; + // show subtitle sync + subtitleSyncContainer.classList.remove('hide'); + return; + } + } else if (action === 'hide' && subtitleSyncTextField.hasFocus) { + // do not hide if element has focus + return; } - /* eslint-enable no-fallthrough */ + + subtitleSyncContainer.classList.add('hide'); } } } diff --git a/src/components/tunerPicker.js b/src/components/tunerPicker.js index d31d3b5f6..68c664409 100644 --- a/src/components/tunerPicker.js +++ b/src/components/tunerPicker.js @@ -26,7 +26,8 @@ function getEditorHtml() { html += '
'; html += '
'; html += ''; - return html += ''; + html += ''; + return html; } function getDeviceHtml(device) { @@ -61,11 +62,12 @@ function getDeviceHtml(device) { html += ''; html += ''; html += ''; - return html += ''; + html += ''; + return html; } function getTunerName(providerId) { - switch (providerId = providerId.toLowerCase()) { + switch (providerId.toLowerCase()) { case 'm3u': return 'M3U'; diff --git a/src/components/tvproviders/schedulesdirect.js b/src/components/tvproviders/schedulesdirect.js index 6d8b02db1..e3218e1be 100644 --- a/src/components/tvproviders/schedulesdirect.js +++ b/src/components/tvproviders/schedulesdirect.js @@ -203,7 +203,7 @@ export default function (page, providerId, options) { } function getTunerName(providerId) { - switch (providerId = providerId.toLowerCase()) { + switch (providerId.toLowerCase()) { case 'm3u': return 'M3U Playlist'; case 'hdhomerun': diff --git a/src/components/tvproviders/xmltv.js b/src/components/tvproviders/xmltv.js index 9d5312e4d..5a4e44db6 100644 --- a/src/components/tvproviders/xmltv.js +++ b/src/components/tvproviders/xmltv.js @@ -106,7 +106,7 @@ export default function (page, providerId, options) { } function getTunerName(providerId) { - switch (providerId = providerId.toLowerCase()) { + switch (providerId.toLowerCase()) { case 'm3u': return 'M3U Playlist'; case 'hdhomerun': diff --git a/src/components/viewManager/viewManager.js b/src/components/viewManager/viewManager.js index 48c708d2a..03138797c 100644 --- a/src/components/viewManager/viewManager.js +++ b/src/components/viewManager/viewManager.js @@ -103,9 +103,9 @@ function getViewEventDetail(view, {state, url, options = {}}, isRestored) { const searchParams = new URLSearchParams(url.substring(index + 1)); const params = {}; - searchParams.forEach((value, key) => - params[key] = value - ); + searchParams.forEach((value, key) => { + params[key] = value; + }); return { detail: { diff --git a/src/config.json b/src/config.json index 5b24006a4..3d80b1efe 100644 --- a/src/config.json +++ b/src/config.json @@ -44,6 +44,7 @@ "pdfPlayer/plugin", "logoScreensaver/plugin", "sessionPlayer/plugin", - "chromecastPlayer/plugin" + "chromecastPlayer/plugin", + "syncPlay/plugin" ] } diff --git a/src/controllers/dashboard/apikeys.js b/src/controllers/dashboard/apikeys.js index 300bdeb09..9eec8503c 100644 --- a/src/controllers/dashboard/apikeys.js +++ b/src/controllers/dashboard/apikeys.js @@ -37,7 +37,8 @@ import { pageIdOn } from '../../utils/dashboard'; const date = datetime.parseISO8601Date(item.DateCreated, true); html += datetime.toLocaleDateString(date) + ' ' + datetime.getDisplayTime(date); html += ''; - return html += ''; + html += ''; + return html; }).join(''); page.querySelector('.resultBody').innerHTML = rows; loading.hide(); diff --git a/src/controllers/dashboard/dashboard.js b/src/controllers/dashboard/dashboard.js index e23fde03b..d76eaba65 100644 --- a/src/controllers/dashboard/dashboard.js +++ b/src/controllers/dashboard/dashboard.js @@ -6,7 +6,7 @@ import serverNotifications from '../../scripts/serverNotifications'; import dom from '../../scripts/dom'; import globalize from '../../scripts/globalize'; import { formatDistanceToNow } from 'date-fns'; -import { localeWithSuffix } from '../../scripts/dfnshelper'; +import { getLocaleWithSuffix } from '../../scripts/dfnshelper'; import loading from '../../components/loading/loading'; import playMethodHelper from '../../components/playback/playmethodhelper'; import cardBuilder from '../../components/cardbuilder/cardBuilder'; @@ -458,7 +458,7 @@ import confirm from '../../components/confirm/confirm'; html += ' / '; - if (nowPlayingItem && nowPlayingItem.RunTimeTicks) { + if (nowPlayingItem.RunTimeTicks) { html += datetime.getDisplayRunningTime(nowPlayingItem.RunTimeTicks); } else { html += '0:00'; @@ -477,7 +477,7 @@ import confirm from '../../components/confirm/confirm'; // how dates are returned by the server when the session is active and show something like 'Active now', instead of past/future sentences if (!nowPlayingItem) { return { - html: globalize.translate('LastSeen', formatDistanceToNow(Date.parse(session.LastActivityDate), localeWithSuffix)), + html: globalize.translate('LastSeen', formatDistanceToNow(Date.parse(session.LastActivityDate), getLocaleWithSuffix())), image: imgUrl }; } @@ -747,14 +747,7 @@ import confirm from '../../components/confirm/confirm'; console.debug('onServerRestarting not implemented', evt, apiClient); } - function onPackageInstalling(evt, apiClient) { - if (apiClient.serverId() === serverId) { - pollForInfo(view, apiClient); - reloadSystemInfo(view, apiClient); - } - } - - function onPackageInstallationCompleted(evt, apiClient) { + function onPackageInstall(_, apiClient) { if (apiClient.serverId() === serverId) { pollForInfo(view, apiClient); reloadSystemInfo(view, apiClient); @@ -786,8 +779,8 @@ import confirm from '../../components/confirm/confirm'; Events.on(serverNotifications, 'RestartRequired', onRestartRequired); Events.on(serverNotifications, 'ServerShuttingDown', onServerShuttingDown); Events.on(serverNotifications, 'ServerRestarting', onServerRestarting); - Events.on(serverNotifications, 'PackageInstalling', onPackageInstalling); - Events.on(serverNotifications, 'PackageInstallationCompleted', onPackageInstallationCompleted); + Events.on(serverNotifications, 'PackageInstalling', onPackageInstall); + Events.on(serverNotifications, 'PackageInstallationCompleted', onPackageInstall); Events.on(serverNotifications, 'Sessions', onSessionsUpdate); Events.on(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); DashboardPage.lastAppUpdateCheck = null; @@ -800,13 +793,11 @@ import confirm from '../../components/confirm/confirm'; }); } - if (ApiClient.isMinServerVersion('3.4.1.25')) { - if (!page.serverActivityLog) { - page.serverActivityLog = new ActivityLog({ - serverId: ApiClient.serverId(), - element: page.querySelector('.serverActivityItems') - }); - } + if (!page.serverActivityLog) { + page.serverActivityLog = new ActivityLog({ + serverId: ApiClient.serverId(), + element: page.querySelector('.serverActivityItems') + }); } refreshActiveRecordings(view, apiClient); @@ -826,8 +817,8 @@ import confirm from '../../components/confirm/confirm'; Events.off(serverNotifications, 'RestartRequired', onRestartRequired); Events.off(serverNotifications, 'ServerShuttingDown', onServerShuttingDown); Events.off(serverNotifications, 'ServerRestarting', onServerRestarting); - Events.off(serverNotifications, 'PackageInstalling', onPackageInstalling); - Events.off(serverNotifications, 'PackageInstallationCompleted', onPackageInstallationCompleted); + Events.off(serverNotifications, 'PackageInstalling', onPackageInstall); + Events.off(serverNotifications, 'PackageInstallationCompleted', onPackageInstall); Events.off(serverNotifications, 'Sessions', onSessionsUpdate); Events.off(serverNotifications, 'ScheduledTasksInfo', onScheduledTasksUpdate); diff --git a/src/controllers/dashboard/devices/devices.js b/src/controllers/dashboard/devices/devices.js index be854fda6..d3644da46 100644 --- a/src/controllers/dashboard/devices/devices.js +++ b/src/controllers/dashboard/devices/devices.js @@ -5,7 +5,7 @@ import dom from '../../../scripts/dom'; import globalize from '../../../scripts/globalize'; import imageHelper from '../../../scripts/imagehelper'; import { formatDistanceToNow } from 'date-fns'; -import { localeWithSuffix } from '../../../scripts/dfnshelper'; +import { getLocaleWithSuffix } from '../../../scripts/dfnshelper'; import '../../../elements/emby-button/emby-button'; import '../../../elements/emby-itemscontainer/emby-itemscontainer'; import '../../../components/cardbuilder/card.scss'; @@ -91,6 +91,8 @@ import confirm from '../../../components/confirm/confirm'; } function load(page, devices) { + const localeWithSuffix = getLocaleWithSuffix(); + let html = ''; html += devices.map(function (device) { let deviceHtml = ''; diff --git a/src/controllers/dashboard/dlna/profile.js b/src/controllers/dashboard/dlna/profile.js index 133faa2c8..3f5d6c365 100644 --- a/src/controllers/dashboard/dlna/profile.js +++ b/src/controllers/dashboard/dlna/profile.js @@ -149,7 +149,8 @@ import { getParameterByName } from '../../../utils/url.ts'; li += '

' + escapeHtml(h.Name + ' = ' + (h.Value || '')) + '

'; li += ''; li += ''; - return li += ''; + li += ''; + return li; }).join('') + ''; const elem = $('.xmlDocumentAttributeList', page).html(html).trigger('create'); $('.btnDeleteXmlAttribute', elem).on('click', function () { diff --git a/src/controllers/dashboard/plugins/available/index.js b/src/controllers/dashboard/plugins/available/index.js index 02ad0bac1..b10518bc7 100644 --- a/src/controllers/dashboard/plugins/available/index.js +++ b/src/controllers/dashboard/plugins/available/index.js @@ -129,7 +129,8 @@ function getPluginHtml(plugin, options, installedPlugins) { html += ''; html += ''; html += ''; - return html += ''; + html += ''; + return html; } function getTabs() { diff --git a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js index 7df1ff0da..c9bc36403 100644 --- a/src/controllers/dashboard/scheduledtasks/scheduledtasks.js +++ b/src/controllers/dashboard/scheduledtasks/scheduledtasks.js @@ -4,7 +4,7 @@ import { Events } from 'jellyfin-apiclient'; import globalize from '../../../scripts/globalize'; import serverNotifications from '../../../scripts/serverNotifications'; import { formatDistance, formatDistanceToNow } from 'date-fns'; -import { getLocale, localeWithSuffix } from '../../../scripts/dfnshelper'; +import { getLocale, getLocaleWithSuffix } from '../../../scripts/dfnshelper'; import '../../../components/listview/listview.scss'; import '../../../elements/emby-button/emby-button'; @@ -86,7 +86,7 @@ import '../../../elements/emby-button/emby-button'; if (task.LastExecutionResult) { const endtime = Date.parse(task.LastExecutionResult.EndTimeUtc); const starttime = Date.parse(task.LastExecutionResult.StartTimeUtc); - html += globalize.translate('LabelScheduledTaskLastRan', formatDistanceToNow(endtime, localeWithSuffix), + html += globalize.translate('LabelScheduledTaskLastRan', formatDistanceToNow(endtime, getLocaleWithSuffix()), formatDistance(starttime, endtime, { locale: getLocale() })); if (task.LastExecutionResult.Status === 'Failed') { html += " (" + globalize.translate('LabelFailed') + ')'; diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index b0ae20886..0899b89b9 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -170,7 +170,6 @@ function renderSeriesTimerEditor(page, item, apiClient, user) { page.querySelector('.seriesTimerScheduleSection').classList.add('hide'); hideAll(page, 'btnCancelSeriesTimer'); - return; } function renderTrackSelections(page, instance, item, forceReload) { @@ -206,8 +205,12 @@ function renderTrackSelections(page, instance, item, forceReload) { }); mediaSources = []; - resolutionNames.forEach(v => mediaSources.push(v)); - sourceNames.forEach(v => mediaSources.push(v)); + resolutionNames.forEach(v => { + mediaSources.push(v); + }); + sourceNames.forEach(v => { + mediaSources.push(v); + }); instance._currentPlaybackMediaSources = mediaSources; diff --git a/src/controllers/list.js b/src/controllers/list.js index 4c9e97ca4..09d398451 100644 --- a/src/controllers/list.js +++ b/src/controllers/list.js @@ -720,8 +720,6 @@ class ItemsView { if (params.type === 'Video') { return globalize.translate('Videos'); } - - return; } function play() { diff --git a/src/controllers/livetv/livetvrecordings.js b/src/controllers/livetv/livetvrecordings.js index 2ce939907..73afca81d 100644 --- a/src/controllers/livetv/livetvrecordings.js +++ b/src/controllers/livetv/livetvrecordings.js @@ -66,11 +66,9 @@ function renderRecordingFolders(context, promise) { function onMoreClick() { const type = this.getAttribute('data-type'); - const serverId = ApiClient.serverId(); - switch (type) { - case 'latest': - Dashboard.navigate('list.html?type=Recordings&serverId=' + serverId); + if (type === 'latest') { + Dashboard.navigate('list.html?type=Recordings&serverId=' + ApiClient.serverId()); } } diff --git a/src/controllers/livetvstatus.js b/src/controllers/livetvstatus.js index 1de366a02..d771464b1 100644 --- a/src/controllers/livetvstatus.js +++ b/src/controllers/livetvstatus.js @@ -49,7 +49,8 @@ function getDeviceHtml(device) { html += ''; html += ''; html += ''; - return html += ''; + html += ''; + return html; } function renderDevices(page, devices) { @@ -191,7 +192,7 @@ function deleteProvider(page, id) { } function getTunerName(providerId) { - switch (providerId = providerId.toLowerCase()) { + switch (providerId.toLowerCase()) { case 'm3u': return 'M3U'; case 'hdhomerun': @@ -206,7 +207,7 @@ function getTunerName(providerId) { } function getProviderName(providerId) { - switch (providerId = providerId.toLowerCase()) { + switch (providerId.toLowerCase()) { case 'schedulesdirect': return 'Schedules Direct'; case 'xmltv': @@ -217,7 +218,7 @@ function getProviderName(providerId) { } function getProviderConfigurationUrl(providerId) { - switch (providerId = providerId.toLowerCase()) { + switch (providerId.toLowerCase()) { case 'xmltv': return '#/livetvguideprovider.html?type=xmltv'; case 'schedulesdirect': diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index a4c090bdb..dc605ca45 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -374,10 +374,9 @@ import Dashboard from '../../utils/dashboard'; } function onInputCommand(e) { - switch (e.detail.command) { - case 'search': - e.preventDefault(); - Dashboard.navigate('search.html?collectionType=movies&parentId=' + params.topParentId); + if (e.detail.command === 'search') { + e.preventDefault(); + Dashboard.navigate('search.html?collectionType=movies&parentId=' + params.topParentId); } } diff --git a/src/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js index d04d4bde5..7682a1241 100644 --- a/src/controllers/music/musicrecommended.js +++ b/src/controllers/music/musicrecommended.js @@ -348,10 +348,9 @@ import Dashboard from '../../utils/dashboard'; } function onInputCommand(e) { - switch (e.detail.command) { - case 'search': - e.preventDefault(); - Dashboard.navigate('search.html?collectionType=music&parentId=' + params.topParentId); + if (e.detail.command === 'search') { + e.preventDefault(); + Dashboard.navigate('search.html?collectionType=music&parentId=' + params.topParentId); } } diff --git a/src/controllers/playback/video/index.html b/src/controllers/playback/video/index.html index 6c02f5162..002a81831 100644 --- a/src/controllers/playback/video/index.html +++ b/src/controllers/playback/video/index.html @@ -21,6 +21,7 @@
+
diff --git a/src/controllers/playback/video/index.js b/src/controllers/playback/video/index.js index 14f56cbcc..a0b5da28f 100644 --- a/src/controllers/playback/video/index.js +++ b/src/controllers/playback/video/index.js @@ -1,6 +1,6 @@ import escapeHtml from 'escape-html'; import { playbackManager } from '../../../components/playback/playbackmanager'; -import SyncPlay from '../../../components/syncPlay/core'; +import SyncPlay from '../../../plugins/syncPlay/core'; import browser from '../../../scripts/browser'; import dom from '../../../scripts/dom'; import inputManager from '../../../scripts/inputManager'; @@ -1204,17 +1204,6 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components resetIdle(); } - function onWindowTouchStart(e) { - clickedElement = e.target; - mouseIsDown = true; - resetIdle(); - } - - function onWindowTouchEnd() { - mouseIsDown = false; - resetIdle(); - } - function onWindowDragEnd() { // mousedown -> dragstart -> dragend !!! no mouseup :( mouseIsDown = false; @@ -1370,12 +1359,12 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components capture: true, passive: true }); - dom.addEventListener(window, 'touchstart', onWindowTouchStart, { + dom.addEventListener(window, 'touchstart', onWindowMouseDown, { capture: true, passive: true }); ['touchend', 'touchcancel'].forEach((event) => { - dom.addEventListener(window, event, onWindowTouchEnd, { + dom.addEventListener(window, event, onWindowMouseUp, { capture: true, passive: true }); @@ -1411,12 +1400,12 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components capture: true, passive: true }); - dom.removeEventListener(window, 'touchstart', onWindowTouchStart, { + dom.removeEventListener(window, 'touchstart', onWindowMouseDown, { capture: true, passive: true }); ['touchend', 'touchcancel'].forEach((event) => { - dom.removeEventListener(window, event, onWindowTouchEnd, { + dom.removeEventListener(window, event, onWindowMouseUp, { capture: true, passive: true }); @@ -1574,6 +1563,25 @@ import { setBackdropTransparency, TRANSPARENCY_LEVEL } from '../../../components return '

' + datetime.getDisplayRunningTime(ticks) + '

'; }; + nowPlayingPositionSlider.getMarkerInfo = function () { + const markers = []; + + const item = currentItem; + + // use markers based on chapters + if (item?.Chapters?.length) { + item.Chapters.forEach(currentChapter => { + markers.push({ + className: 'chapterMarker', + name: currentChapter.Name, + progress: currentChapter.StartPositionTicks / item.RunTimeTicks + }); + }); + } + + return markers; + }; + view.querySelector('.btnPreviousTrack').addEventListener('click', function () { playbackManager.previousTrack(currentPlayer); }); diff --git a/src/controllers/session/forgotPassword/index.js b/src/controllers/session/forgotPassword/index.js index 208e54d06..87a0498bf 100644 --- a/src/controllers/session/forgotPassword/index.js +++ b/src/controllers/session/forgotPassword/index.js @@ -35,7 +35,6 @@ import Dashboard from '../../../utils/dashboard'; Dashboard.navigate('forgotpasswordpin.html'); } }); - return; } } diff --git a/src/controllers/shows/tvrecommended.js b/src/controllers/shows/tvrecommended.js index 7dd916f6d..671ddd12b 100644 --- a/src/controllers/shows/tvrecommended.js +++ b/src/controllers/shows/tvrecommended.js @@ -333,10 +333,9 @@ import autoFocuser from '../../components/autoFocuser'; } function onInputCommand(e) { - switch (e.detail.command) { - case 'search': - e.preventDefault(); - Dashboard.navigate('search.html?collectionType=tv&parentId=' + params.topParentId); + if (e.detail.command === 'search') { + e.preventDefault(); + Dashboard.navigate('search.html?collectionType=tv&parentId=' + params.topParentId); } } diff --git a/src/elements/emby-input/emby-input.js b/src/elements/emby-input/emby-input.js index f9ea23f2e..f62bcbf34 100644 --- a/src/elements/emby-input/emby-input.js +++ b/src/elements/emby-input/emby-input.js @@ -85,13 +85,9 @@ import 'webcomponents.js/webcomponents-lite'; passive: true }); - if (browser.orsay) { - if (this === document.activeElement) { - //Make sure the IME pops up if this is the first/default element on the page - if (document.attachIME) { - document.attachIME(this); - } - } + //Make sure the IME pops up if this is the first/default element on the page + if (browser.orsay && this === document.activeElement && document.attachIME) { + document.attachIME(this); } }; diff --git a/src/elements/emby-itemscontainer/emby-itemscontainer.js b/src/elements/emby-itemscontainer/emby-itemscontainer.js index 37edb8224..b6d0f147b 100644 --- a/src/elements/emby-itemscontainer/emby-itemscontainer.js +++ b/src/elements/emby-itemscontainer/emby-itemscontainer.js @@ -21,10 +21,8 @@ import Sortable from 'sortablejs'; const itemsContainer = this; const multiSelect = itemsContainer.multiSelect; - if (multiSelect) { - if (multiSelect.onContainerClick.call(itemsContainer, e) === false) { - return; - } + if (multiSelect?.onContainerClick.call(itemsContainer, e) === false) { + return; } itemShortcuts.onClick.call(itemsContainer, e); @@ -155,9 +153,9 @@ import Sortable from 'sortablejs'; const eventsToMonitor = getEventsToMonitor(itemsContainer); // TODO: Check user data change reason? - if (eventsToMonitor.indexOf('markfavorite') !== -1) { - itemsContainer.notifyRefreshNeeded(); - } else if (eventsToMonitor.indexOf('markplayed') !== -1) { + if (eventsToMonitor.indexOf('markfavorite') !== -1 + || eventsToMonitor.indexOf('markplayed') !== -1 + ) { itemsContainer.notifyRefreshNeeded(); } } @@ -192,7 +190,6 @@ import Sortable from 'sortablejs'; const itemsContainer = this; if (getEventsToMonitor(itemsContainer).indexOf('seriestimers') !== -1) { itemsContainer.notifyRefreshNeeded(); - return; } } @@ -259,11 +256,9 @@ import Sortable from 'sortablejs'; itemsContainer.notifyRefreshNeeded(true); return; } - } else if (state.NowPlayingItem && state.NowPlayingItem.MediaType === 'Audio') { - if (eventsToMonitor.indexOf('audioplayback') !== -1) { - itemsContainer.notifyRefreshNeeded(true); - return; - } + } else if (state.NowPlayingItem?.MediaType === 'Audio' && eventsToMonitor.indexOf('audioplayback') !== -1) { + itemsContainer.notifyRefreshNeeded(true); + return; } } @@ -298,10 +293,8 @@ import Sortable from 'sortablejs'; } } - if (layoutManager.desktop || layoutManager.mobile) { - if (this.getAttribute('data-multiselect') !== 'false') { - this.enableMultiSelect(true); - } + if (layoutManager.desktop || layoutManager.mobile && this.getAttribute('data-multiselect') !== 'false') { + this.enableMultiSelect(true); } if (layoutManager.tv) { diff --git a/src/elements/emby-ratingbutton/emby-ratingbutton.js b/src/elements/emby-ratingbutton/emby-ratingbutton.js index dea35f36f..91233cef4 100644 --- a/src/elements/emby-ratingbutton/emby-ratingbutton.js +++ b/src/elements/emby-ratingbutton/emby-ratingbutton.js @@ -63,18 +63,6 @@ import ServerConnections from '../../components/ServerConnections'; } button.classList.add('ratingbutton-withrating'); - } else if (likes) { - if (icon) { - icon.classList.add('favorite'); - icon.classList.remove('ratingbutton-icon-withrating'); - } - button.classList.remove('ratingbutton-withrating'); - } else if (likes === false) { - if (icon) { - icon.classList.add('favorite'); - icon.classList.remove('ratingbutton-icon-withrating'); - } - button.classList.remove('ratingbutton-withrating'); } else { if (icon) { icon.classList.add('favorite'); diff --git a/src/elements/emby-select/emby-select.js b/src/elements/emby-select/emby-select.js index e06968782..b57604282 100644 --- a/src/elements/emby-select/emby-select.js +++ b/src/elements/emby-select/emby-select.js @@ -23,11 +23,7 @@ import 'webcomponents.js/webcomponents-lite'; return true; } - if (layoutManager.tv) { - return false; - } - - return true; + return !layoutManager.tv; } function triggerChange(select) { diff --git a/src/elements/emby-slider/emby-slider.js b/src/elements/emby-slider/emby-slider.js index f8c492cef..d49b1b8ff 100644 --- a/src/elements/emby-slider/emby-slider.js +++ b/src/elements/emby-slider/emby-slider.js @@ -105,6 +105,13 @@ import globalize from '../../scripts/globalize'; fraction *= 100; backgroundLower.style.width = fraction + '%'; } + + if (range.markerContainerElement) { + if (!range.triedAddingMarkers) { + addMarkers(range); + } + updateMarkers(range, value); + } }); } @@ -136,6 +143,70 @@ import globalize from '../../scripts/globalize'; }); } + function setMarker(range, valueMarker, marker, valueProgress) { + requestAnimationFrame(function () { + const bubbleTrackRect = range.sliderBubbleTrack.getBoundingClientRect(); + const markerRect = marker.getBoundingClientRect(); + + if (!bubbleTrackRect.width || !markerRect.width) { + // width is not set, most probably because the OSD is currently hidden + return; + } + + let markerPos = (bubbleTrackRect.width * valueMarker / 100) - markerRect.width / 2; + markerPos = Math.min(Math.max(markerPos, - markerRect.width / 2), bubbleTrackRect.width - markerRect.width / 2); + + marker.style.left = markerPos + 'px'; + + if (valueProgress >= valueMarker) { + marker.classList.remove('unwatched'); + marker.classList.add('watched'); + } else { + marker.classList.add('unwatched'); + marker.classList.remove('watched'); + } + }); + } + + function updateMarkers(range, currentValue) { + if (range.markerInfo && range.markerInfo.length && range.markerElements && range.markerElements.length) { + for (let i = 0, length = range.markerElements.length; i < length; i++) { + if (range.markerInfo.length > i) { + setMarker(range, mapFractionToValue(range, range.markerInfo[i].progress), range.markerElements[i], currentValue); + } + } + } + } + + function addMarkers(range) { + range.markerInfo = []; + if (range.getMarkerInfo) { + range.markerInfo = range.getMarkerInfo(); + } + + function getMarkerHtml(markerInfo) { + let markerTypeSpecificClasses = ''; + + if (markerInfo.className === 'chapterMarker') { + markerTypeSpecificClasses = markerInfo.className; + + if (typeof markerInfo.name === 'string' && markerInfo.name.length) { + // limit the class length in case the name contains half a novel + markerTypeSpecificClasses = `${markerInfo.className} marker-${markerInfo.name.substring(0, 100).toLowerCase().replace(' ', '-')}`; + } + } + + return ``; + } + + range.markerInfo.forEach(info => { + range.markerContainerElement.insertAdjacentHTML('beforeend', getMarkerHtml(info)); + }); + + range.markerElements = range.markerContainerElement.querySelectorAll('.sliderMarker'); + range.triedAddingMarkers = true; + } + EmbySliderPrototype.attachedCallback = function () { if (this.getAttribute('data-embyslider') === 'true') { return; @@ -193,7 +264,9 @@ import globalize from '../../scripts/globalize'; this.backgroundUpper = containerElement.querySelector('.mdl-slider-background-upper'); const sliderBubble = containerElement.querySelector('.sliderBubble'); - let hasHideClass = sliderBubble.classList.contains('hide'); + let hasHideBubbleClass = sliderBubble.classList.contains('hide'); + + this.markerContainerElement = containerElement.querySelector('.sliderMarkerContainer'); dom.addEventListener(this, 'input', function () { this.dragging = true; @@ -205,9 +278,9 @@ import globalize from '../../scripts/globalize'; const bubbleValue = mapValueToFraction(this, this.value) * 100; updateBubble(this, bubbleValue, sliderBubble); - if (hasHideClass) { + if (hasHideBubbleClass) { sliderBubble.classList.remove('hide'); - hasHideClass = false; + hasHideBubbleClass = false; } }, { passive: true @@ -221,7 +294,7 @@ import globalize from '../../scripts/globalize'; } sliderBubble.classList.add('hide'); - hasHideClass = true; + hasHideBubbleClass = true; }, { passive: true }); @@ -233,9 +306,9 @@ import globalize from '../../scripts/globalize'; updateBubble(this, bubbleValue, sliderBubble); - if (hasHideClass) { + if (hasHideBubbleClass) { sliderBubble.classList.remove('hide'); - hasHideClass = false; + hasHideBubbleClass = false; } } }, { @@ -245,7 +318,7 @@ import globalize from '../../scripts/globalize'; /* eslint-disable-next-line compat/compat */ dom.addEventListener(this, (window.PointerEvent ? 'pointerleave' : 'mouseleave'), function () { sliderBubble.classList.add('hide'); - hasHideClass = true; + hasHideBubbleClass = true; }, { passive: true }); @@ -452,10 +525,8 @@ import globalize from '../../scripts/globalize'; } for (const range of ranges) { - if (position != null) { - if (position >= range.end) { - continue; - } + if (position != null && position >= range.end) { + continue; } setRange(elem, range.start, range.end); diff --git a/src/elements/emby-slider/emby-slider.scss b/src/elements/emby-slider/emby-slider.scss index 9d663f10a..f40e6329d 100644 --- a/src/elements/emby-slider/emby-slider.scss +++ b/src/elements/emby-slider/emby-slider.scss @@ -266,3 +266,25 @@ display: block; margin-bottom: 0.25em; } + +.sliderMarkerContainer { + position: absolute; + left: 0; + right: 0; + margin: 0 0.54em; /* half of slider thumb size */ +} + +.sliderMarker { + position: absolute; + width: 2px; + height: 0.5em; + transform: translate3d(0, 25%, 0); +} + +.sliderMarker.unwatched { + background-color: rgba(255, 255, 255, 0.3); +} + +.sliderMarker.watched { + background-color: #00a4dc; +} diff --git a/src/elements/emby-tabs/emby-tabs.js b/src/elements/emby-tabs/emby-tabs.js index ce1fcee05..723cb7e8f 100644 --- a/src/elements/emby-tabs/emby-tabs.js +++ b/src/elements/emby-tabs/emby-tabs.js @@ -272,10 +272,8 @@ import '../../assets/css/scrollstyles.scss'; let sibling = elem[method]; while (sibling) { - if (sibling.classList.contains(buttonClass)) { - if (!sibling.classList.contains('hide')) { - return sibling; - } + if (sibling.classList.contains(buttonClass) && !sibling.classList.contains('hide')) { + return sibling; } sibling = sibling[method]; diff --git a/src/index.jsx b/src/index.jsx index 7ca4b92a2..c15297288 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -35,18 +35,13 @@ import './legacy/domParserTextHtml'; import './legacy/focusPreventScroll'; import './legacy/htmlMediaElement'; import './legacy/vendorStyles'; -import SyncPlay from './components/syncPlay/core'; -import { playbackManager } from './components/playback/playbackmanager'; -import SyncPlayNoActivePlayer from './components/syncPlay/ui/players/NoActivePlayer'; -import SyncPlayHtmlVideoPlayer from './components/syncPlay/ui/players/HtmlVideoPlayer'; -import SyncPlayHtmlAudioPlayer from './components/syncPlay/ui/players/HtmlAudioPlayer'; import { currentSettings } from './scripts/settings/userSettings'; import taskButton from './scripts/taskbutton'; import { HistoryRouter } from './components/HistoryRouter.tsx'; import AppRoutes from './routes/index.tsx'; function loadCoreDictionary() { - const languages = ['af', 'ar', 'be-by', 'bg-bg', 'bn_bd', 'ca', 'cs', 'cy', 'da', 'de', 'el', 'en-gb', 'en-us', 'eo', 'es', 'es-419', 'es-ar', 'es_do', 'es-mx', 'et', 'fa', 'fi', 'fil', 'fr', 'fr-ca', 'gl', 'gsw', 'he', 'hi-in', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-lt', 'lv', 'mr', 'ms', 'nb', 'nl', 'nn', 'pl', 'pr', 'pt', 'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl-si', 'sq', 'sv', 'ta', 'th', 'tr', 'uk', 'ur_pk', 'vi', 'zh-cn', 'zh-hk', 'zh-tw']; + const languages = ['af', 'ar', 'be-by', 'bg-bg', 'bn_bd', 'ca', 'cs', 'cy', 'da', 'de', 'el', 'en-gb', 'en-us', 'eo', 'es', 'es-419', 'es-ar', 'es_do', 'es-mx', 'et', 'eu', 'fa', 'fi', 'fil', 'fr', 'fr-ca', 'gl', 'gsw', 'he', 'hi-in', 'hr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt-lt', 'lv', 'mr', 'ms', 'nb', 'nl', 'nn', 'pl', 'pr', 'pt', 'pt-br', 'pt-pt', 'ro', 'ru', 'sk', 'sl-si', 'sq', 'sv', 'ta', 'th', 'tr', 'uk', 'ur_pk', 'vi', 'zh-cn', 'zh-hk', 'zh-tw']; const translations = languages.map(function (language) { return { lang: language, @@ -84,10 +79,10 @@ function init() { } function onGlobalizeInit() { - if (window.appMode === 'android') { - if (window.location.href.toString().toLowerCase().indexOf('start=backgroundsync') !== -1) { - return onAppReady(); - } + if (window.appMode === 'android' + && window.location.href.toString().toLowerCase().indexOf('start=backgroundsync') !== -1 + ) { + return onAppReady(); } document.title = globalize.translateHtml(document.title, 'core'); @@ -102,10 +97,7 @@ function onGlobalizeInit() { import('./assets/css/librarybrowser.scss'); - loadPlugins().then(function () { - initSyncPlay(); - onAppReady(); - }); + loadPlugins().then(onAppReady); } function loadPlugins() { @@ -137,27 +129,6 @@ function loadPlugins() { }); } -function initSyncPlay() { - // Register player wrappers. - SyncPlay.PlayerFactory.setDefaultWrapper(SyncPlayNoActivePlayer); - SyncPlay.PlayerFactory.registerWrapper(SyncPlayHtmlVideoPlayer); - SyncPlay.PlayerFactory.registerWrapper(SyncPlayHtmlAudioPlayer); - - // Listen for player changes. - Events.on(playbackManager, 'playerchange', (event, newPlayer, newTarget, oldPlayer) => { - SyncPlay.Manager.onPlayerChange(newPlayer, newTarget, oldPlayer); - }); - - // Start SyncPlay. - const apiClient = ServerConnections.currentApiClient(); - if (apiClient) SyncPlay.Manager.init(apiClient); - - // FIXME: Multiple apiClients? - Events.on(ServerConnections, 'apiclientcreated', (e, newApiClient) => SyncPlay.Manager.init(newApiClient)); - Events.on(ServerConnections, 'localusersignedin', () => SyncPlay.Manager.updateApiClient(ServerConnections.currentApiClient())); - Events.on(ServerConnections, 'localusersignedout', () => SyncPlay.Manager.updateApiClient(ServerConnections.currentApiClient())); -} - async function onAppReady() { console.debug('begin onAppReady'); diff --git a/src/libraries/navdrawer/navdrawer.js b/src/libraries/navdrawer/navdrawer.js index 64306c2db..3f409568f 100644 --- a/src/libraries/navdrawer/navdrawer.js +++ b/src/libraries/navdrawer/navdrawer.js @@ -303,34 +303,32 @@ class NavDrawer { setEdgeSwipeEnabled(enabled) { const options = this.options; - if (!options.disableEdgeSwipe) { - if (browser.touch) { - if (enabled) { - if (!this._edgeSwipeEnabled) { - this._edgeSwipeEnabled = true; - dom.addEventListener(this.edgeContainer, 'touchstart', this.onEdgeTouchStart, { - passive: true - }); - dom.addEventListener(this.edgeContainer, 'touchend', this.onEdgeTouchEnd, { - passive: true - }); - dom.addEventListener(this.edgeContainer, 'touchcancel', this.onEdgeTouchEnd, { - passive: true - }); - } - } else { - if (this._edgeSwipeEnabled) { - this._edgeSwipeEnabled = false; - dom.removeEventListener(this.edgeContainer, 'touchstart', this.onEdgeTouchStart, { - passive: true - }); - dom.removeEventListener(this.edgeContainer, 'touchend', this.onEdgeTouchEnd, { - passive: true - }); - dom.removeEventListener(this.edgeContainer, 'touchcancel', this.onEdgeTouchEnd, { - passive: true - }); - } + if (!options.disableEdgeSwipe && browser.touch) { + if (enabled) { + if (!this._edgeSwipeEnabled) { + this._edgeSwipeEnabled = true; + dom.addEventListener(this.edgeContainer, 'touchstart', this.onEdgeTouchStart, { + passive: true + }); + dom.addEventListener(this.edgeContainer, 'touchend', this.onEdgeTouchEnd, { + passive: true + }); + dom.addEventListener(this.edgeContainer, 'touchcancel', this.onEdgeTouchEnd, { + passive: true + }); + } + } else { + if (this._edgeSwipeEnabled) { + this._edgeSwipeEnabled = false; + dom.removeEventListener(this.edgeContainer, 'touchstart', this.onEdgeTouchStart, { + passive: true + }); + dom.removeEventListener(this.edgeContainer, 'touchend', this.onEdgeTouchEnd, { + passive: true + }); + dom.removeEventListener(this.edgeContainer, 'touchcancel', this.onEdgeTouchEnd, { + passive: true + }); } } } diff --git a/src/libraries/scroller.js b/src/libraries/scroller.js index 2acc4bdc0..385c4e091 100644 --- a/src/libraries/scroller.js +++ b/src/libraries/scroller.js @@ -291,10 +291,8 @@ const scrollerFactory = function (frame, options) { const now = new Date().getTime(); - if (o.autoImmediate) { - if (!immediate && (now - (lastAnimate || 0)) <= 50) { - immediate = true; - } + if (o.autoImmediate && !immediate && (now - (lastAnimate || 0)) <= 50) { + immediate = true; } if (!immediate && o.skipSlideToWhenVisible && fullItemPos && fullItemPos.isVisible) { @@ -482,7 +480,8 @@ const scrollerFactory = function (frame, options) { */ function dragHandler(event) { dragging.released = event.type === 'mouseup' || event.type === 'touchend'; - const pointer = dragging.touch ? event[dragging.released ? 'changedTouches' : 'touches'][0] : event; + const eventName = dragging.released ? 'changedTouches' : 'touches'; + const pointer = dragging.touch ? event[eventName][0] : event; dragging.pathX = pointer.pageX - dragging.initX; dragging.pathY = pointer.pageY - dragging.initY; dragging.path = Math.sqrt(Math.pow(dragging.pathX, 2) + Math.pow(dragging.pathY, 2)); @@ -808,15 +807,13 @@ const scrollerFactory = function (frame, options) { passive: true }); } - } else if (o.horizontal) { + } else if (o.horizontal && o.mouseWheel) { // Don't bind to mouse events with vertical scroll since the mouse wheel can handle this natively - if (o.mouseWheel) { - // Scrolling navigation - dom.addEventListener(scrollSource, wheelEvent, scrollHandler, { - passive: true - }); - } + // Scrolling navigation + dom.addEventListener(scrollSource, wheelEvent, scrollHandler, { + passive: true + }); } dom.addEventListener(frame, 'click', onFrameClick, { diff --git a/src/plugins/bookPlayer/plugin.js b/src/plugins/bookPlayer/plugin.js index a4f49b8b6..d0fcffa48 100644 --- a/src/plugins/bookPlayer/plugin.js +++ b/src/plugins/bookPlayer/plugin.js @@ -339,11 +339,7 @@ export class BookPlayer { } canPlayItem(item) { - if (item.Path && item.Path.endsWith('epub')) { - return true; - } - - return false; + return item.Path && item.Path.endsWith('epub'); } } diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index 947426ec2..aeced81b8 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -452,11 +452,9 @@ function normalizeImages(state) { if (state && state.NowPlayingItem) { const item = state.NowPlayingItem; - if (!item.ImageTags || !item.ImageTags.Primary) { - if (item.PrimaryImageTag) { - item.ImageTags = item.ImageTags || {}; - item.ImageTags.Primary = item.PrimaryImageTag; - } + if ((!item.ImageTags || !item.ImageTags.Primary) && item.PrimaryImageTag) { + item.ImageTags = item.ImageTags || {}; + item.ImageTags.Primary = item.PrimaryImageTag; } if (item.BackdropImageTag && item.BackdropItemId === item.Id) { item.BackdropImageTags = [item.BackdropImageTag]; diff --git a/src/plugins/comicsPlayer/plugin.js b/src/plugins/comicsPlayer/plugin.js index 57772d8de..d209069ad 100644 --- a/src/plugins/comicsPlayer/plugin.js +++ b/src/plugins/comicsPlayer/plugin.js @@ -172,10 +172,8 @@ export class ComicsPlayer { onWindowKeyUp(e) { const key = keyboardnavigation.getKeyName(e); - switch (key) { - case 'Escape': - this.stop(); - break; + if (key === 'Escape') { + this.stop(); } } @@ -358,11 +356,7 @@ export class ComicsPlayer { } canPlayItem(item) { - if (item.Path && (item.Path.endsWith('cbz') || item.Path.endsWith('cbr'))) { - return true; - } - - return false; + return item.Path && (item.Path.endsWith('cbz') || item.Path.endsWith('cbr')); } } diff --git a/src/plugins/htmlAudioPlayer/plugin.js b/src/plugins/htmlAudioPlayer/plugin.js index 440bb4c3d..1873970e1 100644 --- a/src/plugins/htmlAudioPlayer/plugin.js +++ b/src/plugins/htmlAudioPlayer/plugin.js @@ -41,13 +41,9 @@ function cancelFadeTimeout() { } function supportsFade() { - if (browser.tv) { - // Not working on tizen. - // We could possibly enable on other tv's, but all smart tv browsers tend to be pretty primitive - return false; - } - - return true; + // Not working on tizen. + // We could possibly enable on other tv's, but all smart tv browsers tend to be pretty primitive + return !browser.tv; } function requireHlsPlayer(callback) { @@ -417,10 +413,7 @@ class HtmlAudioPlayer { // This is a retry after error resume() { - const mediaElement = this._mediaElement; - if (mediaElement) { - mediaElement.play(); - } + this.unpause(); } unpause() { diff --git a/src/plugins/htmlVideoPlayer/plugin.js b/src/plugins/htmlVideoPlayer/plugin.js index f1c83961e..99859d6fd 100644 --- a/src/plugins/htmlVideoPlayer/plugin.js +++ b/src/plugins/htmlVideoPlayer/plugin.js @@ -67,16 +67,12 @@ function tryRemoveElement(elem) { } function enableNativeTrackSupport(currentSrc, track) { - if (track) { - if (track.DeliveryMethod === 'Embed') { - return true; - } + if (track?.DeliveryMethod === 'Embed') { + return true; } - if (browser.firefox) { - if ((currentSrc || '').toLowerCase().includes('.m3u8')) { - return false; - } + if (browser.firefox && (currentSrc || '').toLowerCase().includes('.m3u8')) { + return false; } if (browser.ps4) { @@ -92,11 +88,9 @@ function tryRemoveElement(elem) { return false; } - if (browser.iOS) { + if (browser.iOS && (browser.iosVersion || 10) < 10) { // works in the browser but not the native app - if ((browser.iosVersion || 10) < 10) { - return false; - } + return false; } if (track) { @@ -279,10 +273,6 @@ function tryRemoveElement(elem) { * @type {any | undefined} */ #lastProfile; - /** - * @type {MutationObserver | IntersectionObserver | undefined} (Unclear observer typing) - */ - #resizeObserver; constructor() { if (browser.edgeUwp) { @@ -969,11 +959,6 @@ function tryRemoveElement(elem) { * @private */ destroyCustomTrack(videoElement) { - if (this.#resizeObserver) { - this.#resizeObserver.disconnect(); - this.#resizeObserver = null; - } - if (this.#videoSubtitlesElem) { const subtitlesContainer = this.#videoSubtitlesElem.parentNode; if (subtitlesContainer) { @@ -1497,14 +1482,14 @@ function tryRemoveElement(elem) { if ( // Check non-standard Safari PiP support typeof video.webkitSupportsPresentationMode === 'function' && video.webkitSupportsPresentationMode('picture-in-picture') && typeof video.webkitSetPresentationMode === 'function' + // Check non-standard Windows PiP support + || (window.Windows + && Windows.UI.ViewManagement.ApplicationView.getForCurrentView() + .isViewModeSupported(Windows.UI.ViewManagement.ApplicationViewMode.compactOverlay)) // Check standard PiP support || document.pictureInPictureEnabled ) { list.push('PictureInPicture'); - } else if (window.Windows) { - if (Windows.UI.ViewManagement.ApplicationView.getForCurrentView().isViewModeSupported(Windows.UI.ViewManagement.ApplicationViewMode.compactOverlay)) { - list.push('PictureInPicture'); - } } if (browser.safari || browser.iOS || browser.iPad) { @@ -1565,13 +1550,7 @@ function tryRemoveElement(elem) { } const video = this.#mediaElement; - if (video) { - if (video.audioTracks) { - return true; - } - } - - return false; + return !!video?.audioTracks; } static onPictureInPictureError(err) { @@ -1703,10 +1682,7 @@ function tryRemoveElement(elem) { // This is a retry after error resume() { - const mediaElement = this.#mediaElement; - if (mediaElement) { - mediaElement.play(); - } + this.unpause(); } unpause() { diff --git a/src/plugins/pdfPlayer/plugin.js b/src/plugins/pdfPlayer/plugin.js index 5bdd5e0bd..e1f59735d 100644 --- a/src/plugins/pdfPlayer/plugin.js +++ b/src/plugins/pdfPlayer/plugin.js @@ -304,11 +304,7 @@ export class PdfPlayer { } canPlayItem(item) { - if (item.Path && item.Path.endsWith('pdf')) { - return true; - } - - return false; + return item.Path && item.Path.endsWith('pdf'); } } diff --git a/src/plugins/sessionPlayer/plugin.js b/src/plugins/sessionPlayer/plugin.js index 6997dc173..50c6dae8a 100644 --- a/src/plugins/sessionPlayer/plugin.js +++ b/src/plugins/sessionPlayer/plugin.js @@ -159,11 +159,9 @@ function normalizeImages(state, apiClient) { if (state && state.NowPlayingItem) { const item = state.NowPlayingItem; - if (!item.ImageTags || !item.ImageTags.Primary) { - if (item.PrimaryImageTag) { - item.ImageTags = item.ImageTags || {}; - item.ImageTags.Primary = item.PrimaryImageTag; - } + if (!item.ImageTags || !item.ImageTags.Primary && item.PrimaryImageTag) { + item.ImageTags = item.ImageTags || {}; + item.ImageTags.Primary = item.PrimaryImageTag; } if (item.BackdropImageTag && item.BackdropItemId === item.Id) { item.BackdropImageTags = [item.BackdropImageTag]; diff --git a/src/components/syncPlay/core/Controller.js b/src/plugins/syncPlay/core/Controller.js similarity index 100% rename from src/components/syncPlay/core/Controller.js rename to src/plugins/syncPlay/core/Controller.js diff --git a/src/components/syncPlay/core/Helper.js b/src/plugins/syncPlay/core/Helper.js similarity index 100% rename from src/components/syncPlay/core/Helper.js rename to src/plugins/syncPlay/core/Helper.js diff --git a/src/components/syncPlay/core/Manager.js b/src/plugins/syncPlay/core/Manager.js similarity index 99% rename from src/components/syncPlay/core/Manager.js rename to src/plugins/syncPlay/core/Manager.js index 16640c1df..c41fe14fb 100644 --- a/src/components/syncPlay/core/Manager.js +++ b/src/plugins/syncPlay/core/Manager.js @@ -9,7 +9,7 @@ import TimeSyncCore from './timeSync/TimeSyncCore'; import PlaybackCore from './PlaybackCore'; import QueueCore from './QueueCore'; import Controller from './Controller'; -import toast from '../../toast/toast'; +import toast from '../../../components/toast/toast'; import globalize from '../../../scripts/globalize'; /** diff --git a/src/components/syncPlay/core/PlaybackCore.js b/src/plugins/syncPlay/core/PlaybackCore.js similarity index 100% rename from src/components/syncPlay/core/PlaybackCore.js rename to src/plugins/syncPlay/core/PlaybackCore.js diff --git a/src/components/syncPlay/core/QueueCore.js b/src/plugins/syncPlay/core/QueueCore.js similarity index 99% rename from src/components/syncPlay/core/QueueCore.js rename to src/plugins/syncPlay/core/QueueCore.js index 81feac0ac..f1a435106 100644 --- a/src/components/syncPlay/core/QueueCore.js +++ b/src/plugins/syncPlay/core/QueueCore.js @@ -4,7 +4,7 @@ */ import globalize from '../../../scripts/globalize'; -import toast from '../../toast/toast'; +import toast from '../../../components/toast/toast'; import * as Helper from './Helper'; /** @@ -193,7 +193,6 @@ class QueueCore { } this.manager.haltGroupPlayback(apiClient); - return; }); } diff --git a/src/components/syncPlay/core/Settings.js b/src/plugins/syncPlay/core/Settings.js similarity index 100% rename from src/components/syncPlay/core/Settings.js rename to src/plugins/syncPlay/core/Settings.js diff --git a/src/components/syncPlay/core/index.js b/src/plugins/syncPlay/core/index.js similarity index 100% rename from src/components/syncPlay/core/index.js rename to src/plugins/syncPlay/core/index.js diff --git a/src/components/syncPlay/core/players/GenericPlayer.js b/src/plugins/syncPlay/core/players/GenericPlayer.js similarity index 100% rename from src/components/syncPlay/core/players/GenericPlayer.js rename to src/plugins/syncPlay/core/players/GenericPlayer.js diff --git a/src/components/syncPlay/core/players/PlayerFactory.js b/src/plugins/syncPlay/core/players/PlayerFactory.js similarity index 94% rename from src/components/syncPlay/core/players/PlayerFactory.js rename to src/plugins/syncPlay/core/players/PlayerFactory.js index 1de9aa512..ab4170c9e 100644 --- a/src/components/syncPlay/core/players/PlayerFactory.js +++ b/src/plugins/syncPlay/core/players/PlayerFactory.js @@ -16,7 +16,7 @@ class PlayerFactory { /** * Registers a wrapper to the list of players that can be managed. - * @param {GenericPlayer} wrapperClass The wrapper to register. + * @param {typeof GenericPlayer} wrapperClass The wrapper to register. */ registerWrapper(wrapperClass) { console.debug('SyncPlay WrapperFactory registerWrapper:', wrapperClass.type); @@ -25,7 +25,7 @@ class PlayerFactory { /** * Sets the default player wrapper. - * @param {GenericPlayer} wrapperClass The wrapper. + * @param {typeof GenericPlayer} wrapperClass The wrapper. */ setDefaultWrapper(wrapperClass) { console.debug('SyncPlay WrapperFactory setDefaultWrapper:', wrapperClass.type); diff --git a/src/components/syncPlay/core/timeSync/TimeSync.js b/src/plugins/syncPlay/core/timeSync/TimeSync.js similarity index 100% rename from src/components/syncPlay/core/timeSync/TimeSync.js rename to src/plugins/syncPlay/core/timeSync/TimeSync.js diff --git a/src/components/syncPlay/core/timeSync/TimeSyncCore.js b/src/plugins/syncPlay/core/timeSync/TimeSyncCore.js similarity index 100% rename from src/components/syncPlay/core/timeSync/TimeSyncCore.js rename to src/plugins/syncPlay/core/timeSync/TimeSyncCore.js diff --git a/src/components/syncPlay/core/timeSync/TimeSyncServer.js b/src/plugins/syncPlay/core/timeSync/TimeSyncServer.js similarity index 93% rename from src/components/syncPlay/core/timeSync/TimeSyncServer.js rename to src/plugins/syncPlay/core/timeSync/TimeSyncServer.js index 734763c07..4cb9fdd30 100644 --- a/src/components/syncPlay/core/timeSync/TimeSyncServer.js +++ b/src/plugins/syncPlay/core/timeSync/TimeSyncServer.js @@ -9,10 +9,6 @@ import TimeSync from './TimeSync'; * Class that manages time syncing with server. */ class TimeSyncServer extends TimeSync { - constructor(syncPlayManager) { - super(syncPlayManager); - } - /** * Makes a ping request to the server. */ diff --git a/src/plugins/syncPlay/plugin.ts b/src/plugins/syncPlay/plugin.ts new file mode 100644 index 000000000..e1b7c29cd --- /dev/null +++ b/src/plugins/syncPlay/plugin.ts @@ -0,0 +1,49 @@ +import { Events } from 'jellyfin-apiclient'; + +import { playbackManager } from '../../components/playback/playbackmanager'; +import ServerConnections from '../../components/ServerConnections'; +import SyncPlay from './core'; +import SyncPlayNoActivePlayer from './ui/players/NoActivePlayer'; +import SyncPlayHtmlVideoPlayer from './ui/players/HtmlVideoPlayer'; +import SyncPlayHtmlAudioPlayer from './ui/players/HtmlAudioPlayer'; + +class SyncPlayPlugin { + name: string; + id: string; + type: string; + priority: number; + + constructor() { + this.name = 'SyncPlay Plugin'; + 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.priority = 1; + + this.init(); + } + + init() { + // Register player wrappers. + SyncPlay.PlayerFactory.setDefaultWrapper(SyncPlayNoActivePlayer); + SyncPlay.PlayerFactory.registerWrapper(SyncPlayHtmlVideoPlayer); + SyncPlay.PlayerFactory.registerWrapper(SyncPlayHtmlAudioPlayer); + + // Listen for player changes. + Events.on(playbackManager, 'playerchange', (_, newPlayer) => { + SyncPlay.Manager.onPlayerChange(newPlayer); + }); + + // Start SyncPlay. + const apiClient = ServerConnections.currentApiClient(); + if (apiClient) SyncPlay.Manager.init(apiClient); + + // FIXME: Multiple apiClients? + Events.on(ServerConnections, 'apiclientcreated', (_, newApiClient) => SyncPlay.Manager.init(newApiClient)); + Events.on(ServerConnections, 'localusersignedin', () => SyncPlay.Manager.updateApiClient(ServerConnections.currentApiClient())); + Events.on(ServerConnections, 'localusersignedout', () => SyncPlay.Manager.updateApiClient(ServerConnections.currentApiClient())); + } +} + +export default SyncPlayPlugin; diff --git a/src/components/syncPlay/ui/groupSelectionMenu.js b/src/plugins/syncPlay/ui/groupSelectionMenu.js similarity index 96% rename from src/components/syncPlay/ui/groupSelectionMenu.js rename to src/plugins/syncPlay/ui/groupSelectionMenu.js index 807899773..106ee8e42 100644 --- a/src/components/syncPlay/ui/groupSelectionMenu.js +++ b/src/plugins/syncPlay/ui/groupSelectionMenu.js @@ -1,12 +1,12 @@ import { Events } from 'jellyfin-apiclient'; import SyncPlay from '../core'; import SyncPlaySettingsEditor from './settings/SettingsEditor'; -import loading from '../../loading/loading'; -import toast from '../../toast/toast'; -import actionsheet from '../../actionSheet/actionSheet'; +import loading from '../../../components/loading/loading'; +import toast from '../../../components/toast/toast'; +import actionsheet from '../../../components/actionSheet/actionSheet'; import globalize from '../../../scripts/globalize'; import playbackPermissionManager from './playbackPermissionManager'; -import ServerConnections from '../../ServerConnections'; +import ServerConnections from '../../../components/ServerConnections'; import './groupSelectionMenu.scss'; /** diff --git a/src/components/syncPlay/ui/groupSelectionMenu.scss b/src/plugins/syncPlay/ui/groupSelectionMenu.scss similarity index 100% rename from src/components/syncPlay/ui/groupSelectionMenu.scss rename to src/plugins/syncPlay/ui/groupSelectionMenu.scss diff --git a/src/components/syncPlay/ui/playbackPermissionManager.js b/src/plugins/syncPlay/ui/playbackPermissionManager.js similarity index 96% rename from src/components/syncPlay/ui/playbackPermissionManager.js rename to src/plugins/syncPlay/ui/playbackPermissionManager.js index 2a70d7370..816b57bca 100644 --- a/src/components/syncPlay/ui/playbackPermissionManager.js +++ b/src/plugins/syncPlay/ui/playbackPermissionManager.js @@ -1,4 +1,4 @@ -import { appHost } from '../../apphost'; +import { appHost } from '../../../components/apphost'; /** * Creates an audio element that plays a silent sound. diff --git a/src/components/syncPlay/ui/players/HtmlAudioPlayer.js b/src/plugins/syncPlay/ui/players/HtmlAudioPlayer.js similarity index 79% rename from src/components/syncPlay/ui/players/HtmlAudioPlayer.js rename to src/plugins/syncPlay/ui/players/HtmlAudioPlayer.js index 89929eb68..c0b5a3bf6 100644 --- a/src/components/syncPlay/ui/players/HtmlAudioPlayer.js +++ b/src/plugins/syncPlay/ui/players/HtmlAudioPlayer.js @@ -10,10 +10,6 @@ import HtmlVideoPlayer from './HtmlVideoPlayer'; */ class HtmlAudioPlayer extends HtmlVideoPlayer { static type = 'htmlaudioplayer'; - - constructor(player, syncPlayManager) { - super(player, syncPlayManager); - } } export default HtmlAudioPlayer; diff --git a/src/components/syncPlay/ui/players/HtmlVideoPlayer.js b/src/plugins/syncPlay/ui/players/HtmlVideoPlayer.js similarity index 100% rename from src/components/syncPlay/ui/players/HtmlVideoPlayer.js rename to src/plugins/syncPlay/ui/players/HtmlVideoPlayer.js diff --git a/src/components/syncPlay/ui/players/NoActivePlayer.js b/src/plugins/syncPlay/ui/players/NoActivePlayer.js similarity index 99% rename from src/components/syncPlay/ui/players/NoActivePlayer.js rename to src/plugins/syncPlay/ui/players/NoActivePlayer.js index 699d31517..c5f528938 100644 --- a/src/components/syncPlay/ui/players/NoActivePlayer.js +++ b/src/plugins/syncPlay/ui/players/NoActivePlayer.js @@ -3,7 +3,7 @@ * @module components/syncPlay/ui/players/NoActivePlayer */ -import { playbackManager } from '../../../playback/playbackmanager'; +import { playbackManager } from '../../../../components/playback/playbackmanager'; import SyncPlay from '../../core'; import QueueManager from './QueueManager'; diff --git a/src/components/syncPlay/ui/players/QueueManager.js b/src/plugins/syncPlay/ui/players/QueueManager.js similarity index 100% rename from src/components/syncPlay/ui/players/QueueManager.js rename to src/plugins/syncPlay/ui/players/QueueManager.js diff --git a/src/components/syncPlay/ui/settings/SettingsEditor.js b/src/plugins/syncPlay/ui/settings/SettingsEditor.js similarity index 93% rename from src/components/syncPlay/ui/settings/SettingsEditor.js rename to src/plugins/syncPlay/ui/settings/SettingsEditor.js index 3f9072514..ede7e267a 100644 --- a/src/components/syncPlay/ui/settings/SettingsEditor.js +++ b/src/plugins/syncPlay/ui/settings/SettingsEditor.js @@ -6,10 +6,10 @@ import { Events } from 'jellyfin-apiclient'; import SyncPlay from '../../core'; import { setSetting } from '../../core/Settings'; -import dialogHelper from '../../../dialogHelper/dialogHelper'; -import layoutManager from '../../../layoutManager'; -import loading from '../../../loading/loading'; -import toast from '../../../toast/toast'; +import dialogHelper from '../../../../components/dialogHelper/dialogHelper'; +import layoutManager from '../../../../components/layoutManager'; +import loading from '../../../../components/loading/loading'; +import toast from '../../../../components/toast/toast'; import globalize from '../../../../scripts/globalize'; import 'material-design-icons-iconfont'; @@ -18,8 +18,8 @@ import '../../../../elements/emby-select/emby-select'; import '../../../../elements/emby-button/emby-button'; import '../../../../elements/emby-button/paper-icon-button-light'; import '../../../../elements/emby-checkbox/emby-checkbox'; -import '../../../listview/listview.scss'; -import '../../../formdialog.scss'; +import '../../../../components/listview/listview.scss'; +import '../../../../components/formdialog.scss'; function centerFocus(elem, horiz, on) { import('../../../../scripts/scrollHelper').then((scrollHelper) => { diff --git a/src/components/syncPlay/ui/settings/editor.html b/src/plugins/syncPlay/ui/settings/editor.html similarity index 100% rename from src/components/syncPlay/ui/settings/editor.html rename to src/plugins/syncPlay/ui/settings/editor.html diff --git a/src/plugins/youtubePlayer/plugin.js b/src/plugins/youtubePlayer/plugin.js index 96d23c2f3..788dbfcfb 100644 --- a/src/plugins/youtubePlayer/plugin.js +++ b/src/plugins/youtubePlayer/plugin.js @@ -333,10 +333,8 @@ class YoutubePlayer { setVolume(val) { const currentYoutubePlayer = this.currentYoutubePlayer; - if (currentYoutubePlayer) { - if (val != null) { - currentYoutubePlayer.setVolume(val); - } + if (currentYoutubePlayer && val != null) { + currentYoutubePlayer.setVolume(val); } } getVolume() { diff --git a/src/scripts/browser.js b/src/scripts/browser.js index b340a403f..fe0601fc9 100644 --- a/src/scripts/browser.js +++ b/src/scripts/browser.js @@ -19,25 +19,14 @@ function isTv() { return true; } - if (isWeb0s()) { - return true; - } - - return false; + return isWeb0s(); } function isWeb0s() { const userAgent = navigator.userAgent.toLowerCase(); - if (userAgent.indexOf('netcast') !== -1) { - return true; - } - - if (userAgent.indexOf('web0s') !== -1) { - return true; - } - - return false; + return userAgent.indexOf('netcast') !== -1 + || userAgent.indexOf('web0s') !== -1; } function isMobile(userAgent) { @@ -84,11 +73,7 @@ function hasKeyboard(browser) { return true; } - if (browser.tv) { - return true; - } - - return false; + return !!browser.tv; } function iOSversion() { @@ -324,11 +309,9 @@ if (browser.mobile || browser.tv) { browser.slow = true; } -if (typeof document !== 'undefined') { - /* eslint-disable-next-line compat/compat */ - if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0)) { - browser.touch = true; - } +/* eslint-disable-next-line compat/compat */ +if (typeof document !== 'undefined' && ('ontouchstart' in window) || (navigator.maxTouchPoints > 0)) { + browser.touch = true; } browser.keyboard = hasKeyboard(browser); diff --git a/src/scripts/browserDeviceProfile.js b/src/scripts/browserDeviceProfile.js index 5c3b0b8ca..f68faf4e4 100644 --- a/src/scripts/browserDeviceProfile.js +++ b/src/scripts/browserDeviceProfile.js @@ -53,12 +53,8 @@ import browser from './browser'; } const media = document.createElement('video'); - if (media.canPlayType('application/x-mpegURL').replace(/no/, '') || - media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, '')) { - return true; - } - - return false; + return !!(media.canPlayType('application/x-mpegURL').replace(/no/, '') || + media.canPlayType('application/vnd.apple.mpegURL').replace(/no/, '')); } function canPlayHlsWithMSE() { @@ -110,7 +106,7 @@ import browser from './browser'; function canPlayAudioFormat(format) { let typeString; - if (format === 'flac') { + if (format === 'flac' || format === 'asf') { if (browser.tizen || browser.web0s || browser.edgeUwp) { return true; } @@ -118,10 +114,6 @@ import browser from './browser'; if (browser.tizen || browser.edgeUwp) { return true; } - } else if (format === 'asf') { - if (browser.tizen || browser.web0s || browser.edgeUwp) { - return true; - } } else if (format === 'opus') { if (browser.web0s) { // canPlayType lies about OPUS support @@ -163,17 +155,11 @@ import browser from './browser'; return true; } - if (browser.edgeUwp) { - return true; - } - - return false; + return !!browser.edgeUwp; } function testCanPlayAv1(videoTestElement) { - if (browser.tizenVersion >= 5.5) { - return true; - } else if (browser.web0sVersion >= 5) { + if (browser.tizenVersion >= 5.5 || browser.web0sVersion >= 5) { return true; } @@ -199,6 +185,7 @@ import browser from './browser'; switch (container) { case 'asf': + case 'wmv': supported = browser.tizen || browser.web0s || browser.edgeUwp; videoAudioCodecs = []; break; @@ -241,10 +228,6 @@ import browser from './browser'; videoCodecs.push('mpeg2video'); } break; - case 'wmv': - supported = browser.tizen || browser.web0s || browser.edgeUwp; - videoAudioCodecs = []; - break; case 'ts': supported = testCanPlayTs(); videoCodecs.push('h264'); @@ -822,12 +805,12 @@ import browser from './browser'; maxH264Level = 52; } - if (browser.tizen || - videoTestElement.canPlayType('video/mp4; codecs="avc1.6e0033"').replace(/no/, '')) { + if ((browser.tizen || + videoTestElement.canPlayType('video/mp4; codecs="avc1.6e0033"').replace(/no/, '')) // These tests are passing in safari, but playback is failing - if (!browser.safari && !browser.iOS && !browser.web0s && !browser.edge && !browser.mobile) { - h264Profiles += '|high 10'; - } + && !browser.safari && !browser.iOS && !browser.web0s && !browser.edge && !browser.mobile + ) { + h264Profiles += '|high 10'; } let maxHevcLevel = 120; diff --git a/src/scripts/dfnshelper.js b/src/scripts/dfnshelper.js index e184f1035..8e3a2516e 100644 --- a/src/scripts/dfnshelper.js +++ b/src/scripts/dfnshelper.js @@ -1,4 +1,4 @@ -import { af, arDZ, be, bg, bn, ca, cs, cy, da, de, el, enGB, enUS, eo, es, et, faIR, fi, fr, frCA, gl, he, hi, hr, hu, id, is, it, ja, kk, ko, lt, lv, ms, nb, +import { af, arDZ, be, bg, bn, ca, cs, cy, da, de, el, enGB, enUS, eo, es, et, eu, faIR, fi, fr, frCA, gl, he, hi, hr, hu, id, is, it, ja, kk, ko, lt, lv, ms, nb, nl, nn, pl, ptBR, pt, ro, ru, sk, sl, sv, ta, th, tr, uk, vi, zhCN, zhTW } from 'date-fns/locale'; import globalize from './globalize'; @@ -22,6 +22,7 @@ const dateLocales = (locale) => ({ 'es-do': es, 'es-mx': es, 'et': et, + 'eu': eu, 'fa': faIR, 'fi': fi, 'fr': fr, @@ -67,9 +68,14 @@ export function getLocale() { return dateLocales(globalize.getCurrentLocale()) || dateLocales(globalize.getCurrentLocale().replace(/-.*/, '')) || enUS; } -export const localeWithSuffix = { addSuffix: true, locale: getLocale() }; +export function getLocaleWithSuffix() { + return { + addSuffix: true, + locale: getLocale() + }; +} export default { getLocale: getLocale, - localeWithSuffix: localeWithSuffix + getLocaleWithSuffix }; diff --git a/src/scripts/dom.js b/src/scripts/dom.js index 8cc3e7d03..e336088c8 100644 --- a/src/scripts/dom.js +++ b/src/scripts/dom.js @@ -183,9 +183,9 @@ width = height * (16.0 / 9.0); } - return standardWidths.sort(function (a, b) { - return Math.abs(width - a) - Math.abs(width - b); - })[0]; + standardWidths.sort((a, b) => Math.abs(width - a) - Math.abs(width - b)); + + return standardWidths[0]; } /** diff --git a/src/scripts/editorsidebar.js b/src/scripts/editorsidebar.js index ca0db0503..10a474798 100644 --- a/src/scripts/editorsidebar.js +++ b/src/scripts/editorsidebar.js @@ -215,19 +215,7 @@ import { getParameterByName } from '../utils/url.ts'; } } - function onNodeOpen(event, data) { - const page = $(this).parents('.page')[0]; - const node = data.node; - if (node.children) { - loadNodesToLoad(page, node); - } - if (node.li_attr && node.id != '#' && !node.li_attr.loadedFromServer) { - node.li_attr.loadedFromServer = true; - $.jstree.reference('.libraryTree', page).load_node(node.id, loadNodeCallback); - } - } - - function onNodeLoad(event, data) { + function onNodeOpen(_, data) { const page = $(this).parents('.page')[0]; const node = data.node; if (node.children) { @@ -254,7 +242,13 @@ import { getParameterByName } from '../utils/url.ts'; variant: 'large' } } - }).off('select_node.jstree', onNodeSelect).on('select_node.jstree', onNodeSelect).off('open_node.jstree', onNodeOpen).on('open_node.jstree', onNodeOpen).off('load_node.jstree', onNodeLoad).on('load_node.jstree', onNodeLoad); + }) + .off('select_node.jstree', onNodeSelect) + .on('select_node.jstree', onNodeSelect) + .off('open_node.jstree', onNodeOpen) + .on('open_node.jstree', onNodeOpen) + .off('load_node.jstree', onNodeOpen) + .on('load_node.jstree', onNodeOpen); } function loadNodesToLoad(page, node) { @@ -327,7 +321,10 @@ import { getParameterByName } from '../utils/url.ts'; }); }).on('pagebeforehide', '.metadataEditorPage', function () { const page = this; - $('.libraryTree', page).off('select_node.jstree', onNodeSelect).off('open_node.jstree', onNodeOpen).off('load_node.jstree', onNodeLoad); + $('.libraryTree', page) + .off('select_node.jstree', onNodeSelect) + .off('open_node.jstree', onNodeOpen) + .off('load_node.jstree', onNodeOpen); }); let itemId; window.MetadataEditor = { diff --git a/src/scripts/gamepadtokey.js b/src/scripts/gamepadtokey.js index d7916f1fe..40e33a7ca 100644 --- a/src/scripts/gamepadtokey.js +++ b/src/scripts/gamepadtokey.js @@ -168,12 +168,7 @@ function throttle(key) { const time = times[key] || 0; const now = new Date().getTime(); - if ((now - time) >= 200) { - //times[key] = now; - return true; - } - - return false; + return (now - time) >= 200; } function resetThrottle(key) { @@ -187,11 +182,7 @@ function allowInput() { return false; } - if (appHost.getWindowState() === 'Minimized') { - return false; - } - - return true; + return appHost.getWindowState() !== 'Minimized'; } function raiseEvent(name, key, keyCode) { diff --git a/src/scripts/imagehelper.js b/src/scripts/imagehelper.js index 4fd2c0a0c..d481f7e4a 100644 --- a/src/scripts/imagehelper.js +++ b/src/scripts/imagehelper.js @@ -1,57 +1,63 @@ +const BASE_DEVICE_IMAGE_URL = 'assets/img/devices/'; + +function getWebDeviceIcon(browser) { + switch (browser) { + case 'Opera': + case 'Opera TV': + case 'Opera Android': + return BASE_DEVICE_IMAGE_URL + 'opera.svg'; + case 'Chrome': + case 'Chrome Android': + return BASE_DEVICE_IMAGE_URL + 'chrome.svg'; + case 'Firefox': + case 'Firefox Android': + return BASE_DEVICE_IMAGE_URL + 'firefox.svg'; + case 'Safari': + case 'Safari iPad': + case 'Safari iPhone': + return BASE_DEVICE_IMAGE_URL + 'safari.svg'; + case 'Edge Chromium': + case 'Edge Chromium Android': + case 'Edge Chromium iPad': + case 'Edge Chromium iPhone': + return BASE_DEVICE_IMAGE_URL + 'edgechromium.svg'; + case 'Edge': + return BASE_DEVICE_IMAGE_URL + 'edge.svg'; + case 'Internet Explorer': + return BASE_DEVICE_IMAGE_URL + 'msie.svg'; + default: + return BASE_DEVICE_IMAGE_URL + 'html5.svg'; + } +} /* eslint-disable indent */ export function getDeviceIcon(device) { - const baseUrl = 'assets/img/devices/'; switch (device.AppName || device.Client) { case 'Samsung Smart TV': - return baseUrl + 'samsung.svg'; + return BASE_DEVICE_IMAGE_URL + 'samsung.svg'; case 'Xbox One': - return baseUrl + 'xbox.svg'; + return BASE_DEVICE_IMAGE_URL + 'xbox.svg'; case 'Sony PS4': - return baseUrl + 'playstation.svg'; + return BASE_DEVICE_IMAGE_URL + 'playstation.svg'; case 'Kodi': case 'Kodi JellyCon': - return baseUrl + 'kodi.svg'; + return BASE_DEVICE_IMAGE_URL + 'kodi.svg'; case 'Jellyfin Android': case 'AndroidTV': case 'Android TV': - return baseUrl + 'android.svg'; + return BASE_DEVICE_IMAGE_URL + 'android.svg'; case 'Jellyfin Mobile (iOS)': case 'Jellyfin Mobile (iPadOS)': case 'Jellyfin iOS': case 'Infuse': - return baseUrl + 'apple.svg'; + return BASE_DEVICE_IMAGE_URL + 'apple.svg'; + case 'Home Assistant': + return BASE_DEVICE_IMAGE_URL + 'home-assistant.svg'; case 'Jellyfin Web': - switch (device.Name || device.DeviceName) { - case 'Opera': - case 'Opera TV': - case 'Opera Android': - return baseUrl + 'opera.svg'; - case 'Chrome': - case 'Chrome Android': - return baseUrl + 'chrome.svg'; - case 'Firefox': - case 'Firefox Android': - return baseUrl + 'firefox.svg'; - case 'Safari': - case 'Safari iPad': - case 'Safari iPhone': - return baseUrl + 'safari.svg'; - case 'Edge Chromium': - case 'Edge Chromium Android': - case 'Edge Chromium iPad': - case 'Edge Chromium iPhone': - return baseUrl + 'edgechromium.svg'; - case 'Edge': - return baseUrl + 'edge.svg'; - case 'Internet Explorer': - return baseUrl + 'msie.svg'; - default: - return baseUrl + 'html5.svg'; - } + return getWebDeviceIcon(device.Name || device.DeviceName); default: - return baseUrl + 'other.svg'; + return BASE_DEVICE_IMAGE_URL + 'other.svg'; } } diff --git a/src/scripts/libraryBrowser.js b/src/scripts/libraryBrowser.js index 87ee2a7d4..eaec77740 100644 --- a/src/scripts/libraryBrowser.js +++ b/src/scripts/libraryBrowser.js @@ -68,10 +68,8 @@ export function showLayoutMenu (button, currentLayout, views) { cancelable: false })); - if (!dispatchEvent) { - if (window.$) { - $(button).trigger('layoutchange', [id]); - } + if (!dispatchEvent && window.$) { + $(button).trigger('layoutchange', [id]); } } }); @@ -117,7 +115,8 @@ export function getQueryPagingHtml (options) { html += '
'; } - return html += ''; + html += ''; + return html; } export function showSortMenu (options) { diff --git a/src/scripts/libraryMenu.js b/src/scripts/libraryMenu.js index 81f819f80..ce1918e19 100644 --- a/src/scripts/libraryMenu.js +++ b/src/scripts/libraryMenu.js @@ -9,7 +9,8 @@ import viewManager from '../components/viewManager/viewManager'; import { appRouter } from '../components/appRouter'; import { appHost } from '../components/apphost'; import { playbackManager } from '../components/playback/playbackmanager'; -import groupSelectionMenu from '../components/syncPlay/ui/groupSelectionMenu'; +import { pluginManager } from '../components/pluginManager'; +import groupSelectionMenu from '../plugins/syncPlay/ui/groupSelectionMenu'; import browser from './browser'; import globalize from './globalize'; import imageHelper from './imagehelper'; @@ -154,8 +155,14 @@ import '../assets/css/flexstyles.scss'; const policy = user.Policy ? user.Policy : user.localUser.Policy; - const apiClient = getCurrentApiClient(); - if (headerSyncButton && policy?.SyncPlayAccess !== 'None' && apiClient.isMinServerVersion('10.6.0')) { + if ( + // Button is present + headerSyncButton + // SyncPlay plugin is loaded + && pluginManager.plugins.filter(plugin => plugin.id === 'syncplay').length > 0 + // SyncPlay enabled for user + && policy?.SyncPlayAccess !== 'None' + ) { headerSyncButton.classList.remove('hide'); } } else { diff --git a/src/scripts/mouseManager.js b/src/scripts/mouseManager.js index 1ad3389c0..b6ba2e3ca 100644 --- a/src/scripts/mouseManager.js +++ b/src/scripts/mouseManager.js @@ -88,12 +88,10 @@ import dom from '../scripts/dom'; function onPointerEnter(e) { const pointerType = e.pointerType || (layoutManager.mobile ? 'touch' : 'mouse'); - if (pointerType === 'mouse') { - if (!isMouseIdle) { - const parent = focusManager.focusableParent(e.target); - if (parent) { - focusManager.focus(parent); - } + if (pointerType === 'mouse' && !isMouseIdle) { + const parent = focusManager.focusableParent(e.target); + if (parent) { + focusManager.focus(parent); } } } @@ -107,11 +105,7 @@ import dom from '../scripts/dom'; return false; } - if (browser.tv) { - return true; - } - - return false; + return !!browser.tv; } function onMouseInterval() { diff --git a/src/scripts/multiDownload.js b/src/scripts/multiDownload.js index 54921bf4e..ec3db5201 100644 --- a/src/scripts/multiDownload.js +++ b/src/scripts/multiDownload.js @@ -56,7 +56,8 @@ export default function (urls) { urls.forEach(function (url) { // the download init has to be sequential for firefox if the urls are not on the same domain if (browser.firefox && !sameDomain(url)) { - return setTimeout(download.bind(null, url), 100 * ++delay); + setTimeout(download.bind(null, url), 100 * ++delay); + return; } download(url); diff --git a/src/scripts/serverNotifications.js b/src/scripts/serverNotifications.js index 73a10bcbd..30d2171ae 100644 --- a/src/scripts/serverNotifications.js +++ b/src/scripts/serverNotifications.js @@ -1,5 +1,5 @@ import { playbackManager } from '../components/playback/playbackmanager'; -import SyncPlay from '../components/syncPlay/core'; +import SyncPlay from '../plugins/syncPlay/core'; import { Events } from 'jellyfin-apiclient'; import inputManager from '../scripts/inputManager'; import focusManager from '../components/focusManager'; @@ -123,16 +123,11 @@ function processGeneralCommand(cmd, apiClient) { displayMessage(cmd); break; case 'ToggleOsd': - // todo - break; case 'ToggleContextMenu': - // todo - break; case 'SendKey': // todo break; case 'SendString': - // todo focusManager.sendText(cmd.Arguments.String); break; default: diff --git a/src/scripts/shell.js b/src/scripts/shell.js index aba8698cd..b21d658f9 100644 --- a/src/scripts/shell.js +++ b/src/scripts/shell.js @@ -43,7 +43,9 @@ export default { */ downloadFiles(items) { if (window.NativeShell?.downloadFile) { - items.forEach(item => window.NativeShell.downloadFile(item)); + items.forEach(item => { + window.NativeShell.downloadFile(item); + }); return true; } return false; diff --git a/src/strings/ar.json b/src/strings/ar.json index 22f643e25..3a3ffe10a 100644 --- a/src/strings/ar.json +++ b/src/strings/ar.json @@ -1672,5 +1672,9 @@ "RememberSubtitleSelections": "تعيين مسار الترجمة على أساس البند السابق", "RememberAudioSelectionsHelp": "حاول ضبط المسار الصوتي على أقرب تطابق للفيديو الأخير.", "RememberAudioSelections": "تعيين مسار الصوت على أساس البند السابق", - "LabelMaxVideoResolution": "الحد الأقصى المسموح به لقرار تحويل ترميز الفيديو" + "LabelMaxVideoResolution": "الحد الأقصى المسموح به لقرار تحويل ترميز الفيديو", + "OptionDateShowAdded": "تاريخ اضافة العرض", + "OptionDateEpisodeAdded": "تاريخ اضافة الحلقة", + "IgnoreDtsHelp": "قد يؤدي تعطيل هذا الخيار إلى حل بعض المشكلات ، على سبيل المثال فقدان الصوت على القنوات التي تحتوي على دفق صوت وفيديو منفصل.", + "IgnoreDts": "تجاهل DTS (الطابع الزمني لفك التشفير)" } diff --git a/src/strings/ca.json b/src/strings/ca.json index a5f85cae8..c8db8f23e 100644 --- a/src/strings/ca.json +++ b/src/strings/ca.json @@ -969,7 +969,7 @@ "NoNewDevicesFound": "No es van trobar nous dispositius. Per afegir un nou sintonitzador, tancar aquest diàleg i introduïu la informació de el dispositiu de forma manual.", "NoCreatedLibraries": "Sembla que no s'ha creat cap biblioteca encara. {0} Li agradaria crear una ara? {1}", "No": "No", - "NextUp": "Fins a la pròxima", + "NextUp": "A continuació", "NextTrack": "Passar a la següent", "Next": "Pròxim", "News": "Notícies", @@ -1644,5 +1644,8 @@ "ButtonClose": "Tancar", "ButtonBackspace": "Retrocés", "Arranger": "Arranjador", - "AddToFavorites": "Afegir a preferits" + "AddToFavorites": "Afegir a preferits", + "EnableSplashScreen": "Habilita la pantalla de benvinguda", + "ScreenResolution": "Resolució de pantalla", + "Bold": "Negreta" } diff --git a/src/strings/en-gb.json b/src/strings/en-gb.json index a05d5e3d7..5b721ee88 100644 --- a/src/strings/en-gb.json +++ b/src/strings/en-gb.json @@ -1675,5 +1675,9 @@ "RememberSubtitleSelections": "Set subtitle track based on previous item", "RememberAudioSelectionsHelp": "Try to set the audio track to the closest match to the last video.", "RememberAudioSelections": "Set audio track based on previous item", - "LabelMaxVideoResolution": "Maximum Allowed Video Transcoding Resolution" + "LabelMaxVideoResolution": "Maximum Allowed Video Transcoding Resolution", + "OptionDateShowAdded": "Date Show Added", + "OptionDateEpisodeAdded": "Date Episode Added", + "IgnoreDtsHelp": "Disabling this option may resolve some issues, e.g. missing audio on channels with separate audio and video streams.", + "IgnoreDts": "Ignore DTS (decoding timestamp)" } diff --git a/src/strings/fr.json b/src/strings/fr.json index 85fe0a38c..41fdac284 100644 --- a/src/strings/fr.json +++ b/src/strings/fr.json @@ -1676,5 +1676,8 @@ "RememberSubtitleSelections": "Définir la piste de sous-titre en fonction de l'élément précédent", "RememberAudioSelectionsHelp": "Choisir la piste audio la plus proche de la dernière vidéo.", "RememberAudioSelections": "Définir la piste audio en fonction de l'élément précédent", - "IgnoreDtsHelp": "Désactiver cette option pourrait résoudre certains problèmes, comme par exemple l'audio manquant sur des pistes ayant des flux audios et vidéos séparés." + "IgnoreDtsHelp": "La désactivation de cette option peut résoudre certains problèmes, par ex. audio manquant sur les canaux avec des flux audio et vidéo séparés.", + "OptionDateShowAdded": "Date d'ajout de la série", + "OptionDateEpisodeAdded": "Date d'ajout de l'épisode", + "IgnoreDts": "Ignorer DTS (horodatage de décodage)" } diff --git a/src/strings/pt-pt.json b/src/strings/pt-pt.json index 770239a50..e0c72aa6d 100644 --- a/src/strings/pt-pt.json +++ b/src/strings/pt-pt.json @@ -1413,7 +1413,7 @@ "SmallCaps": "Maiúsculas reduzidas", "LabelTonemappingRange": "Alcance do Tone Mapping :", "TonemappingAlgorithmHelp": "O Tone Mapping pode ser afinado. Se não estiver familiarizado com estas opções, mantenha-as nas opções padrão. O valor recomendado é 'BT.2390'.", - "AllowTonemappingHelp": "O processo de Tone Mapping transforma o Dynamic Range de um vídeo HDR em SDR, mantendo os detalhes e as cores, sendo estes bastante importantes para preservar a cena original. Atualmente funciona apenas com vídeos HDR10 ou HLG. Esta opção requer os executáveis CUDA ou OpenCL correspondentes.", + "AllowTonemappingHelp": "O processo de Tone Mapping transforma o Dynamic Range de um vídeo HDR em SDR, mantendo os detalhes e as cores, sendo estes bastante importantes para preservar a cena original. Atualmente funciona apenas com vídeos HDR10 ou HLG. Esta opção requer os executáveis CUDA ou OpenCL correspondentes.", "LabelMaxMuxingQueueSizeHelp": "Número máximo de pacotes armazenados em buffer enquanto aguardam que todas as transmissões iniciem. Tente aumentá-lo caso se depare com o seguinte erro nos registos do FFmpeg \"Too many packets buffered for output stream\". O valor recomendado é 2048.", "DeleteAll": "Apagar tudo", "DeleteDevicesConfirmation": "Tem a certeza que deseja remover todos os dispositivos? Todas as outras sessões serão terminadas. Os dispositivos reaparecerão da próxima vez que um utilizador iniciar sessão.", diff --git a/src/strings/pt.json b/src/strings/pt.json index 89c160448..ed7298ea2 100644 --- a/src/strings/pt.json +++ b/src/strings/pt.json @@ -934,7 +934,7 @@ "AllLibraries": "Todas as bibliotecas", "AllLanguages": "Todos os idiomas", "AllEpisodes": "Todos os episódios", - "AllComplexFormats": "Todos os formatos complexos (ASS, SSA, VOBSUB, PGS, SUB, IDX, etc.)", + "AllComplexFormats": "Todos os formatos complexos (ASS, SSA, VobSub, PGS, SUB, IDX, etc.)", "AllChannels": "Todos os canais", "All": "Todos", "Alerts": "Alertas", diff --git a/src/strings/sk.json b/src/strings/sk.json index 649edb3e4..9fee841ca 100644 --- a/src/strings/sk.json +++ b/src/strings/sk.json @@ -1675,5 +1675,9 @@ "RememberSubtitleSelections": "Nastaviť titulky podľa predchádzajúcej položky", "RememberAudioSelectionsHelp": "Pokúsi sa nastaviť zvukovú stopu tak, aby sa čo najviac zhodovala s posledným videom.", "RememberAudioSelections": "Nastaviť zvukovú stopu podľa predchádzajúcej položky", - "LabelMaxVideoResolution": "Maximálne dovolené rozlíšenie videa pre prekódovanie" + "LabelMaxVideoResolution": "Maximálne dovolené rozlíšenie videa pre prekódovanie", + "OptionDateShowAdded": "Dátum pridania seriálu", + "OptionDateEpisodeAdded": "Dátum pridania epizódy", + "IgnoreDtsHelp": "Vypnutím sa môžu vyriešiť niektoré problémy, napr. chýbajúci zvuk pri kanáloch so samostatnými zvukovými a video streamami.", + "IgnoreDts": "Ignorovať DTS (dekódovacia časová pečiatka)" } diff --git a/src/strings/ta.json b/src/strings/ta.json index 1432ff278..20e085348 100644 --- a/src/strings/ta.json +++ b/src/strings/ta.json @@ -429,9 +429,9 @@ "HeaderXmlSettings": "XML அமைப்புகள்", "HeaderXmlDocumentAttributes": "XML ஆவண பண்புக்கூறுகள்", "HeaderXmlDocumentAttribute": "XML ஆவணப் பண்புக்கூறு", - "HeaderVideos": "வீடியோக்கள்", - "HeaderVideoTypes": "வீடியோ வகைகள்", - "HeaderVideoType": "வீடியோ வகை", + "HeaderVideos": "காணொளிகள்", + "HeaderVideoTypes": "காணொளி வகைகள்", + "HeaderVideoType": "காணொளி வகை", "HeaderVideoQuality": "வீடியோ தரம்", "HeaderUsers": "பயனர்கள்", "HeaderUser": "பயனர்", @@ -604,7 +604,7 @@ "LabelMinScreenshotDownloadWidth": "குறைந்தபட்ச ஸ்கிரீன்ஷாட் பதிவிறக்க அகலம்:", "LabelMinResumePercentageHelp": "இந்த நேரத்திற்கு முன் நிறுத்தப்பட்டால் தலைப்புகள் காட்டப்படாது என்று கருதப்படுகிறது.", "LabelMinResumePercentage": "குறைந்தபட்ச மறுதொடக்கம் சதவீதம்:", - "LabelMinResumeDurationHelp": "பின்னணி இருப்பிடத்தைச் சேமிக்கும் மற்றும் மீண்டும் தொடங்க அனுமதிக்கும் வினாடிகளில் மிகக் குறுகிய வீடியோ நீளம்.", + "LabelMinResumeDurationHelp": "பின்னணி இருப்பிடத்தைச் சேமிக்கும் மற்றும் மீண்டும் தொடங்க அனுமதிக்கும் வினாடிகளில் மிகக் குறுகிய காணொளி நீளம்.", "LabelMinResumeDuration": "குறைந்தபட்ச மறுதொடக்கம் காலம்:", "LabelMinBackdropDownloadWidth": "குறைந்தபட்ச பின்னணி பதிவிறக்க அகலம்:", "LabelMethod": "முறை:", @@ -670,7 +670,7 @@ "LabelHardwareAccelerationType": "வன்பொருள் முடுக்கம்:", "LabelEncoderPreset": "குறியீட்டு முன்னமைவு:", "LabelH264Crf": "H.264 குறியாக்கம் CRF:", - "LabelGroupMoviesIntoCollectionsHelp": "திரைப்படங்களின் பட்டியல் காட்சியைத் தேர்வுசெய்தால், பெட்டித் தொகுப்புகள் குழுவாக்கப்பட்ட திரைப்படங்களுடன் உருப்படிகளாகக் காட்டப்படும்.", + "LabelGroupMoviesIntoCollectionsHelp": "திரைப்படப் பட்டியல்களைக் காண்பிக்கும் போது தொகுப்பில் உள்ள திரைப்படங்கள் ஒரு குழுவாகக் காட்டப்படும்.", "LabelGroupMoviesIntoCollections": "திரைப்படங்களை தொகுப்பாக குழு செய்யவும்", "LabelServerNameHelp": "சேவையகத்தை அடையாளம் காண இந்த பெயர் பயன்படுத்தப்படும் மற்றும் சேவையகத்தின் ஹோஸ்ட்பெயருக்கு இயல்புநிலையாக இருக்கும்.", "LabelFriendlyName": "நட்பு பெயர்:", @@ -681,7 +681,7 @@ "LabelFinish": "முடி", "LabelFileOrUrl": "கோப்பு அல்லது URL:", "LabelFailed": "தோல்வி", - "LabelExtractChaptersDuringLibraryScanHelp": "நூலக ஸ்கேன் போது வீடியோக்கள் இறக்குமதி செய்யப்படும்போது அத்தியாய படங்களை உருவாக்கவும். இல்லையெனில், வழக்கமான பட ஸ்கேன் வேகமாக முடிக்க அனுமதிக்கும் அத்தியாயப் படங்கள் திட்டமிடப்பட்ட பணியின் போது அவை பிரித்தெடுக்கப்படும்.", + "LabelExtractChaptersDuringLibraryScanHelp": "நூலக ஸ்கேன் போது காணொளிகள் இறக்குமதி செய்யப்படும்போது அத்தியாய படங்களை உருவாக்கவும். இல்லையெனில், வழக்கமான பட ஸ்கேன் வேகமாக முடிக்க அனுமதிக்கும் அத்தியாயப் படங்கள் திட்டமிடப்பட்ட பணியின் போது அவை பிரித்தெடுக்கப்படும்.", "LabelExtractChaptersDuringLibraryScan": "நூலக ஸ்கேன் போது அத்தியாய படங்களை பிரித்தெடுக்கவும்", "LabelBaseUrlHelp": "சேவையக URL இல் தனிப்பயன் துணை அடைவைச் சேர்க்கவும். உதாரணத்திற்கு: http://example.com/<baseurl>", "LabelBaseUrl": "அடிப்படை URL:", @@ -807,7 +807,7 @@ "LabelSonyAggregationFlags": "Sony திரட்டல் கொடிகள்:", "LabelSkipIfGraphicalSubsPresentHelp": "வசன வரிகள் உரை பதிப்புகளை வைத்திருப்பது மிகவும் திறமையான விநியோகத்திற்கு வழிவகுக்கும் மற்றும் வீடியோ டிரான்ஸ்கோடிங்கின் சாத்தியத்தை குறைக்கும்.", "LabelSkipIfGraphicalSubsPresent": "வீடியோவில் ஏற்கனவே உட்பொதிக்கப்பட்ட வசன வரிகள் இருந்தால் தவிர்க்கவும்", - "LabelSkipIfAudioTrackPresentHelp": "ஆடியோ மொழியைப் பொருட்படுத்தாமல் எல்லா வீடியோக்களுக்கும் வசன வரிகள் இருப்பதை உறுதிப்படுத்த இதைத் தேர்வுநீக்கவும்.", + "LabelSkipIfAudioTrackPresentHelp": "ஆடியோ மொழியைப் பொருட்படுத்தாமல் எல்லா காணொளிகளுக்கும் வசன வரிகள் இருப்பதை உறுதிப்படுத்த இதைத் தேர்வுநீக்கவும்.", "LabelSkipIfAudioTrackPresent": "இயல்புநிலை ஆடியோ டிராக் பதிவிறக்க மொழியுடன் பொருந்தினால் தவிர்க்கவும்", "LabelSkipForwardLength": "முன்னோக்கி நீளத்தைத் தவிர்:", "LabelSkipBackLength": "பின் நீளத்தைத் தவிர்:", @@ -833,7 +833,7 @@ "LabelRuntimeMinutes": "இயக்க நேரம்:", "LabelRequireHttpsHelp": "சரிபார்க்கப்பட்டால், சேவையகம் தானாகவே HTTP வழியாக அனைத்து கோரிக்கைகளையும் HTTPS க்கு திருப்பிவிடும். HTTPS இல் சேவையகம் கேட்கவில்லை என்றால் இது எந்த விளைவையும் ஏற்படுத்தாது.", "LabelRequireHttps": "HTTPS தேவை", - "LabelRemoteClientBitrateLimitHelp": "நெட்வொர்க் சாதனங்களுக்கு வெளியே ஒரு விருப்பமான ஸ்ட்ரீம் பிட்ரேட் வரம்பு. உங்கள் இணைய இணைப்பு கையாளக்கூடியதை விட சாதனங்களை அதிக பிட்ரேட்டைக் கோருவதைத் தடுக்க இது பயனுள்ளதாக இருக்கும். பறக்கும்போது வீடியோக்களை குறைந்த பிட்ரேட்டுக்கு டிரான்ஸ்கோட் செய்வதற்காக இது உங்கள் சேவையகத்தில் CPU சுமை அதிகரிக்கும்.", + "LabelRemoteClientBitrateLimitHelp": "நெட்வொர்க் சாதனங்களுக்கு வெளியே ஒரு விருப்பமான ஸ்ட்ரீம் பிட்ரேட் வரம்பு. உங்கள் இணைய இணைப்பு கையாளக்கூடியதை விட சாதனங்களை அதிக பிட்ரேட்டைக் கோருவதைத் தடுக்க இது பயனுள்ளதாக இருக்கும். பறக்கும்போது காணொளிகளை குறைந்த பிட்ரேட்டுக்கு டிரான்ஸ்கோட் செய்வதற்காக இது உங்கள் சேவையகத்தில் CPU சுமை அதிகரிக்கும்.", "LabelRemoteClientBitrateLimit": "இணைய ஸ்ட்ரீமிங் பிட்ரேட் வரம்பு (Mbps):", "LabelReleaseDate": "வெளிவரும் தேதி:", "LabelRefreshMode": "புதுப்பிப்பு பயன்முறை:", @@ -848,7 +848,7 @@ "LabelProtocolInfoHelp": "சாதனத்திலிருந்து GetProtocolInfo கோரிக்கைகளுக்கு பதிலளிக்கும் போது பயன்படுத்தப்படும் மதிப்பு.", "LabelProtocolInfo": "நெறிமுறை தகவல்:", "LabelProtocol": "நெறிமுறை:", - "LabelProfileVideoCodecs": "வீடியோ கோடெக்குகள்:", + "LabelProfileVideoCodecs": "காணொளி கோடெக்குகள்:", "LabelProfileContainersHelp": "கமாவால் பிரிக்கப்பட்டது. எல்லா கொள்கலன்களுக்கும் விண்ணப்பிக்க இதை காலியாக விடலாம்.", "LabelProfileContainer": "கொள்கலன்:", "LabelProfileCodecsHelp": "கமாவால் பிரிக்கப்பட்டது. எல்லா கோடெக்குகளுக்கும் விண்ணப்பிக்க இதை காலியாக விடலாம்.", @@ -1665,5 +1665,28 @@ "EnableRewatchingNextUp": "அடுத்ததில் மீண்டும் பார்ப்பதை இயக்கவும்", "Digital": "டிஜிட்டல்", "EnableEnhancedNvdecDecoderHelp": "சோதனை NVDEC செயல்படுத்தல், டிகோடிங் பிழைகளை நீங்கள் சந்திக்கும் வரை இந்த விருப்பத்தை இயக்க வேண்டாம்.", - "HomeVideosPhotos": "முகப்பு வீடியோக்கள் மற்றும் புகைப்படங்கள்" + "HomeVideosPhotos": "முகப்பு காணொளிகள் மற்றும் புகைப்படங்கள்", + "LabelMaxVideoResolution": "அனுமதிக்கப்பட்ட அதிகபட்ச காணொளி டிரான்ஸ்கோடிங் தெளிவுத்திறன்", + "IgnoreDtsHelp": "இந்த விருப்பத்தை முடக்குவது சில சிக்கல்களை தீர்க்கலாம், எ.கா. தனி ஆடியோ மற்றும் காணொளி ஸ்ட்ரீம்கள் கொண்ட சேனல்களில் ஆடியோ இருக்காது.", + "MediaInfoDvProfile": "DV விவரம்", + "MediaInfoDvLevel": "DV நிலை", + "MediaInfoDvVersionMinor": "சிறிய DV பதிப்பு", + "MediaInfoDvVersionMajor": "முக்கிய DV பதிப்பு", + "MediaInfoDoViTitle": "DV தலைப்பு", + "MediaInfoVideoRangeType": "வீடியோ வரம்பு வகை", + "LabelVideoRangeType": "வீடியோ வரம்பு வகை:", + "VideoRangeTypeNotSupported": "வீடியோவின் வரம்பு வகை ஆதரிக்கப்படவில்லை", + "LabelVppTonemappingContrastHelp": "VPP tone மேப்பிங்கில் கான்ட்ராஸ்ட் ஆதாயத்தைப் பயன்படுத்தவும். பரிந்துரைக்கப்பட்ட மற்றும் இயல்புநிலை மதிப்புகள் 1.2 மற்றும் 1 ஆகும்.", + "LabelVppTonemappingBrightnessHelp": "VPP tone மேப்பிங்கில் பிரகாச ஆதாயத்தைப் பயன்படுத்தவும். பரிந்துரைக்கப்பட்ட மற்றும் இயல்புநிலை மதிப்புகள் இரண்டும் 0 ஆகும்.", + "EnableSplashScreen": "ஸ்பிளாஸ் திரையை இயக்கவும்", + "ScreenResolution": "திரை அளவு", + "RememberSubtitleSelectionsHelp": "சப்டைட்டில் டிராக்கை கடைசி வீடியோவுக்கு மிக நெருக்கமான பொருத்தமாக அமைக்க முயற்சிக்கவும்.", + "RememberSubtitleSelections": "முந்தைய உருப்படியின் அடிப்படையில் வசன வரிகளை அமைக்கவும்", + "RememberAudioSelectionsHelp": "கடைசி வீடியோவுக்கு மிக நெருக்கமான பொருத்தத்திற்கு ஆடியோ டிராக்கை அமைக்க முயற்சிக்கவும்.", + "RememberAudioSelections": "முந்தைய உருப்படியின் அடிப்படையில் ஆடியோ டிராக்கை அமைக்கவும்", + "OptionDateShowAdded": "நிகழ்ச்சி சேர்க்கப்பட்ட தேதி", + "OptionDateEpisodeAdded": "அத்தியாயம் சேர்க்கப்பட்ட தேதி", + "Bold": "தடி", + "LabelTextWeight": "உரையின் எடை:", + "IgnoreDts": "DTS புறக்கணிக்கவும்" } diff --git a/src/strings/tr.json b/src/strings/tr.json index 1afaa9734..7b1262c88 100644 --- a/src/strings/tr.json +++ b/src/strings/tr.json @@ -914,7 +914,7 @@ "QuickConnectActivationSuccessful": "Başarıyla etkinleştirildi", "QuickConnect": "Hızlı Bağlantı", "LabelQuickConnectCode": "Hızlı Bağlantı kodu:", - "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Bu ayarlar, bu cihaz tarafından başlatılan herhangi bir Chromecast oynatma için de geçerlidir.", + "SubtitleAppearanceSettingsAlsoPassedToCastDevices": "Bu ayarlar, bu cihaz tarafından başlatılan herhangi bir ChromeCast oynatma için de geçerlidir.", "StopPlayback": "Oynatmayı durdur", "SyncPlayAccessHelp": "SyncPlay özelliği, oynatmayı diğer cihazlarla senkronize etmeyi sağlar. Bu kullanıcının SyncPlay sahip olduğu erişim düzeyini seçin.", "TitlePlayback": "Oynatma", @@ -1670,5 +1670,8 @@ "RememberSubtitleSelectionsHelp": "Altyazı dilini, son videoya en yakın eşleşmeye göre ayarlamayı dene.", "RememberAudioSelectionsHelp": "Ses dilini, son videoya en yakın eşleşmeye göre ayarlamayı dene.", "RememberAudioSelections": "Ses dilini önceki öğeye göre ayarla", - "RememberSubtitleSelections": "Altyazı dilini önceki öğeye göre ayarla" + "RememberSubtitleSelections": "Altyazı dilini önceki öğeye göre ayarla", + "TabContainers": "Barındırıcılar", + "OptionDateShowAdded": "Dizi Eklenme Tarihi", + "OptionDateEpisodeAdded": "Bölüm Eklenme Tarihi" } diff --git a/src/strings/ug.json b/src/strings/ug.json index d9bab4f51..c2126a764 100644 --- a/src/strings/ug.json +++ b/src/strings/ug.json @@ -2,7 +2,7 @@ "Album": "البم", "Actor": "ئاكتيور", "Absolute": "مۇتلەق", - "ButtonCast": "ميڊيا کي ڊوائس ۾ ڪاسٽ ڪريو", + "ButtonCast": "ئۈسكۈنىگە يوللاش", "Channels": "قانال", "Books": "كىتاب", "Artists": "سەنئەتكار", @@ -13,5 +13,17 @@ "Favorites": "ساقلىغۇچ", "AddedOnValue": "{0} ى قوشۇلدى", "Add": "قوشۇش", - "AccessRestrictedTryAgainLater": "نۆۋەتتە زىيارىتىڭىز چەكلىمىگە ئۇچرىدى. سەل تۇرۇپ قايتا سىناڭ." + "AccessRestrictedTryAgainLater": "نۆۋەتتە زىيارىتىڭىز چەكلىمىگە ئۇچرىدى. سەل تۇرۇپ قايتا سىناڭ.", + "ValueSpecialEpisodeName": "خاسلىق - {0}", + "Sync": "ماس قەدەمدەش", + "Songs": "ناخشىلار", + "Shows": "پروگراممىلار", + "Playlists": "قويۇش تىزىملىكى", + "Photos": "رەسىملەر", + "MusicVideos": "سىنلىق مۇزىكا", + "Movies": "فىلىملەر", + "HeaderContinueWatching": "داۋاملىق كۆرۈش", + "HeaderAlbumArtists": "پىلاستىنكا سەنئەتكارلىرى", + "Genres": "ئۇسلۇبلار", + "Small": "كىچىك" } diff --git a/src/strings/vi.json b/src/strings/vi.json index 4b3e69f6a..ae96ca1c4 100644 --- a/src/strings/vi.json +++ b/src/strings/vi.json @@ -1668,5 +1668,8 @@ "Trailer": "", "TypeOptionPluralVideo": "Videos", "ButtonSpace": "Cách", - "ButtonBackspace": "Xóa" + "ButtonBackspace": "Xóa", + "OptionDateEpisodeAdded": "Ngày Thêm Tập", + "IgnoreDtsHelp": "Việc tắt tùy chọn này có thể giải quyết một số vấn đề, ví dụ: thiếu âm thanh trên các kênh có luồng âm thanh và video riêng biệt.", + "IgnoreDts": "Bỏ qua DTS (dấu thời gian giải mã)" } diff --git a/webpack.common.js b/webpack.common.js index fddcfe66a..b7c3ad6f4 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -121,7 +121,7 @@ const config = { }, { test: /\.(js|jsx)$/, - exclude: /node_modules[\\/](?!@uupaa[\\/]dynamic-import-polyfill|blurhash|date-fns|epubjs|flv.js|libarchive.js|marked|react-router|screenfull)/, + exclude: /node_modules[\\/](?!@uupaa[\\/]dynamic-import-polyfill|@remix-run[\\/]router|blurhash|date-fns|epubjs|flv.js|libarchive.js|marked|react-router|screenfull)/, use: [{ loader: 'babel-loader', options: {