feat: print references
This commit is contained in:
+19
-5
@@ -1,6 +1,8 @@
|
||||
import { filterNonNullable } from "@optolith/helpers/array"
|
||||
import { PublicationRefs } from "optolith-database-schema/types/source/_PublicationRef"
|
||||
import { GetById } from "./helpers/getTypes.js"
|
||||
import { LocaleEnvironment } from "./helpers/locale.js"
|
||||
import { getReferencesTranslation } from "./references/index.js"
|
||||
|
||||
/**
|
||||
* Creates a function that creates the JSON representation of the rules text for
|
||||
@@ -15,14 +17,25 @@ export const createEntityDescriptionCreator =
|
||||
return () => undefined
|
||||
}
|
||||
|
||||
return (params) => {
|
||||
const rawEntry = fn(entry, ...args)(params)
|
||||
return (locale, getPublicationById) => {
|
||||
const rawEntry = fn(entry, ...args)(locale, getPublicationById)
|
||||
|
||||
if (rawEntry === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return { ...rawEntry, body: filterNonNullable(rawEntry.body) }
|
||||
return {
|
||||
...rawEntry,
|
||||
body: filterNonNullable(rawEntry.body),
|
||||
references:
|
||||
rawEntry.references === undefined
|
||||
? undefined
|
||||
: getReferencesTranslation(
|
||||
getPublicationById,
|
||||
locale,
|
||||
rawEntry.references
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +57,8 @@ export type EntityDescriptionCreator<
|
||||
* JSON representation of the rules text for a ibrary entry.
|
||||
*/
|
||||
export type EntityDescriptionConfiguredCreator<R = EntityDescription> = (
|
||||
locale: LocaleEnvironment
|
||||
locale: LocaleEnvironment,
|
||||
getPublicationById: GetById.Static.Publication
|
||||
) => R | undefined
|
||||
|
||||
/**
|
||||
@@ -55,7 +69,7 @@ export type EntityDescription = {
|
||||
subtitle?: string
|
||||
className: string
|
||||
body: EntityDescriptionSection[]
|
||||
references?: PublicationRefs
|
||||
references?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { isNotNullish } from "@optolith/helpers/nullable"
|
||||
import { assertExhaustive } from "@optolith/helpers/typeSafety"
|
||||
import { PublicationRefs } from "optolith-database-schema/types/source/_PublicationRef"
|
||||
import { GetById } from "../helpers/getTypes.js"
|
||||
import { LocaleEnvironment } from "../helpers/locale.js"
|
||||
import {
|
||||
isSimpleOccurrence,
|
||||
isSimpleOccurrences,
|
||||
isVersionedOccurrence,
|
||||
} from "./occurrence.js"
|
||||
import {
|
||||
fromRawPageRange,
|
||||
normalizePageRanges,
|
||||
numberRangeToPageRange,
|
||||
printPageRange,
|
||||
printPageRanges,
|
||||
} from "./pageRange.js"
|
||||
|
||||
export const getReferencesTranslation = (
|
||||
getPublicationById: GetById.Static.Publication,
|
||||
locale: LocaleEnvironment,
|
||||
references: PublicationRefs
|
||||
) =>
|
||||
references
|
||||
.map((ref) => {
|
||||
const publication = getPublicationById(ref.id.publication)
|
||||
const publicationTranslations = locale.translateMap(
|
||||
publication?.translations
|
||||
)
|
||||
const occurrences = locale.translateMap(ref.occurrences)
|
||||
|
||||
if (
|
||||
publication === undefined ||
|
||||
publicationTranslations === undefined ||
|
||||
occurrences === undefined
|
||||
) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (isSimpleOccurrence(occurrences)) {
|
||||
return `${publicationTranslations.name} ${printPageRange(
|
||||
locale.translate,
|
||||
numberRangeToPageRange(occurrences)
|
||||
)}`
|
||||
}
|
||||
|
||||
if (isSimpleOccurrences(occurrences)) {
|
||||
const ranges = normalizePageRanges(
|
||||
occurrences.map(numberRangeToPageRange)
|
||||
)
|
||||
return `${publicationTranslations.name} ${printPageRanges(
|
||||
locale.translate,
|
||||
ranges
|
||||
)}`
|
||||
}
|
||||
|
||||
if (isVersionedOccurrence(occurrences)) {
|
||||
const initialPageRanges = normalizePageRanges(
|
||||
occurrences.initial.pages.map(fromRawPageRange)
|
||||
)
|
||||
|
||||
const initial =
|
||||
occurrences.initial.printing === undefined
|
||||
? printPageRanges(locale.translate, initialPageRanges)
|
||||
: `${printPageRanges(
|
||||
locale.translate,
|
||||
initialPageRanges
|
||||
)} (${locale.translate(
|
||||
"since the {0}. printing",
|
||||
occurrences.initial.printing
|
||||
)})`
|
||||
|
||||
const revisions =
|
||||
occurrences.revisions?.map((rev) => {
|
||||
switch (rev.tag) {
|
||||
case "Since": {
|
||||
const pageRanges = normalizePageRanges(
|
||||
rev.since.pages.map(fromRawPageRange)
|
||||
)
|
||||
return `${printPageRanges(
|
||||
locale.translate,
|
||||
pageRanges
|
||||
)} (${locale.translate(
|
||||
"since the {0}. printing",
|
||||
rev.since.printing
|
||||
)})`
|
||||
}
|
||||
case "Deprecated": {
|
||||
return locale.translate(
|
||||
"removed in {0}. printing",
|
||||
rev.deprecated.printing
|
||||
)
|
||||
}
|
||||
default:
|
||||
return assertExhaustive(rev)
|
||||
}
|
||||
}) ?? []
|
||||
|
||||
const allPageRanges = [initial, ...revisions].join("; ")
|
||||
|
||||
return `${publicationTranslations.name} ${allPageRanges}`
|
||||
}
|
||||
|
||||
return assertExhaustive(occurrences)
|
||||
})
|
||||
.filter(isNotNullish)
|
||||
.join("; ")
|
||||
@@ -0,0 +1,18 @@
|
||||
import {
|
||||
Occurrence,
|
||||
SimpleOccurrence,
|
||||
SimpleOccurrences,
|
||||
VersionedOccurrence,
|
||||
} from "optolith-database-schema/types/source/_PublicationRef"
|
||||
|
||||
export const isSimpleOccurrence = (
|
||||
occurrence: Occurrence
|
||||
): occurrence is SimpleOccurrence => Object.hasOwn(occurrence, "first_page")
|
||||
|
||||
export const isSimpleOccurrences = (
|
||||
occurrence: Occurrence
|
||||
): occurrence is SimpleOccurrences => Array.isArray(occurrence)
|
||||
|
||||
export const isVersionedOccurrence = (
|
||||
occurrence: Occurrence
|
||||
): occurrence is VersionedOccurrence => Object.hasOwn(occurrence, "initial")
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Compare } from "@optolith/helpers/compare"
|
||||
import { assertExhaustive } from "@optolith/helpers/typeSafety"
|
||||
import { Page } from "optolith-database-schema/types/source/_PublicationRef"
|
||||
import { Translate } from "../helpers/translate.js"
|
||||
|
||||
/**
|
||||
* A comparison function for two pages.
|
||||
*/
|
||||
export const comparePage: Compare<Page> = (a, b) => {
|
||||
switch (a.tag) {
|
||||
case "InsideCoverFront":
|
||||
return b.tag === "InsideCoverFront" ? 0 : -1
|
||||
case "InsideCoverBack":
|
||||
return b.tag === "InsideCoverBack" ? 0 : 1
|
||||
case "Numbered":
|
||||
return b.tag === "Numbered"
|
||||
? a.numbered - b.numbered
|
||||
: b.tag === "InsideCoverFront"
|
||||
? 1
|
||||
: -1
|
||||
default:
|
||||
return assertExhaustive(a)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if two pages are equal.
|
||||
*/
|
||||
export const equalsPage = (a: Page, b: Page): boolean => comparePage(a, b) === 0
|
||||
|
||||
/**
|
||||
* Returns the successor of a page.
|
||||
*/
|
||||
export const succ = (page: Page): Page => {
|
||||
switch (page.tag) {
|
||||
case "InsideCoverFront":
|
||||
return { tag: "Numbered", numbered: 1 }
|
||||
case "InsideCoverBack":
|
||||
return { tag: "InsideCoverFront", inside_cover_front: {} }
|
||||
case "Numbered":
|
||||
return { tag: "Numbered", numbered: page.numbered + 1 }
|
||||
default:
|
||||
return assertExhaustive(page)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a page object for a page number.
|
||||
*/
|
||||
export const numberToPage = (number: number): Page => ({
|
||||
tag: "Numbered",
|
||||
numbered: number,
|
||||
})
|
||||
|
||||
/**
|
||||
* Returns a string representation of a page.
|
||||
*/
|
||||
export const printPage = (translate: Translate, page: Page) => {
|
||||
switch (page.tag) {
|
||||
case "InsideCoverFront":
|
||||
return translate("Front Cover Inside")
|
||||
case "InsideCoverBack":
|
||||
return translate("Back Cover Inside")
|
||||
case "Numbered":
|
||||
return page.numbered.toString()
|
||||
default:
|
||||
return assertExhaustive(page)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { range } from "@optolith/helpers/array"
|
||||
import {
|
||||
Page,
|
||||
PageRange as RawPageRange,
|
||||
SimpleOccurrence,
|
||||
} from "optolith-database-schema/types/source/_PublicationRef"
|
||||
import { Translate } from "../helpers/translate.js"
|
||||
import {
|
||||
comparePage,
|
||||
equalsPage,
|
||||
numberToPage,
|
||||
printPage,
|
||||
succ,
|
||||
} from "./page.js"
|
||||
|
||||
/**
|
||||
* A range of pages, including the first and last page, if the range includes
|
||||
* more than on page.
|
||||
*/
|
||||
export type PageRange = {
|
||||
firstPage: Page
|
||||
lastPage?: Page
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a numeric range to a page object range.
|
||||
*/
|
||||
export const numberRangeToPageRange = (
|
||||
numberRange: SimpleOccurrence
|
||||
): PageRange =>
|
||||
numberRange.last_page === undefined
|
||||
? { firstPage: numberToPage(numberRange.first_page) }
|
||||
: {
|
||||
firstPage: numberToPage(numberRange.first_page),
|
||||
lastPage: numberToPage(numberRange.last_page),
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a page object range from the database to a local page object range.
|
||||
*/
|
||||
export const fromRawPageRange = (pageRange: RawPageRange): PageRange =>
|
||||
pageRange.last_page === undefined
|
||||
? { firstPage: pageRange.first_page }
|
||||
: {
|
||||
firstPage: pageRange.first_page,
|
||||
lastPage: pageRange.last_page,
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts and combines page ranges while removing duplicates.
|
||||
*/
|
||||
export const normalizePageRanges = (ranges: PageRange[]): PageRange[] =>
|
||||
ranges
|
||||
.flatMap(({ firstPage, lastPage = firstPage }): Page[] => {
|
||||
if (firstPage.tag === "Numbered" && lastPage.tag === "Numbered") {
|
||||
return range(firstPage.numbered, lastPage.numbered).map((numbered) => ({
|
||||
tag: "Numbered",
|
||||
numbered,
|
||||
}))
|
||||
} else {
|
||||
return [firstPage, lastPage]
|
||||
}
|
||||
})
|
||||
.filter(
|
||||
(page, i, pages) => pages.findIndex((p) => equalsPage(page, p)) === i
|
||||
)
|
||||
.sort(comparePage)
|
||||
.reduce<PageRange[]>((acc, page): PageRange[] => {
|
||||
const lastRange = acc[acc.length - 1]
|
||||
|
||||
if (lastRange === undefined) {
|
||||
return [{ firstPage: page }]
|
||||
}
|
||||
|
||||
const { firstPage, lastPage = firstPage } = lastRange
|
||||
|
||||
if (equalsPage(page, succ(lastPage))) {
|
||||
return [...acc.slice(0, -1), { firstPage, lastPage: page }]
|
||||
}
|
||||
|
||||
return [...acc, { firstPage: page }]
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* Returns a string representation of a page range.
|
||||
*/
|
||||
export const printPageRange = (translate: Translate, pageRange: PageRange) =>
|
||||
pageRange.lastPage === undefined
|
||||
? printPage(translate, pageRange.firstPage)
|
||||
: `${printPage(translate, pageRange.firstPage)}–${printPage(
|
||||
translate,
|
||||
pageRange.lastPage
|
||||
)}`
|
||||
|
||||
/**
|
||||
* Returns a string representation of a list of page ranges.
|
||||
*/
|
||||
export const printPageRanges = (
|
||||
translate: Translate,
|
||||
pageRanges: PageRange[]
|
||||
) =>
|
||||
pageRanges.map((pageRange) => printPageRange(translate, pageRange)).join(", ")
|
||||
@@ -0,0 +1,91 @@
|
||||
import assert from "node:assert/strict"
|
||||
import { describe, it } from "node:test"
|
||||
import { comparePage } from "../../src/references/page.js"
|
||||
|
||||
describe("comparePage", () => {
|
||||
it("returns 0 if the pages are equal", () => {
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "InsideCoverBack", inside_cover_back: {} },
|
||||
{ tag: "InsideCoverBack", inside_cover_back: {} }
|
||||
),
|
||||
0
|
||||
)
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "InsideCoverFront", inside_cover_front: {} },
|
||||
{ tag: "InsideCoverFront", inside_cover_front: {} }
|
||||
),
|
||||
0
|
||||
)
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "Numbered", numbered: 42 },
|
||||
{ tag: "Numbered", numbered: 42 }
|
||||
),
|
||||
0
|
||||
)
|
||||
})
|
||||
|
||||
it("returns a negative number if first should be sorted before the second", () => {
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "InsideCoverFront", inside_cover_front: {} },
|
||||
{ tag: "InsideCoverBack", inside_cover_back: {} }
|
||||
),
|
||||
-1
|
||||
)
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "InsideCoverFront", inside_cover_front: {} },
|
||||
{ tag: "Numbered", numbered: 42 }
|
||||
),
|
||||
-1
|
||||
)
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "Numbered", numbered: 42 },
|
||||
{ tag: "InsideCoverBack", inside_cover_back: {} }
|
||||
),
|
||||
-1
|
||||
)
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "Numbered", numbered: 24 },
|
||||
{ tag: "Numbered", numbered: 42 }
|
||||
),
|
||||
-18
|
||||
)
|
||||
})
|
||||
|
||||
it("returns a positive number if first should be sorted after the second", () => {
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "InsideCoverBack", inside_cover_back: {} },
|
||||
{ tag: "InsideCoverFront", inside_cover_front: {} }
|
||||
),
|
||||
1
|
||||
)
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "Numbered", numbered: 42 },
|
||||
{ tag: "InsideCoverFront", inside_cover_front: {} }
|
||||
),
|
||||
1
|
||||
)
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "InsideCoverBack", inside_cover_back: {} },
|
||||
{ tag: "Numbered", numbered: 42 }
|
||||
),
|
||||
1
|
||||
)
|
||||
assert.equal(
|
||||
comparePage(
|
||||
{ tag: "Numbered", numbered: 42 },
|
||||
{ tag: "Numbered", numbered: 24 }
|
||||
),
|
||||
18
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user