diff --git a/frontend/scripts/virtual-modules.ts b/frontend/scripts/virtual-modules.ts index 431b837027b..05391079f55 100644 --- a/frontend/scripts/virtual-modules.ts +++ b/frontend/scripts/virtual-modules.ts @@ -57,7 +57,7 @@ const vuetifyExports = localeNames * Get commit hash */ const commit_available = !Number(process.env.IS_STABLE) && Boolean(process.env.COMMIT_HASH); -const commit_hash = commit_available && `'${process.env.COMMIT_HASH}'` || undefined; +const commit_hash = (commit_available && `'${process.env.COMMIT_HASH}'`) || undefined; /** * Date-fns exports all english locales with variants, so we need to add the match manually diff --git a/frontend/src/components/Buttons/Playback/PlaybackSettingsButton.vue b/frontend/src/components/Buttons/Playback/PlaybackSettingsButton.vue index c49bc240ac1..0abfda2fbc2 100644 --- a/frontend/src/components/Buttons/Playback/PlaybackSettingsButton.vue +++ b/frontend/src/components/Buttons/Playback/PlaybackSettingsButton.vue @@ -139,7 +139,7 @@ const validationRules = [ * Chromium ranges: * https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/html/media/html_media_element.cc */ - return num_val >= 0.0625 && num_val <= 16 || t('mustBeInRange', { min: 0.0625, max: 16 }); + return (num_val >= 0.0625 && num_val <= 16) || t('mustBeInRange', { min: 0.0625, max: 16 }); } ]; diff --git a/frontend/src/components/Dialogs/ConfirmDialog.vue b/frontend/src/components/Dialogs/ConfirmDialog.vue index a357f006270..f328ba4a7a3 100644 --- a/frontend/src/components/Dialogs/ConfirmDialog.vue +++ b/frontend/src/components/Dialogs/ConfirmDialog.vue @@ -18,7 +18,7 @@ - + - {{ state.confirmText }} + {{ state.confirmText ?? t('accept') }} @@ -89,9 +89,9 @@ export async function useConfirmDialog( params: ConfirmDialogState, raiseError = false ): Promise { - state.title = params.title || ''; + state.title = params.title; + state.text = params.text; state.subtitle = params.subtitle; - state.text = params.text || ''; state.confirmText = params.confirmText; state.confirmColor = params.confirmColor; @@ -109,6 +109,4 @@ export async function useConfirmDialog( diff --git a/frontend/src/components/Forms/LoginForm.vue b/frontend/src/components/Forms/LoginForm.vue index f378e7dc177..47d4832cb22 100644 --- a/frontend/src/components/Forms/LoginForm.vue +++ b/frontend/src/components/Forms/LoginForm.vue @@ -72,7 +72,6 @@ import IconEye from 'virtual:icons/mdi/eye'; import IconEyeOff from 'virtual:icons/mdi/eye-off'; import { ref, shallowRef } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useRouter } from 'vue-router'; import { fetchIndexPage } from '@/utils/items'; import { remote } from '@/plugins/remote'; import { jsonConfig } from '@/utils/external-config'; @@ -85,8 +84,6 @@ defineEmits<{ const { t } = useI18n(); -const router = useRouter(); - const valid = shallowRef(false); const login = ref({ username: '', password: '', rememberMe: true }); const showPassword = shallowRef(false); diff --git a/frontend/src/components/Item/MediaStreamSelector.vue b/frontend/src/components/Item/MediaStreamSelector.vue index c7fa13411cd..31594639309 100644 --- a/frontend/src/components/Item/MediaStreamSelector.vue +++ b/frontend/src/components/Item/MediaStreamSelector.vue @@ -123,7 +123,7 @@ const trackIndex = ref(defaultStreamIndex ?? mediaStreams.find(tr if ( (type === 'Video' || type === 'Audio') && trackIndex.value === null - && selectItems.value[0] !== undefined + && selectItems.value?.[0] ) { // eslint-disable-next-line unicorn/no-null trackIndex.value = selectItems.value[0].value ?? null; diff --git a/frontend/src/composables/apis.ts b/frontend/src/composables/apis.ts index cac1b4567da..7c96d4998fd 100644 --- a/frontend/src/composables/apis.ts +++ b/frontend/src/composables/apis.ts @@ -13,7 +13,7 @@ import { apiStore } from '@/store/api'; import { isArray, isNil } from '@/utils/validation'; import { JView_isRouting } from '@/store/keys'; -/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-explicit-any */ type OmittedKeys = 'fields' | 'userId' | 'enableImages' | 'enableTotalRecordCount' | 'enableImageTypes'; type ParametersAsGetters any> = T extends (...args: infer P) => any ? { [K in keyof P]: () => BetterOmit, OmittedKeys> } @@ -470,4 +470,4 @@ export function methodsAsObject any>, K }; } -/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */ +/* eslint-enable @typescript-eslint/no-explicit-any */ diff --git a/frontend/src/composables/use-datefns.ts b/frontend/src/composables/use-datefns.ts index 14273a1fea5..d24775e5609 100644 --- a/frontend/src/composables/use-datefns.ts +++ b/frontend/src/composables/use-datefns.ts @@ -2,7 +2,7 @@ import * as datefnslocales from 'virtual:locales/date-fns'; import { i18n } from '@/plugins/i18n'; import { isObj } from '@/utils/validation'; -/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return */ +/* eslint-disable @typescript-eslint/no-explicit-any */ /** * Use any date fns function with proper localization, based on the current locale. @@ -35,4 +35,4 @@ export function useDateFns any>( return func(...params); } -/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-return */ +/* eslint-enable @typescript-eslint/no-explicit-any */ diff --git a/frontend/src/pages/genre/[itemId].vue b/frontend/src/pages/genre/[itemId].vue index d5c432fa11b..2a212977f7f 100644 --- a/frontend/src/pages/genre/[itemId].vue +++ b/frontend/src/pages/genre/[itemId].vue @@ -65,7 +65,7 @@ const route = useRoute('/genre/[itemId]'); const { itemId } = route.params; const includeItemTypes = computed(() => { - const typesQuery = route.query.type as BaseItemKind ?? []; + const typesQuery = route.query.type ?? [] as BaseItemKind; return isStr(typesQuery) ? [typesQuery] diff --git a/frontend/src/pages/server/login.vue b/frontend/src/pages/server/login.vue index 48b9c84e945..41452d67f6f 100644 --- a/frontend/src/pages/server/login.vue +++ b/frontend/src/pages/server/login.vue @@ -101,7 +101,6 @@ meta: import type { UserDto } from '@jellyfin/sdk/lib/generated-client'; import { ref, shallowRef, computed, watch } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useRouter } from 'vue-router'; import { remote } from '@/plugins/remote'; import { jsonConfig } from '@/utils/external-config'; import { usePageTitle } from '@/composables/page-title'; @@ -109,7 +108,6 @@ import { useSnackbar } from '@/composables/use-snackbar'; import { isConnectedToServer } from '@/store'; const { t } = useI18n(); -const router = useRouter(); usePageTitle(() => t('login')); diff --git a/frontend/src/plugins/directives.ts b/frontend/src/plugins/directives.ts index 17217c959a1..64969e36dd3 100644 --- a/frontend/src/plugins/directives.ts +++ b/frontend/src/plugins/directives.ts @@ -4,7 +4,7 @@ import type { DirectiveBinding } from 'vue'; * Toggles the CSS 'visibility' property of an element. */ export function hideDirective( - element: HTMLElement, + element: HTMLElement | undefined, binding: DirectiveBinding ): void { if (element) { diff --git a/frontend/src/store/playback-manager.ts b/frontend/src/store/playback-manager.ts index bf24026d5c0..540edde70d6 100644 --- a/frontend/src/store/playback-manager.ts +++ b/frontend/src/store/playback-manager.ts @@ -838,10 +838,6 @@ class PlaybackManagerStore extends CommonStore { itemId })); - if (!items.value) { - throw new Error('No items found'); - } - for (const item of items.value) { await this.addToQueue(item); } diff --git a/frontend/src/utils/images.ts b/frontend/src/utils/images.ts index 4b8e3694681..3a3990f3182 100644 --- a/frontend/src/utils/images.ts +++ b/frontend/src/utils/images.ts @@ -52,9 +52,9 @@ export function getImageTag( case ImageType.Primary: { return ( item.AlbumPrimaryImageTag - || item.ChannelPrimaryImageTag - || item.ParentPrimaryImageTag - || undefined + ?? item.ChannelPrimaryImageTag + ?? item.ParentPrimaryImageTag + ?? undefined ); } case ImageType.Art: { diff --git a/frontend/src/utils/items.ts b/frontend/src/utils/items.ts index 986991a4bc2..d2cf4557697 100644 --- a/frontend/src/utils/items.ts +++ b/frontend/src/utils/items.ts @@ -248,8 +248,8 @@ export function canPlay(item: BaseItemDto | undefined): boolean { 'Series', 'Trailer', 'Video' - ].includes(item.Type || '') - || ['Video', 'Audio'].includes(item.MediaType || '') + ].includes(item.Type ?? '') + || ['Video', 'Audio'].includes(item.MediaType ?? '') || item.IsFolder ); } @@ -285,7 +285,7 @@ export function canMarkWatched(item: BaseItemDto): boolean { */ export function canInstantMix(item: BaseItemDto): boolean { return ['Audio', 'MusicAlbum', 'MusicArtist', 'MusicGenre'].includes( - item.Type || '' + item.Type ?? '' ); } @@ -328,7 +328,7 @@ export function getItemDetailsLink( if (!isPerson(item) && isLibrary(item)) { routeName = '/library/[itemId]'; } else { - const type = overrideType || item.Type; + const type = overrideType ?? item.Type; switch (type) { case 'Series': { diff --git a/packages/configs/lint/rules/typescript-vue.ts b/packages/configs/lint/rules/typescript-vue.ts index 994084b3c3f..9a99290e5bc 100644 --- a/packages/configs/lint/rules/typescript-vue.ts +++ b/packages/configs/lint/rules/typescript-vue.ts @@ -14,6 +14,8 @@ import globals from 'globals'; import vueParser from 'vue-eslint-parser'; import { eqeqeqConfig, vueAndTsFiles, vueFiles, tsFiles } from '../shared'; +const recommendedKey = 'flat/recommended'; + /** * Util functions */ @@ -34,11 +36,11 @@ const common = [ name: '(@jellyfin-vue/configs/lint/typescript-vue - typescript-eslint) Extended config from plugin (ESLint rules with type checking)' }, { - ...regexp.configs['flat/recommended'], + ...regexp.configs[recommendedKey], name: '(@jellyfin-vue/configs/lint/typescript-vue - regexp) Extended config from plugin' }, { - ...promise.configs['flat/recommended'], + ...promise.configs[recommendedKey], name: '(@jellyfin-vue/configs/lint/typescript-vue - promise) Extended config from plugin' }, { @@ -77,10 +79,26 @@ const common = [ 'jsdoc/informative-docs': 'error' } }, + /** + * TODO: Re-enable this at some point when the type checking is improved. + * These rules are annoying when using not well-supported TypeScript libraries + * and imported SFC files are not recognised properly and needs the use of: + * https://github.com/ota-meshi/typescript-eslint-parser-for-extra-files + */ + { + name: '(@jellyfin-vue/configs/lint/typescript-vue - TypeScript & Vue) Disable no-unsafe-* rules', + rules: { + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-return': 'off' + } + }, { name: '(@jellyfin-vue/configs/lint/typescript-vue - TypeScript & Vue) Custom config', rules: { - '@typescript-eslint/no-redundant-type-constituents': 'off', + // '@typescript-eslint/no-redundant-type-constituents': 'off', '@typescript-eslint/restrict-template-expressions': 'off', '@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-dynamic-delete': 'off', @@ -116,7 +134,7 @@ const common = [ /** Vue SFC only rules */ const vue_config = [ - ...vue.configs['flat/recommended'].map((config) => { + ...vue.configs[recommendedKey].map((config) => { /** * Specified above, unnecessary to overwrite */ @@ -131,12 +149,12 @@ const vue_config = [ return config; }), { - ...flatArrayOfObjects(vueScopedCSS.configs['flat/recommended']), + ...flatArrayOfObjects(vueScopedCSS.configs[recommendedKey]), name: '(@jellyfin-vue/configs/lint/typescript-vue - Vue Scoped CSS) Extended config from plugin', files: vueFiles }, { - ...css.configs['flat/recommended'], + ...css.configs[recommendedKey], name: '(@jellyfin-vue/configs/lint/typescript-vue - Vue CSS) Extended config from plugin', files: vueFiles },