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
},