diff --git a/CHANGELOG b/CHANGELOG
index 3f37553be..de007ea37 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -16,7 +16,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Updated
-- Display non-aggregated filter values [SCC-5125](https://newyorkpubliclibrary.atlassian.net/browse/SCC-5125)
+- Displayed non-aggregated filter values [SCC-5125](https://newyorkpubliclibrary.atlassian.net/browse/SCC-5125)
+- Updated series links in bib details [SCC-5195](https://newyorkpubliclibrary.atlassian.net/browse/SCC-5195)
## [1.8.2] 2026-04-09
diff --git a/__test__/fixtures/bibFixtures.ts b/__test__/fixtures/bibFixtures.ts
index 7ee8dc1fb..752237925 100644
--- a/__test__/fixtures/bibFixtures.ts
+++ b/__test__/fixtures/bibFixtures.ts
@@ -1231,6 +1231,677 @@ export const bibWithItems = {
},
}
+export const bibWithSeries = {
+ resource: {
+ "@context":
+ "http://discovery-api-qa.nypl.org/api/v0.1/discovery/context_all.jsonld",
+ "@type": ["nypl:Item", "nypl:Resource"],
+ "@id": "res:b16470373",
+ buildingLocationIds: ["rc"],
+ carrierType: [
+ {
+ "@id": "carriertypes:nc",
+ prefLabel: "volume",
+ },
+ ],
+ contributorLiteral: ["Spada, Pietro, 1935-"],
+ contributorsDisplay: [
+ {
+ display: "Spada, Pietro, 1935-, editor",
+ "@value": "Spada, Pietro, 1935-",
+ },
+ ],
+ createdString: ["2006"],
+ createdYear: 2006,
+ creatorLiteral: ["Rossini, Gioacchino, 1792-1868"],
+ creatorsDisplay: [
+ {
+ display: "Rossini, Gioacchino, 1792-1868",
+ "@value": "Rossini, Gioacchino, 1792-1868",
+ },
+ ],
+ dateStartYear: 2006,
+ dateString: ["2006"],
+ dates: [
+ {
+ range: {
+ lt: "2007",
+ gte: "2006",
+ },
+ raw: "061108s2006 it uua i n lat cccm4a ",
+ tag: "s",
+ },
+ ],
+ dimensions: ["31 cm."],
+ extent: ["1 score ([ii], 18 p.) ;"],
+ idOclc: ["75399987"],
+ identifier: [
+ {
+ "@type": "bf:ShelfMark",
+ "@value": "JMG 06-1852",
+ },
+ {
+ "@type": "nypl:Bnumber",
+ "@value": "16470373",
+ },
+ {
+ "@type": "nypl:Oclc",
+ "@value": "75399987",
+ },
+ {
+ "@type": "bf:Identifier",
+ "@value": "BS. 1607 Boccaccini & Spada",
+ },
+ {
+ "@type": "bf:Identifier",
+ "@value": "(OCoLC)72521271",
+ },
+ {
+ "@type": "bf:Identifier",
+ "@value": "(OCoLC)75399987",
+ },
+ {
+ "@type": "bf:Identifier",
+ "@value": "(WaOLN)M060000006",
+ },
+ ],
+ issuance: [
+ {
+ "@id": "urn:biblevel:m",
+ prefLabel: "monograph/item",
+ },
+ ],
+ itemAggregations: [
+ {
+ "@type": "nypl:Aggregation",
+ "@id": "res:location",
+ id: "location",
+ field: "location",
+ values: [
+ {
+ value: "loc:rcpm2",
+ count: 1,
+ label: "Offsite",
+ },
+ ],
+ },
+ {
+ "@type": "nypl:Aggregation",
+ "@id": "res:format",
+ id: "format",
+ field: "format",
+ values: [
+ {
+ value: "Notated music",
+ count: 1,
+ label: "Notated music",
+ },
+ ],
+ },
+ {
+ "@type": "nypl:Aggregation",
+ "@id": "res:status",
+ id: "status",
+ field: "status",
+ values: [
+ {
+ value: "status:a",
+ count: 1,
+ label: "Available",
+ },
+ ],
+ },
+ ],
+ items: [
+ {
+ "@id": "res:i17270445",
+ "@type": ["bf:Item"],
+ accessMessage: [
+ {
+ "@id": "accessMessage:2",
+ prefLabel: "Request in advance",
+ },
+ ],
+ catalogItemType: [
+ {
+ "@id": "catalogItemType:57",
+ prefLabel: "printed music limited circ MaRLI",
+ },
+ ],
+ eddFulfillment: {
+ "@id": "fulfillment:recap-edd",
+ },
+ eddRequestable: true,
+ formatLiteral: ["Notated music"],
+ holdingLocation: [
+ {
+ "@id": "loc:rcpm2",
+ prefLabel: "Offsite",
+ },
+ ],
+ idBarcode: ["33433073832424"],
+ identifier: [
+ {
+ "@type": "bf:ShelfMark",
+ "@value": "JMG 06-1852",
+ },
+ {
+ "@type": "bf:Barcode",
+ "@value": "33433073832424",
+ },
+ ],
+ owner: [
+ {
+ "@id": "orgs:1002",
+ prefLabel:
+ "New York Public Library for the Performing Arts, Dorothy and Lewis B. Cullman Center",
+ },
+ ],
+ physFulfillment: {
+ "@id": "fulfillment:recap-offsite",
+ },
+ physRequestable: true,
+ physicalLocation: ["JMG 06-1852"],
+ recapCustomerCode: ["NP"],
+ requestable: [true],
+ shelfMark: ["JMG 06-1852"],
+ specRequestable: false,
+ status: [
+ {
+ "@id": "status:a",
+ prefLabel: "Available",
+ },
+ ],
+ uri: "i17270445",
+ idNyplSourceId: {
+ "@type": "SierraNypl",
+ "@value": "17270445",
+ },
+ },
+ ],
+ language: [
+ {
+ "@id": "lang:lat",
+ prefLabel: "Latin",
+ },
+ ],
+ lccClassification: ["M2018.R83 C57"],
+ materialType: [
+ {
+ "@id": "resourcetypes:not",
+ prefLabel: "Notated music",
+ },
+ ],
+ mediaType: [
+ {
+ "@id": "mediatypes:n",
+ prefLabel: "unmediated",
+ },
+ ],
+ note: [
+ {
+ noteType: "Note",
+ "@type": "bf:Note",
+ prefLabel: "For vocal soloists (TTB) with orchestra.",
+ },
+ {
+ noteType: "Note",
+ "@type": "bf:Note",
+ prefLabel:
+ "Preface in Italian with English, French, and German translations.",
+ },
+ {
+ noteType: "Language",
+ "@type": "bf:Note",
+ prefLabel: "Latin words.",
+ },
+ ],
+ numCheckinCardItems: 0,
+ numElectronicResources: 0,
+ numItemDatesParsed: 0,
+ numItemVolumesParsed: 0,
+ numItemsMatched: 1,
+ numItemsTotal: 1,
+ nyplSource: ["sierra-nypl"],
+ parallelContributorLiteral: [null],
+ parallelContributorsDisplay: [],
+ parallelCreatorLiteral: [null],
+ parallelCreatorsDisplay: [],
+ parallelSeriesAddedEntry: [],
+ parallelSeriesAddedEntryDisplay: [],
+ parallelSubjectLiteral: [],
+ physicalDescription: ["1 score ([ii], 18 p.) ; 31 cm."],
+ placeOfPublication: ["Pavona di Albano Laziale, Roma"],
+ popularity: 1,
+ publicationStatement: [
+ "Pavona di Albano Laziale, Roma : Boccaccini & Spada, c2006.",
+ ],
+ publisherLiteral: ["Boccaccini & Spada"],
+ series: ["Inediti e rarità rossiniane ;"],
+ seriesDisplay: [
+ {
+ display: "Inediti e rarità rossiniane ; 12",
+ "@value": "Inediti e rarità rossiniane ;",
+ },
+ ],
+ seriesUniformTitle: [
+ "Rossini, Gioacchino 1792-1868. Works. Selections (Boccaccini & Spada editore) ;",
+ ],
+ seriesUniformTitleDisplay: [
+ {
+ display:
+ "Rossini, Gioacchino 1792-1868. Works. Selections (Boccaccini & Spada editore) ; 12.",
+ "@value":
+ "Rossini, Gioacchino 1792-1868. Works. Selections (Boccaccini & Spada editore) ;",
+ },
+ ],
+ shelfMark: ["JMG 06-1852"],
+ subjectLiteral: ["Sacred vocal trios with orchestra -- Scores."],
+ title: ["Christe : per 2 tenori, basso ed orchestra"],
+ titleDisplay: [
+ "Christe : per 2 tenori, basso ed orchestra / Gioacchino Rossini ; a cura di Pietro Spada.",
+ ],
+ type: ["nypl:Item"],
+ updatedAt: 1776009782717,
+ uri: "b16470373",
+ updatedAtDate: "2026-04-12T16:03:02.717Z",
+ hasItemVolumes: false,
+ hasItemDates: false,
+ collection: [
+ {
+ "@id": "pam",
+ prefLabel: "Music Division",
+ buildingLocationLabel:
+ "The New York Public Library for the Performing Arts (LPA)",
+ locationsPath: "locations/lpa/music-division",
+ },
+ ],
+ format: [
+ {
+ "@id": "c",
+ prefLabel: "Notated music",
+ },
+ ],
+ electronicResources: [],
+ },
+ annotatedMarc: {
+ id: "16470373",
+ nyplSource: "sierra-nypl",
+ fields: [
+ {
+ label: "Author",
+ values: [
+ {
+ content: "Rossini, Gioacchino, 1792-1868.",
+ source: {
+ ind1: "1",
+ ind2: " ",
+ content: null,
+ marcTag: "100",
+ fieldTag: "a",
+ subfields: [
+ {
+ tag: "a",
+ content: "Rossini, Gioacchino,",
+ },
+ {
+ tag: "d",
+ content: "1792-1868.",
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ label: "Title",
+ values: [
+ {
+ content:
+ "Christe : per 2 tenori, basso ed orchestra / Gioacchino Rossini ; a cura di Pietro Spada.",
+ source: {
+ ind1: "1",
+ ind2: "0",
+ content: null,
+ marcTag: "245",
+ fieldTag: "t",
+ subfields: [
+ {
+ tag: "a",
+ content: "Christe :",
+ },
+ {
+ tag: "b",
+ content: "per 2 tenori, basso ed orchestra /",
+ },
+ {
+ tag: "c",
+ content: "Gioacchino Rossini ; a cura di Pietro Spada.",
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ label: "Imprint",
+ values: [
+ {
+ content:
+ "Pavona di Albano Laziale, Roma : Boccaccini & Spada, c2006.",
+ source: {
+ ind1: " ",
+ ind2: " ",
+ content: null,
+ marcTag: "260",
+ fieldTag: "p",
+ subfields: [
+ {
+ tag: "a",
+ content: "Pavona di Albano Laziale, Roma :",
+ },
+ {
+ tag: "b",
+ content: "Boccaccini & Spada,",
+ },
+ {
+ tag: "c",
+ content: "c2006.",
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ label: "Format",
+ values: [
+ {
+ content: "Partitura.",
+ source: {
+ ind1: " ",
+ ind2: " ",
+ content: null,
+ marcTag: "254",
+ fieldTag: "e",
+ subfields: [
+ {
+ tag: "a",
+ content: "Partitura.",
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ label: "Description",
+ values: [
+ {
+ content: "1 score ([ii], 18 p.) ; 31 cm.",
+ source: {
+ ind1: " ",
+ ind2: " ",
+ content: null,
+ marcTag: "300",
+ fieldTag: "r",
+ subfields: [
+ {
+ tag: "a",
+ content: "1 score ([ii], 18 p.) ;",
+ },
+ {
+ tag: "c",
+ content: "31 cm.",
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ label: "Playing Time",
+ values: [
+ {
+ content: "000700",
+ source: {
+ ind1: " ",
+ ind2: " ",
+ content: null,
+ marcTag: "306",
+ fieldTag: "r",
+ subfields: [
+ {
+ tag: "a",
+ content: "000700",
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ label: "Series",
+ values: [
+ {
+ content: "Inediti e rarità rossiniane ; 12",
+ source: {
+ ind1: "1",
+ ind2: " ",
+ content: null,
+ marcTag: "490",
+ fieldTag: "s",
+ subfields: [
+ {
+ tag: "a",
+ content: "Inediti e rarità rossiniane ;",
+ },
+ {
+ tag: "v",
+ content: "12",
+ },
+ ],
+ },
+ },
+ {
+ content:
+ "Rossini, Gioacchino 1792-1868. Works. Selections (Boccaccini & Spada editore) ; 12.",
+ source: {
+ ind1: " ",
+ ind2: "0",
+ content: null,
+ marcTag: "830",
+ fieldTag: "s",
+ subfields: [
+ {
+ tag: "a",
+ content: "Rossini, Gioacchino",
+ },
+ {
+ tag: "d",
+ content: "1792-1868.",
+ },
+ {
+ tag: "t",
+ content: "Works.",
+ },
+ {
+ tag: "k",
+ content: "Selections (Boccaccini & Spada editore) ;",
+ },
+ {
+ tag: "v",
+ content: "12.",
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ label: "Note",
+ values: [
+ {
+ content: "For vocal soloists (TTB) with orchestra.",
+ source: {
+ ind1: " ",
+ ind2: " ",
+ content: null,
+ marcTag: "500",
+ fieldTag: "n",
+ subfields: [
+ {
+ tag: "a",
+ content: "For vocal soloists (TTB) with orchestra.",
+ },
+ ],
+ },
+ },
+ {
+ content:
+ "Preface in Italian with English, French, and German translations.",
+ source: {
+ ind1: " ",
+ ind2: " ",
+ content: null,
+ marcTag: "500",
+ fieldTag: "n",
+ subfields: [
+ {
+ tag: "a",
+ content:
+ "Preface in Italian with English, French, and German translations.",
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ label: "Language",
+ values: [
+ {
+ content: "Latin words.",
+ source: {
+ ind1: " ",
+ ind2: " ",
+ content: null,
+ marcTag: "546",
+ fieldTag: "n",
+ subfields: [
+ {
+ tag: "a",
+ content: "Latin words.",
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ label: "Subject",
+ values: [
+ {
+ content: "Sacred vocal trios with orchestra -- Scores.",
+ source: {
+ ind1: " ",
+ ind2: "0",
+ content: null,
+ marcTag: "650",
+ fieldTag: "d",
+ subfields: [
+ {
+ tag: "a",
+ content: "Sacred vocal trios with orchestra",
+ },
+ {
+ tag: "v",
+ content: "Scores.",
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ label: "Added Author",
+ values: [
+ {
+ content: "Spada, Pietro, 1935- Editor",
+ source: {
+ ind1: "1",
+ ind2: " ",
+ content: null,
+ marcTag: "700",
+ fieldTag: "b",
+ subfields: [
+ {
+ tag: "a",
+ content: "Spada, Pietro,",
+ },
+ {
+ tag: "d",
+ content: "1935-",
+ },
+ {
+ tag: "4",
+ content: "edt",
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ label: "Publisher No.",
+ values: [
+ {
+ content: "BS. 1607 Boccaccini & Spada",
+ source: {
+ ind1: "2",
+ ind2: "2",
+ content: null,
+ marcTag: "028",
+ fieldTag: "l",
+ subfields: [
+ {
+ tag: "a",
+ content: "BS. 1607",
+ },
+ {
+ tag: "b",
+ content: "Boccaccini & Spada",
+ },
+ ],
+ },
+ },
+ ],
+ },
+ {
+ label: "Research Call Number",
+ values: [
+ {
+ content: "JMG 06-1852",
+ source: {
+ ind1: "8",
+ ind2: " ",
+ content: null,
+ marcTag: "852",
+ fieldTag: "q",
+ subfields: [
+ {
+ tag: "h",
+ content: "JMG 06-1852",
+ },
+ ],
+ },
+ },
+ ],
+ },
+ ],
+ },
+}
+
export const bibNoItems = {
resource: {
"@context":
@@ -8971,7 +9642,7 @@ export const noParallels = {
publicationStatement: ["[Paris, France] : Gallimard, c2005."],
publisherLiteral: ["Gallimard,"],
series: ["Childhood"],
- seriesStatement: ["Haute enfance"],
+ seriesDisplay: [{ display: "Childhood", "@value": "Childhood" }],
shelfMark: ["JFC 06-438"],
subjectLiteral: [
"Authors, French -- 20th century -- Biography.",
@@ -12771,7 +13442,6 @@ export const bibWithSubjectHeadings = {
popularity: 1,
publicationStatement: ["[Paris, France] : Gallimard, c2005."],
publisherLiteral: ["Gallimard"],
- seriesStatement: ["Haute enfance"],
shelfMark: ["JFC 06-438"],
subjectLiteral: [
"Cortanze, Gérard de -- Childhood and youth.",
diff --git a/src/components/BibPage/BibDetail.test.tsx b/src/components/BibPage/BibDetail.test.tsx
index 190357f08..218a55e41 100644
--- a/src/components/BibPage/BibDetail.test.tsx
+++ b/src/components/BibPage/BibDetail.test.tsx
@@ -38,7 +38,6 @@ describe("BibDetail component", () => {
expect(screen.getByText("French")).toBeInTheDocument()
expect(screen.getByText("Series")).toBeInTheDocument()
expect(screen.queryAllByText("Childhood")[0]).toBeInTheDocument()
- expect(screen.getByText("Series statement")).toBeInTheDocument()
expect(screen.queryAllByText(/Haute enfance/)[0]).toBeInTheDocument()
})
it("merges annotated MARC and resource fields without label duplicates", () => {
@@ -175,8 +174,8 @@ describe("BibDetail component", () => {
render(, {
wrapper: MemoryRouterProvider,
})
- const seriesStatement = screen.getByText("Childhood")
- expect(seriesStatement).toHaveAttribute(
+ const series = screen.getByText("Childhood")
+ expect(series).toHaveAttribute(
"href",
expect.stringContaining("/search?filters[series][0]=Childhood")
)
diff --git a/src/components/BibPage/BibDetail.tsx b/src/components/BibPage/BibDetail.tsx
index ffc4535db..f9d262bff 100644
--- a/src/components/BibPage/BibDetail.tsx
+++ b/src/components/BibPage/BibDetail.tsx
@@ -105,13 +105,13 @@ export const BrowseLinkDetailElement = ({
url: `/browse${
browseType === "subjects" ? "" : "/authors/"
}?q=${encodeURIComponentWithPeriods(
- urlInfo.urlLabel
+ urlInfo.urlText
)}&search_scope=starts_with`,
- urlLabel: `[${indexLinkLabel}]`,
+ urlText: `[${indexLinkLabel}]`,
},
"internal",
true,
- `${indexLinkLabel} for "${urlInfo.urlLabel}"`
+ `${indexLinkLabel} for "${urlInfo.urlText}"`
)}
>
@@ -125,10 +125,12 @@ const LinkElement = (
isBold = false,
ariaLabel?: string
) => {
- return (
- <>
+ const { text, urlText, url: href } = url
+
+ if (!text) {
+ return (
- {url.urlLabel}
+ {url.urlText}
- {url.text && {url.text}}
+ )
+ }
+
+ const parts = text.split(urlText)
+
+ return (
+ <>
+ {parts.map((part, index) => (
+
+ {part}
+ {index < parts.length - 1 && (
+
+ {urlText}
+
+ )}
+
+ ))}
>
)
}
diff --git a/src/components/HoldPages/HoldRequestItemDetails.tsx b/src/components/HoldPages/HoldRequestItemDetails.tsx
index 7463e1b50..1d7c9faaf 100644
--- a/src/components/HoldPages/HoldRequestItemDetails.tsx
+++ b/src/components/HoldPages/HoldRequestItemDetails.tsx
@@ -25,7 +25,7 @@ const HoldRequestItemDetails = ({ item }: HoldRequestItemDetailsProps) => {
>
{LinkedDetailElement({
label: "Title",
- value: [{ url: `${PATHS.BIB}/${item.bibId}`, urlLabel: item.bibTitle }],
+ value: [{ url: `${PATHS.BIB}/${item.bibId}`, urlText: item.bibTitle }],
link: "internal",
})}
diff --git a/src/models/BibDetails.ts b/src/models/BibDetails.ts
index 476ba9af0..b65e838e7 100644
--- a/src/models/BibDetails.ts
+++ b/src/models/BibDetails.ts
@@ -7,7 +7,7 @@ import type {
AnyBibDetail,
MarcLinkedDetail,
AnyMarcDetail,
- ContributorEntry,
+ DisplayPackedEntry,
} from "../types/bibDetailsTypes"
import {
convertToSentenceCase,
@@ -78,39 +78,87 @@ export default class BibDetails {
? "creatorsDisplay"
: "contributorsDisplay"
- const mapDisplay = (arr?: ContributorEntry[]): BibDetailURL[] =>
+ const mapDisplay = (arr?: DisplayPackedEntry[]): BibDetailURL[] =>
Array.isArray(arr)
? arr.map(({ display, "@value": name }) => {
- const [, roles] = display.split(name)
return {
url: getContributorSearchURL(name),
- urlLabel: name,
- text: roles?.trim() || undefined,
+ urlText: name,
+ text: display,
}
})
: []
const displayValues = mapDisplay(this.bib[displayField])
- // Set of displayed names for deduping
- const seen = new Set(displayValues.map((v) => v.urlLabel))
-
// Literals (fallback)
const literalValues: string[] = this.bib[literalField] || []
- const literals: BibDetailURL[] = literalValues
- .filter((name) => !seen.has(name))
- .map((name) => ({
- url: getContributorSearchURL(name),
- urlLabel: name,
- }))
+ const literals: BibDetailURL[] = literalValues.map((name) => ({
+ url: getContributorSearchURL(name),
+ urlText: name,
+ }))
+
+ return displayValues?.length > 0
+ ? {
+ label,
+ link: "internal",
+ value: displayValues,
+ }
+ : literals?.length > 0
+ ? {
+ label,
+ link: "internal",
+ value: literals,
+ }
+ : null
+ }
+
+ buildLinkedSeriesDetail(literalField): LinkedBibDetail | null {
+ let displayField = "seriesDisplay"
+ let label = "Series"
+ switch (literalField) {
+ case "seriesAddedEntry":
+ displayField = "seriesAddedEntryDisplay"
+ label = "Series added entry"
+ break
+ case "seriesUniformTitle":
+ displayField = "seriesUniformTitleDisplay"
+ label = "Series uniform title"
+ break
+ }
+
+ const getSeriesSearchUrl = (name: string) =>
+ `/search?filters[series][0]=${encodeURIComponentWithPeriods(name)}`
- const combinedValues = [...displayValues, ...literals]
+ const displayData: DisplayPackedEntry[] = this.bib[displayField] || []
+ const displayValues: BibDetailURL[] = displayData.map(
+ ({ display, "@value": name }) => {
+ return {
+ url: getSeriesSearchUrl(name),
+ urlText: name,
+ text: display,
+ }
+ }
+ )
+
+ // Literals (fallback)
+ const literalValues: string[] = this.bib[literalField] || []
+ const literals: BibDetailURL[] = literalValues.map((name) => ({
+ url: getSeriesSearchUrl(name),
+ urlText: name,
+ }))
- return combinedValues?.length > 0
+ return displayValues?.length > 0
? {
label,
link: "internal",
- value: combinedValues,
+ value: displayValues,
+ }
+ : literals?.length > 0
+ ? {
+ label,
+ link: "internal",
+ value: literals,
}
: null
}
@@ -124,7 +172,7 @@ export default class BibDetails {
if (label === "Connect to:") {
const urlValues = values.map(({ label, content }) => ({
url: content,
- urlLabel: label,
+ urlText: label,
}))
const detail = this.buildExternalLinkedDetail(
"Connect to:",
@@ -212,6 +260,8 @@ export default class BibDetails {
"addedAuthorTitle",
"placeOfPublication",
"series",
+ "seriesAddedEntry",
+ "seriesUniformTitle",
"uniformTitle",
"subjectLiteral",
"titleAlt",
@@ -228,7 +278,8 @@ export default class BibDetails {
{ field: "summary", label: "Summary" },
{ field: "donor", label: "Donor/sponsor" },
{ field: "series", label: "Series" },
- { field: "seriesStatement", label: "Series statement" },
+ { field: "seriesAddedEntry", label: "Series added entry" },
+ { field: "seriesUniformTitle", label: "Series uniform title" },
{ field: "uniformTitle", label: "Uniform title" },
{ field: "titleAlt", label: "Alternative title" },
{ field: "formerTitle", label: "Former title" },
@@ -259,10 +310,38 @@ export default class BibDetails {
)
}
+ // Series uniform title values display under Series added entry
+ combineSeriesAddedEntries(resourceEndpointDetails: AnyBibDetail[]) {
+ const addedEntry = resourceEndpointDetails.find(
+ (d) => d.label === "Series added entry"
+ ) as LinkedBibDetail
+ const uniformTitle = resourceEndpointDetails.find(
+ (d) => d.label === "Series uniform title"
+ ) as LinkedBibDetail
+
+ if (uniformTitle) {
+ if (addedEntry) {
+ addedEntry.value = [...addedEntry.value, ...uniformTitle.value]
+ return resourceEndpointDetails.filter(
+ (d) => d.label !== "Series uniform title"
+ )
+ }
+ uniformTitle.label = "Series added entry"
+ }
+
+ return resourceEndpointDetails
+ }
+
combineBibDetailsData(
resourceEndpointDetails: AnyBibDetail[],
annotatedMarcDetails: AnyMarcDetail[]
): AnyBibDetail[] {
+ // Merge Series added entry and Series uniform title fields
+ resourceEndpointDetails = this.combineSeriesAddedEntries(
+ resourceEndpointDetails
+ )
+
+ // Normalize and merge bib and annotated marc fields
const normalizeValues = (val: any) => {
if (!val) return []
if (Array.isArray(val)) {
@@ -271,12 +350,12 @@ export default class BibDetails {
.map((v) =>
typeof v === "string"
? v.trim()
- : v?.content?.trim() || v?.urlLabel?.trim()
+ : v?.content?.trim() || v?.urlText?.trim()
)
}
if (typeof val === "string") return [val.trim()]
if (val?.content) return [val.content.trim()]
- return [val?.urlLabel?.trim()]
+ return [val?.urlText?.trim()]
}
const labelsSet = new Set(resourceEndpointDetails.map((d) => d.label))
@@ -358,14 +437,23 @@ export default class BibDetails {
label: string
field: string
}): LinkedBibDetail {
- const value = this.bib[fieldMapping.field]
- if (!value?.length) return null
if (fieldMapping.field === "contributorLiteral") {
return this.buildLinkedContributorDetail(
"contributorLiteral",
"Additional authors"
)
}
+ if (
+ fieldMapping.field === "seriesAddedEntry" ||
+ fieldMapping.field === "series" ||
+ fieldMapping.field === "seriesUniformTitle"
+ ) {
+ return this.buildLinkedSeriesDetail(fieldMapping.field)
+ }
+
+ const value = this.bib[fieldMapping.field]
+ if (!value?.length) return null
+
return {
link: "internal",
label: convertToSentenceCase(fieldMapping.label),
@@ -381,7 +469,7 @@ export default class BibDetails {
v
)}`
}
- return { url: internalUrl, urlLabel: v }
+ return { url: internalUrl, urlText: v }
}),
}
}
@@ -513,7 +601,7 @@ export default class BibDetails {
})
.map((sc) => ({
url: sc.url,
- urlLabel: sc.label,
+ urlText: sc.label,
}))
return this.buildExternalLinkedDetail(convertToSentenceCase(label), values)
}
diff --git a/src/models/modelTests/BibDetails.test.ts b/src/models/modelTests/BibDetails.test.ts
index 0f2c6bfbb..08d33a21e 100644
--- a/src/models/modelTests/BibDetails.test.ts
+++ b/src/models/modelTests/BibDetails.test.ts
@@ -9,6 +9,7 @@ import {
princetonRecord,
bibWithItems,
physicalDescriptionBib,
+ bibWithSeries,
} from "../../../__test__/fixtures/bibFixtures"
import type { LinkedBibDetail } from "../../types/bibDetailsTypes"
import BibDetailsModel from "../BibDetails"
@@ -45,6 +46,12 @@ describe("Bib Details model", () => {
bibWithSubjectHeadings.resource,
bibWithSubjectHeadings.annotatedMarc
)
+
+ const bibWithSeriesModel = new BibDetailsModel(
+ bibWithSeries.resource,
+ bibWithSeries.annotatedMarc
+ )
+
describe("owner", () => {
it("populates owner when owner is present", () => {
const partnerBib = new BibDetailsModel(princetonRecord)
@@ -157,7 +164,7 @@ describe("Bib Details model", () => {
label: "Supplementary content",
value: [
{
- urlLabel: "Image",
+ urlText: "Image",
url: "http://images.contentreserve.com/ImageType-100/0293-1/{C87D2BB9-0E13-4851-A9E2-547643F41A0E}Img100.jpg",
},
],
@@ -170,7 +177,7 @@ describe("Bib Details model", () => {
label: "Supplementary content",
value: [
{
- urlLabel: "Image",
+ urlText: "Image",
url: "http://images.contentreserve.com/ImageType-100/0293-1/{C87D2BB9-0E13-4851-A9E2-547643F41A0E}Img100.jpg",
},
],
@@ -203,7 +210,7 @@ describe("Bib Details model", () => {
value: [
{
url: "/search?filters[placeOfPublication][0]=Mansfield%2C%20Ohio",
- urlLabel: "Mansfield, Ohio",
+ urlText: "Mansfield, Ohio",
},
],
},
@@ -218,7 +225,7 @@ describe("Bib Details model", () => {
value: [
{
url: "/search?filters[donor][0]=Gift%20of%20the%20DeWitt%20Wallace%20Endowment%20Fund%2C%20named%20in%20honor%20of%20the%20founder%20of%20Reader's%20Digest",
- urlLabel:
+ urlText:
"Gift of the DeWitt Wallace Endowment Fund, named in honor of the founder of Reader's Digest",
},
],
@@ -252,6 +259,22 @@ describe("Bib Details model", () => {
expect(subjects.link).toBe("internal")
expect(subjects.value[0].url).toContain("/browse/subjects/")
})
+ it("creates series fields and merges series uniform title into series added entry", () => {
+ const series = bibWithSeriesModel.bottomDetails.find(
+ (d) => d.label === "Series"
+ ) as LinkedBibDetail
+ expect(series.link).toBe("internal")
+ expect(series.value[0].url).toContain("search?filters[series][0]")
+ expect(series.value[0].urlText).toEqual("Inediti e rarità rossiniane ;")
+ expect(series.value[0].text).toEqual("Inediti e rarità rossiniane ; 12")
+ const seriesAddedEntry = bibWithSeriesModel.bottomDetails.find(
+ (d) => d.label === "Series added entry"
+ ) as LinkedBibDetail
+ // Value is from seriesUniformTitle
+ expect(seriesAddedEntry.value[0].urlText).toEqual(
+ "Rossini, Gioacchino 1792-1868. Works. Selections (Boccaccini & Spada editore) ;"
+ )
+ })
})
describe("parallels", () => {
diff --git a/src/types/bibDetailsTypes.ts b/src/types/bibDetailsTypes.ts
index ac48a4b15..eb47555dc 100644
--- a/src/types/bibDetailsTypes.ts
+++ b/src/types/bibDetailsTypes.ts
@@ -38,8 +38,8 @@ export interface MarcLinkedDetail {
export interface BibDetailURL {
url: string
- urlLabel?: string
- // Unlinked text following the URL
+ urlText?: string
+ // Unlinked text surrounding (and including) the linked urlText
text?: string
}
@@ -48,7 +48,7 @@ export interface FieldMapping {
field: string
}
-export interface ContributorEntry {
+export interface DisplayPackedEntry {
display: string
"@value": string
}