Merge pull request #7248 from thornbill/card-album-artists

Fix multiple album artists in card footer
This commit is contained in:
Bill Thornton
2025-10-23 08:10:03 -04:00
committed by GitHub
5 changed files with 66 additions and 34 deletions

View File

@@ -1,5 +1,8 @@
import React, { type FC } from 'react';
import Box from '@mui/material/Box';
import { ensureArray } from 'utils/array';
import type { TextLine } from './cardHelper';
interface CardTextProps {
@@ -7,27 +10,33 @@ interface CardTextProps {
textLine: TextLine;
}
const SEPARATOR = ' / ';
const CardText: FC<CardTextProps> = ({ className, textLine }) => {
const { title, titleAction } = textLine;
// eslint-disable-next-line sonarjs/function-return-type
const renderCardText = () => {
if (titleAction) {
return (
<a
className='itemAction textActionButton'
href={titleAction.url}
title={titleAction.title}
{...titleAction.dataAttributes}
>
{titleAction.title}
</a>
);
} else {
return title;
}
};
return <Box className={className}>{renderCardText()}</Box>;
return (
<Box className={className}>
{titleAction ? (
ensureArray(titleAction).map((action, i, arr) => (
<>
<a
className='itemAction textActionButton'
href={action.url}
title={action.title}
{...action.dataAttributes}
>
{action.title}
</a>
{/* If there are more items, add the separator */}
{(i < arr.length - 1) && SEPARATOR}
</>
))
) : (
ensureArray(title).join(SEPARATOR)
)}
</Box>
);
};
export default CardText;

View File

@@ -1,4 +1,5 @@
import { Api } from '@jellyfin/sdk';
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
import type { BaseItemPerson } from '@jellyfin/sdk/lib/generated-client/models/base-item-person';
import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type';
import { getImageApi } from '@jellyfin/sdk/lib/utils/api/image-api';
@@ -12,6 +13,7 @@ import { isUsingLiveTvNaming } from '../cardBuilderUtils';
import { getDataAttributes } from 'utils/items';
import { ItemKind } from 'types/base/models/item-kind';
import { ItemMediaKind } from 'types/base/models/item-media-kind';
import { ensureArray } from 'utils/array';
import type { NullableNumber, NullableString } from 'types/base/common/shared/types';
import type { ItemDto } from 'types/base/models/item-dto';
@@ -65,8 +67,8 @@ interface TextAction {
}
export interface TextLine {
title?: NullableString;
titleAction?: TextAction;
title?: NullableString | string[];
titleAction?: TextAction | TextAction[];
}
export function getTextActionButton(
@@ -210,9 +212,25 @@ function getParentTitle(
item: ItemDto
) {
if (isOuterFooter && item.AlbumArtists?.length) {
(item.AlbumArtists[0] as ItemDto).Type = ItemKind.MusicArtist;
(item.AlbumArtists[0] as ItemDto).IsFolder = true;
return getTextActionButton(item.AlbumArtists[0], null, serverId);
return item.AlbumArtists
.map(artist => {
const artistItem: ItemDto = {
...artist,
Type: BaseItemKind.MusicArtist,
IsFolder: true
};
return getTextActionButton(artistItem, null, serverId);
})
.reduce((acc, line) => ({
title: [
...ensureArray(acc.title),
...ensureArray(line.title)
],
titleAction: [
...ensureArray(acc.titleAction),
...ensureArray(line.titleAction)
]
}), {});
} else {
return {
title: isUsingLiveTvNaming(item.Type) ?

View File

@@ -575,9 +575,14 @@ function getCardFooterText(item, apiClient, options, footerClass, progressHtml,
if (showOtherText) {
if (options.showParentTitle && parentTitleUnderneath) {
if (flags.isOuterFooter && item.AlbumArtists?.length) {
item.AlbumArtists[0].Type = 'MusicArtist';
item.AlbumArtists[0].IsFolder = true;
lines.push(getTextActionButton(item.AlbumArtists, null, serverId));
const artistText = item.AlbumArtists
.map(artist => {
artist.Type = BaseItemKind.MusicArtist;
artist.IsFolder = true;
return getTextActionButton(artist, null, serverId);
})
.join(' / ');
lines.push(artistText);
} else {
lines.push(escapeHtml(isUsingLiveTvNaming(item.Type) ? item.Name : (item.SeriesName || item.Series || item.Album || item.AlbumArtist || '')));
}

View File

@@ -52,14 +52,6 @@ export function getDisplayName(item, options = {}) {
}
}
if (Array.isArray(item)) {
if (item.length > 1) {
return item.map(i => getDisplayName(i, options)).join(' / ');
} else if (item.length === 1) {
return item[0].Name;
}
}
return name;
}

8
src/utils/array.ts Normal file
View File

@@ -0,0 +1,8 @@
/**
* Utility function that converts a value that can be a single item, array of items, null, or undefined to an array.
*/
export function ensureArray<T>(val: T | T[] | null | undefined): T[] {
if (val == null) return [];
if (Array.isArray(val)) return val;
return [ val ];
}