diff --git a/src/components/cardbuilder/cardBuilder.js b/src/components/cardbuilder/cardBuilder.js index 352b38a6a1..6d0c73afe0 100644 --- a/src/components/cardbuilder/cardBuilder.js +++ b/src/components/cardbuilder/cardBuilder.js @@ -4,6 +4,7 @@ * @module components/cardBuilder/cardBuilder */ +import { PersonKind } from '@jellyfin/sdk/lib/generated-client/models/person-kind'; import escapeHtml from 'escape-html'; import browser from 'scripts/browser'; @@ -698,8 +699,26 @@ function getCardFooterText(item, apiClient, options, footerClass, progressHtml, } } - if (options.showPersonRoleOrType && item.Role) { - lines.push(globalize.translate('PersonRole', escapeHtml(item.Role))); + if (options.showPersonRoleOrType && item.Type) { + if (item.Role) { + if ([ PersonKind.Actor, PersonKind.GuestStar ].includes(item.Type)) { + // List actor roles formatted like "as Character Name" + lines.push(globalize.translate('PersonRole', escapeHtml(item.Role))); + } else if (item.Role.toLowerCase() === item.Type.toLowerCase()) { + // Role and Type are the same so use the localized Type + lines.push(escapeHtml(globalize.translate(item.Type))); + } else if (item.Role.toLowerCase().includes(item.Type.toLowerCase())) { + // Avoid duplication if the Role includes the Type (i.e. Executive Producer) + lines.push(escapeHtml(item.Role)); + } else { + // Type and Role are unique so list both (i.e. Writer | Novel) + lines.push(escapeHtml(globalize.translate(item.Type))); + lines.push(escapeHtml(item.Role)); + } + } else { + // No Role so use the localized Type + lines.push(escapeHtml(globalize.translate(item.Type))); + } } } diff --git a/src/controllers/itemDetails/index.js b/src/controllers/itemDetails/index.js index f5cf971eba..71aebc57a4 100644 --- a/src/controllers/itemDetails/index.js +++ b/src/controllers/itemDetails/index.js @@ -1,3 +1,4 @@ +import { PersonKind } from '@jellyfin/sdk/lib/generated-client/models/person-kind'; import { intervalToDuration } from 'date-fns'; import DOMPurify from 'dompurify'; import escapeHtml from 'escape-html'; @@ -822,8 +823,18 @@ function setInitialCollapsibleState(page, item, apiClient, context, user) { page.querySelector('#specialsCollapsible').classList.add('hide'); } - renderCast(page, item); - renderGuestCast(page, item); + const cast = []; + const guestCast = []; + (item.People || []).forEach(p => { + if (p.Type === PersonKind.GuestStar) { + guestCast.push(p); + } else { + cast.push(p); + } + }); + + renderCast(page, item, cast); + renderGuestCast(page, item, guestCast); if (item.PartCount && item.PartCount > 1) { page.querySelector('#additionalPartsCollapsible').classList.remove('hide'); @@ -1803,11 +1814,7 @@ function renderSpecials(page, item, user) { }); } -function renderCast(page, item) { - const people = (item.People || []).filter(function (p) { - return p.Type === 'Actor'; - }); - +function renderCast(page, item, people) { if (!people.length) { page.querySelector('#castCollapsible').classList.add('hide'); return; @@ -1827,9 +1834,7 @@ function renderCast(page, item) { }); } -function renderGuestCast(page, item) { - const people = (item.People || []).filter(p => p.Type === 'GuestStar'); - +function renderGuestCast(page, item, people) { if (!people.length) { page.querySelector('#guestCastCollapsible').classList.add('hide'); return;