@museumwnf/viewer-core 2.9.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/catalogue/catalogue.js +18 -11
- package/src/composables/useCatalogueData.js +51 -17
- package/src/createViewer.js +2 -0
- package/src/displayLanguage.js +18 -0
- package/src/dxa/exhibition/useExhibitionData.js +3 -0
- package/src/dxa/exhibition/useExhibitionSheet.js +7 -3
- package/src/dxa/gallery/useGalleryData.js +3 -0
- package/src/dxa/gallery/useGallerySheet.js +7 -3
- package/src/i18n/markdown.js +25 -4
- package/src/index.js +2 -1
- package/src/record/derivations.js +27 -0
package/package.json
CHANGED
|
@@ -34,15 +34,20 @@ export const CATALOGUE_DATE_MODE = 'overlap'
|
|
|
34
34
|
* - `itemById`: every item, the visible rule not applied — a page that
|
|
35
35
|
* deliberately shows a hidden item (a timeline's illustration) reads it.
|
|
36
36
|
* `partnerById`, `countryById`, `dynastyById`: the visible records.
|
|
37
|
-
* - `itemLabel(item)`, `countryLabel(id)`, `partnerLabel(id)`,
|
|
38
|
-
* `dynastyLabel(id)`: plain text, `''` for nothing
|
|
37
|
+
* - `itemLabel(item)`, `countryLabel(id, lang)`, `partnerLabel(id, lang)`,
|
|
38
|
+
* `dynastyLabel(id, lang)`: plain text, `''` for nothing, in `lang` when
|
|
39
|
+
* given (the language of the record the name stands next to), in the
|
|
40
|
+
* display language otherwise.
|
|
39
41
|
* - `itemRoute(item)`, `partnerRoute(partner)`: the `item` and `partner`
|
|
40
42
|
* routes by name.
|
|
41
43
|
* - `itemRow(item, meta)`: the row `RecordList`/`RecordGrid`/
|
|
42
44
|
* `RelatedRecords` share. `meta` lists what follows the name, in order:
|
|
43
45
|
* `'country'`, `'dates'`, `'location'`, `'dynasties'`, `'holder'` (the
|
|
44
46
|
* partner's name, only when the package carries the partner, so a label
|
|
45
|
-
* is never an id), or a function `(item, text) => string`.
|
|
47
|
+
* is never an id), or a function `(item, text, lang) => string`. The row
|
|
48
|
+
* reads the item in the display language when it has text there, in
|
|
49
|
+
* English otherwise, and names its country, dynasties and partner in that
|
|
50
|
+
* same language.
|
|
46
51
|
*/
|
|
47
52
|
export function useCatalogue(options = {}) {
|
|
48
53
|
const data = useCatalogueData(options)
|
|
@@ -59,30 +64,32 @@ export function useCatalogue(options = {}) {
|
|
|
59
64
|
const dynastyById = data.index('dynasties')
|
|
60
65
|
|
|
61
66
|
const itemLabel = (item) => (item ? labelOf('items', item.id) : '')
|
|
62
|
-
const countryLabel = (id) => (id ? labelOf('countries', id) : '')
|
|
63
|
-
const partnerLabel = (id) => (id ? labelOf('partners', id) : '')
|
|
64
|
-
const dynastyLabel = (id) => (id ? labelOf('dynasties', id) : '')
|
|
67
|
+
const countryLabel = (id, lang) => (id ? labelOf('countries', id, { lang }) : '')
|
|
68
|
+
const partnerLabel = (id, lang) => (id ? labelOf('partners', id, { lang }) : '')
|
|
69
|
+
const dynastyLabel = (id, lang) => (id ? labelOf('dynasties', id, { lang }) : '')
|
|
65
70
|
|
|
66
71
|
const itemRoute = (item) => ({ name: 'item', params: { id: item.id } })
|
|
67
72
|
const partnerRoute = (partner) => ({ name: 'partner', params: { id: partner.id } })
|
|
68
73
|
|
|
74
|
+
// Each takes the language the row's record is shown in, so its names match it.
|
|
69
75
|
const META = {
|
|
70
|
-
country: (item) => countryLabel(item.country_id),
|
|
76
|
+
country: (item, text, lang) => countryLabel(item.country_id, lang),
|
|
71
77
|
dates: (item, text) => text.dates,
|
|
72
78
|
location: (item, text) => text.location,
|
|
73
|
-
dynasties: (item) => (item.dynasty_ids ?? []).map(dynastyLabel).filter(Boolean).join(', '),
|
|
74
|
-
holder: (item) => (partnerById.value.has(item.partner_id) ? partnerLabel(item.partner_id) : ''),
|
|
79
|
+
dynasties: (item, text, lang) => (item.dynasty_ids ?? []).map((id) => dynastyLabel(id, lang)).filter(Boolean).join(', '),
|
|
80
|
+
holder: (item, text, lang) => (partnerById.value.has(item.partner_id) ? partnerLabel(item.partner_id, lang) : ''),
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
function itemRow(item, meta = ['country', 'dates']) {
|
|
78
|
-
const
|
|
84
|
+
const lang = data.languageOf('items', item.id)
|
|
85
|
+
const text = tr('items', item.id, lang)
|
|
79
86
|
return {
|
|
80
87
|
id: item.id,
|
|
81
88
|
image: item.images?.[0]?.url ?? '',
|
|
82
89
|
imageAlt: itemLabel(item),
|
|
83
90
|
name: mdInline(text.name ?? item.internal_name ?? item.id),
|
|
84
91
|
meta: meta
|
|
85
|
-
.map((part) => (typeof part === 'function' ? part(item, text) : META[part]?.(item, text)))
|
|
92
|
+
.map((part) => (typeof part === 'function' ? part(item, text, lang) : META[part]?.(item, text, lang)))
|
|
86
93
|
.filter(Boolean),
|
|
87
94
|
badge: item.type ?? '',
|
|
88
95
|
to: itemRoute(item),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { computed } from 'vue'
|
|
1
|
+
import { computed, watch } from 'vue'
|
|
2
|
+
import { displayLanguage } from '../displayLanguage.js'
|
|
2
3
|
import { useDataPackage } from './useDataPackage.js'
|
|
3
4
|
import { byId as coreById, entityRef } from './useEntities.js'
|
|
4
5
|
import { md as coreMd, mdInline as coreMdInline, mdStrip as coreMdStrip } from '../i18n/markdown.js'
|
|
@@ -25,10 +26,10 @@ const BASE_LANGUAGE = 'en'
|
|
|
25
26
|
|
|
26
27
|
// A term's spellings and definition in one language, falling back to
|
|
27
28
|
// English, then to the term's own headword — the same row shape
|
|
28
|
-
// `useRecordSheet`'s `termRow` builds, but for the whole glossary in
|
|
29
|
-
//
|
|
30
|
-
// own language, which is why it is not reused: a catalogue list
|
|
31
|
-
//
|
|
29
|
+
// `useRecordSheet`'s `termRow` builds, but for the whole glossary in the
|
|
30
|
+
// display language rather than one record's `glossary_ids` in the record's
|
|
31
|
+
// own language, which is why it is not reused: a catalogue list is shown in
|
|
32
|
+
// the display language, a record sheet in the language the record is read in.
|
|
32
33
|
function glossaryRows(language, translations) {
|
|
33
34
|
const rows = translations('glossary', language)
|
|
34
35
|
const fallback = translations('glossary', BASE_LANGUAGE)
|
|
@@ -49,27 +50,46 @@ function glossaryRows(language, translations) {
|
|
|
49
50
|
* visible form of an entity and its index, and the package re-exports.
|
|
50
51
|
*
|
|
51
52
|
* `eager` names the entities `loadEnglish` loads — every entity the package
|
|
52
|
-
* carries by default
|
|
53
|
-
* the glossary binding read
|
|
53
|
+
* carries by default — in `defaultLanguage` and in the display language.
|
|
54
|
+
* `tr`, the labels and the glossary binding read the display language (the
|
|
55
|
+
* language the website is shown in, reference data included: a country is
|
|
56
|
+
* named in the language its record is shown in), and fall back, record by
|
|
57
|
+
* record, to `defaultLanguage` (`'en'` for every site so far). `visible` names,
|
|
54
58
|
* per entity, the predicate `entity()`/`index()` apply — a per-build
|
|
55
59
|
* language filter, a hidden-partner rule, a display-status check are all
|
|
56
60
|
* "which records this build shows", declared here once instead of coded
|
|
57
61
|
* into every page that lists that entity. `glossary` turns off the default
|
|
58
62
|
* binding on `md`/`mdInline` (a caller may still pass its own `glossary` to
|
|
59
|
-
* either, on or off).
|
|
63
|
+
* either, on or off). `followDisplayLanguage: false` keeps everything in
|
|
64
|
+
* `defaultLanguage`, for the DXA families until their pages are checked
|
|
65
|
+
* against legacy in other languages.
|
|
60
66
|
*/
|
|
61
|
-
export function useCatalogueData({
|
|
67
|
+
export function useCatalogueData({
|
|
68
|
+
eager, defaultLanguage = BASE_LANGUAGE, visible = {}, glossary = true, followDisplayLanguage = true,
|
|
69
|
+
} = {}) {
|
|
62
70
|
const pkg = useDataPackage()
|
|
63
71
|
const { availableLanguages, loadTranslations, translations } = pkg
|
|
64
72
|
const eagerEntities = eager ?? pkg.entityNames
|
|
73
|
+
const shown = computed(() => (followDisplayLanguage ? displayLanguage.value : defaultLanguage))
|
|
65
74
|
|
|
66
|
-
/** One record's translated fields, falling back to `defaultLanguage` then `{}`. */
|
|
67
|
-
function tr(entity, id, lang =
|
|
75
|
+
/** One record's translated fields, in `lang` (the display language), falling back to `defaultLanguage` then `{}`. */
|
|
76
|
+
function tr(entity, id, lang = shown.value) {
|
|
68
77
|
return pkg.tr(entity, id, lang, defaultLanguage)
|
|
69
78
|
}
|
|
70
79
|
|
|
80
|
+
/**
|
|
81
|
+
* The language one record is shown in: the display language when the
|
|
82
|
+
* record has text in it, `defaultLanguage` otherwise. The reference names
|
|
83
|
+
* shown next to it (its country, its partner, its dynasties) take this
|
|
84
|
+
* language, so a record left in English is not printed with French names.
|
|
85
|
+
*/
|
|
86
|
+
function languageOf(entity, id) {
|
|
87
|
+
const lang = shown.value
|
|
88
|
+
return id && translations(entity, lang)[id] ? lang : defaultLanguage
|
|
89
|
+
}
|
|
90
|
+
|
|
71
91
|
const defaultGlossary = computed(() =>
|
|
72
|
-
glossary ? glossaryEntries(glossaryRows(
|
|
92
|
+
glossary ? glossaryEntries(glossaryRows(shown.value, translations)) : [],
|
|
73
93
|
)
|
|
74
94
|
|
|
75
95
|
// A caller's own `glossary` — a record's own terms, not every term in the
|
|
@@ -84,20 +104,34 @@ export function useCatalogueData({ eager, defaultLanguage = BASE_LANGUAGE, visib
|
|
|
84
104
|
const mdInline = bindGlossary(coreMdInline)
|
|
85
105
|
const mdStrip = coreMdStrip
|
|
86
106
|
|
|
87
|
-
/**
|
|
88
|
-
|
|
107
|
+
/**
|
|
108
|
+
* `mdStrip(tr(entity, id, lang).name ?? record[fallback] ?? id)` — the one
|
|
109
|
+
* label shape every site wrote. `lang` is the display language unless the
|
|
110
|
+
* caller names the language of the record the label stands next to.
|
|
111
|
+
*/
|
|
112
|
+
function labelOf(entity, id, { fallback = 'internal_name', lang } = {}) {
|
|
89
113
|
if (!id) return ''
|
|
90
|
-
|
|
114
|
+
// A label passed as a callback (`.map(countryLabel)`, a facet's
|
|
115
|
+
// `label(value, ctx)`) receives an index or a context here, not a language.
|
|
116
|
+
const name = tr(entity, id, typeof lang === 'string' ? lang : undefined).name
|
|
91
117
|
if (name) return mdStrip(name)
|
|
92
118
|
const record = coreById(entity).value.get(id)
|
|
93
119
|
return mdStrip(record?.[fallback] ?? id)
|
|
94
120
|
}
|
|
95
121
|
|
|
96
122
|
let englishReady = null
|
|
97
|
-
/**
|
|
123
|
+
/**
|
|
124
|
+
* Loads `defaultLanguage` for every entity in `eager`, once, and the display
|
|
125
|
+
* language too, again each time it changes. Resolves with the
|
|
126
|
+
* `defaultLanguage` files: a page renders in English until the other
|
|
127
|
+
* language arrives, and re-renders then.
|
|
128
|
+
*/
|
|
98
129
|
function loadEnglish() {
|
|
99
130
|
if (!englishReady) {
|
|
100
131
|
englishReady = Promise.all(eagerEntities.map((name) => loadTranslations(name, defaultLanguage)))
|
|
132
|
+
watch(shown, (lang) => {
|
|
133
|
+
if (lang !== defaultLanguage) eagerEntities.forEach((name) => loadTranslations(name, lang))
|
|
134
|
+
}, { immediate: true })
|
|
101
135
|
}
|
|
102
136
|
return englishReady
|
|
103
137
|
}
|
|
@@ -135,7 +169,7 @@ export function useCatalogueData({ eager, defaultLanguage = BASE_LANGUAGE, visib
|
|
|
135
169
|
}
|
|
136
170
|
|
|
137
171
|
return {
|
|
138
|
-
tr, md, mdInline, mdStrip, loadEnglish, labelOf, entity, index,
|
|
172
|
+
tr, md, mdInline, mdStrip, loadEnglish, labelOf, languageOf, entity, index,
|
|
139
173
|
availableLanguages, loadTranslations, translations,
|
|
140
174
|
}
|
|
141
175
|
}
|
package/src/createViewer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createApp } from 'vue'
|
|
2
2
|
import AppRoot from './AppRoot.vue'
|
|
3
3
|
import { useDataPackage } from './composables/useDataPackage.js'
|
|
4
|
+
import { bindDisplayLanguage } from './displayLanguage.js'
|
|
4
5
|
import { createI18n } from './i18n/index.js'
|
|
5
6
|
import { connectLanguageToRouter, resolveInitialLanguage } from './i18n/language.js'
|
|
6
7
|
import { VIEWER_CONFIG } from './injectionKeys.js'
|
|
@@ -24,6 +25,7 @@ export function createViewer(config = {}) {
|
|
|
24
25
|
app.use(i18n)
|
|
25
26
|
|
|
26
27
|
connectLanguageToRouter({ locale: i18n.locale, offered: languages, router })
|
|
28
|
+
bindDisplayLanguage(i18n.locale)
|
|
27
29
|
|
|
28
30
|
return app
|
|
29
31
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { computed, shallowRef } from 'vue'
|
|
2
|
+
|
|
3
|
+
// The language the website is shown in, for code that runs outside a
|
|
4
|
+
// component: the catalogue data layer is built once, at module scope, where
|
|
5
|
+
// `useI18n()` cannot inject. `createViewer` binds the application's locale
|
|
6
|
+
// here, the way it hands its configuration to `setSiteConfig`; until it does
|
|
7
|
+
// (a test mounting one component), the website is in English.
|
|
8
|
+
|
|
9
|
+
const BASE_LANGUAGE = 'en'
|
|
10
|
+
const bound = shallowRef(null)
|
|
11
|
+
|
|
12
|
+
/** The active display language, read-only. */
|
|
13
|
+
export const displayLanguage = computed(() => bound.value?.value ?? BASE_LANGUAGE)
|
|
14
|
+
|
|
15
|
+
/** Binds the application's locale ref; `createViewer` calls it once. */
|
|
16
|
+
export function bindDisplayLanguage(locale) {
|
|
17
|
+
bound.value = locale ?? null
|
|
18
|
+
}
|
|
@@ -61,6 +61,9 @@ export function useExhibitionData(config = {}) {
|
|
|
61
61
|
|
|
62
62
|
const catalogue = useCatalogueData({
|
|
63
63
|
eager: config.eager ?? DEFAULT_EAGER,
|
|
64
|
+
// English-first until these pages are checked against legacy in other
|
|
65
|
+
// languages (the display-language rule, inventory-app#2414).
|
|
66
|
+
followDisplayLanguage: false,
|
|
64
67
|
defaultLanguage: defaultLang,
|
|
65
68
|
visible: {
|
|
66
69
|
// An item's `languages` is what it has TRANSLATIONS in, so a
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
// dynasties stand in when it has none, and keep their own popouts
|
|
28
28
|
// (decision D5, inventory-app#2233).
|
|
29
29
|
|
|
30
|
-
import { artistLine, pictureCredits, recordArtist } from '../../record/derivations.js'
|
|
30
|
+
import { artistLine, pictureCredits, picturesInLanguage, recordArtist } from '../../record/derivations.js'
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* The exhibition's item-sheet spec, over its data layer's `data`.
|
|
@@ -88,13 +88,17 @@ export function useExhibitionSheet(data, config = {}) {
|
|
|
88
88
|
entity: 'items',
|
|
89
89
|
translations: ['glossary', 'dynasties', 'partners'],
|
|
90
90
|
// Credits are the sheet language's own: dxa-api read them from that
|
|
91
|
-
// language's authors_objects rows
|
|
91
|
+
// language's authors_objects rows (else the record's own credit column)
|
|
92
|
+
// and printed nothing when it had neither. Each person is printed as its
|
|
93
|
+
// SQL did, "Firstname LASTNAME" (`credit_names`, `creditLine`).
|
|
94
|
+
creditNames: true,
|
|
92
95
|
fields,
|
|
93
96
|
layout: 'list',
|
|
94
97
|
shortDescription: 'short_description',
|
|
95
98
|
shortDescriptionAfter: 'description',
|
|
99
|
+
// Only the pictures with a row in the sheet's language, as dxa-api listed them.
|
|
96
100
|
media: (record, { language }) =>
|
|
97
|
-
[...(record.images
|
|
101
|
+
[...picturesInLanguage(record.images, language)]
|
|
98
102
|
.sort((a, b) => (a.display_order ?? 0) - (b.display_order ?? 0))
|
|
99
103
|
.map((p) => ({
|
|
100
104
|
url: p.url,
|
|
@@ -28,6 +28,9 @@ export function useGalleryData(config = {}) {
|
|
|
28
28
|
|
|
29
29
|
const catalogue = useCatalogueData({
|
|
30
30
|
eager: config.eager ?? DEFAULT_EAGER,
|
|
31
|
+
// English-first until these pages are checked against legacy in other
|
|
32
|
+
// languages (the display-language rule, inventory-app#2414).
|
|
33
|
+
followDisplayLanguage: false,
|
|
31
34
|
})
|
|
32
35
|
|
|
33
36
|
const {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
// dynasties stand in when it has none, and keep their own popouts
|
|
28
28
|
// (decision D5, inventory-app#2233).
|
|
29
29
|
|
|
30
|
-
import { artistLine, pictureCredits, recordArtist } from '../../record/derivations.js'
|
|
30
|
+
import { artistLine, pictureCredits, picturesInLanguage, recordArtist } from '../../record/derivations.js'
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* The gallery's item-sheet spec, over its data layer's `data`.
|
|
@@ -88,13 +88,17 @@ export function useGallerySheet(data, config = {}) {
|
|
|
88
88
|
entity: 'items',
|
|
89
89
|
translations: ['glossary', 'dynasties', 'partners'],
|
|
90
90
|
// Credits are the sheet language's own: dxa-api read them from that
|
|
91
|
-
// language's authors_objects rows
|
|
91
|
+
// language's authors_objects rows (else the record's own credit column)
|
|
92
|
+
// and printed nothing when it had neither. Each person is printed as its
|
|
93
|
+
// SQL did, "Firstname LASTNAME" (`credit_names`, `creditLine`).
|
|
94
|
+
creditNames: true,
|
|
92
95
|
fields,
|
|
93
96
|
layout: 'list',
|
|
94
97
|
shortDescription: 'short_description',
|
|
95
98
|
shortDescriptionAfter: 'description',
|
|
99
|
+
// Only the pictures with a row in the sheet's language, as dxa-api listed them.
|
|
96
100
|
media: (record, { language }) =>
|
|
97
|
-
[...(record.images
|
|
101
|
+
[...picturesInLanguage(record.images, language)]
|
|
98
102
|
.sort((a, b) => (a.display_order ?? 0) - (b.display_order ?? 0))
|
|
99
103
|
.map((p) => ({
|
|
100
104
|
url: p.url,
|
package/src/i18n/markdown.js
CHANGED
|
@@ -30,6 +30,27 @@ const escaping = {
|
|
|
30
30
|
},
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// No text on these websites is code. Legacy wrote the backtick as a letter
|
|
34
|
+
// (the ʿayn of "Fu`ad", "`Abbas"), and a text pasted from a word processor can
|
|
35
|
+
// hold one anywhere; read as Markdown, two of them turned the words between
|
|
36
|
+
// into a code span, printed in a code font with any markup inside as
|
|
37
|
+
// characters. An indented line was a code block the same way. Without these
|
|
38
|
+
// three tokenizers a backtick is the character it is, and an indented line a
|
|
39
|
+
// line of its paragraph.
|
|
40
|
+
const noCode = {
|
|
41
|
+
tokenizer: {
|
|
42
|
+
codespan() {
|
|
43
|
+
return undefined
|
|
44
|
+
},
|
|
45
|
+
code() {
|
|
46
|
+
return undefined
|
|
47
|
+
},
|
|
48
|
+
fences() {
|
|
49
|
+
return undefined
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
|
|
33
54
|
// `breaks` is the one difference, and it is a difference in the source rather
|
|
34
55
|
// than in policy. A text in the dictionary is written as prose and wraps freely,
|
|
35
56
|
// so a single newline means nothing. A record's field arrives from a database
|
|
@@ -37,8 +58,8 @@ const escaping = {
|
|
|
37
58
|
// together.
|
|
38
59
|
const prose = new Marked({ async: false, gfm: true, breaks: false })
|
|
39
60
|
const record = new Marked({ async: false, gfm: true, breaks: true })
|
|
40
|
-
prose.use(escaping)
|
|
41
|
-
record.use(escaping)
|
|
61
|
+
prose.use(escaping, noCode)
|
|
62
|
+
record.use(escaping, noCode)
|
|
42
63
|
|
|
43
64
|
const pipeline = (breaks) => (breaks ? record : prose)
|
|
44
65
|
|
|
@@ -123,8 +144,8 @@ function withGlossary(breaks, glossary) {
|
|
|
123
144
|
} else {
|
|
124
145
|
const withProse = new Marked({ async: false, gfm: true, breaks: false })
|
|
125
146
|
const withRecord = new Marked({ async: false, gfm: true, breaks: true })
|
|
126
|
-
withProse.use(escaping, { extensions: [extension] })
|
|
127
|
-
withRecord.use(escaping, { extensions: [extension] })
|
|
147
|
+
withProse.use(escaping, noCode, { extensions: [extension] })
|
|
148
|
+
withRecord.use(escaping, noCode, { extensions: [extension] })
|
|
128
149
|
entry = { prose: withProse, record: withRecord }
|
|
129
150
|
}
|
|
130
151
|
glossaryPipelines.set(key, entry)
|
package/src/index.js
CHANGED
|
@@ -17,6 +17,7 @@ export { md, mdInline, mdStrip, renderBlock, renderInline, renderPlain } from '.
|
|
|
17
17
|
export { languageLabels, offeredLanguages } from './languages.js'
|
|
18
18
|
export { defaultScrollBehavior, resolveViews } from './router/index.js'
|
|
19
19
|
export { mediaUrl, useSiteConfig } from './siteConfig.js'
|
|
20
|
+
export { bindDisplayLanguage, displayLanguage } from './displayLanguage.js'
|
|
20
21
|
export { sourceUrl, useSiteRights } from './rights.js'
|
|
21
22
|
|
|
22
23
|
// The list pages: query state, pagination, facets, dates, keyword search.
|
|
@@ -39,7 +40,7 @@ export {
|
|
|
39
40
|
glossaryEntries, glossaryTermsFor, glossaryTermsForText, sheetRows, useRecordSheet,
|
|
40
41
|
} from './record/recordSheet.js'
|
|
41
42
|
export {
|
|
42
|
-
artistLine, citation, pictureCredits, recordArtist, relatedRecords, removeEndingPoint, searchGlossary, timelineLinkFor, useGlossaryPopup,
|
|
43
|
+
artistLine, citation, creditLine, pictureCredits, picturesInLanguage, recordArtist, relatedRecords, removeEndingPoint, searchGlossary, timelineLinkFor, useGlossaryPopup,
|
|
43
44
|
useRelatedRecords,
|
|
44
45
|
} from './record/derivations.js'
|
|
45
46
|
export { buildCollectionTree, collectionTreeFromThemes, useCollectionTree } from './record/collectionTree.js'
|
|
@@ -193,6 +193,33 @@ export function pictureCredits(picture, language) {
|
|
|
193
193
|
return { photographer: picture?.photographer ?? '', copyright: picture?.copyright ?? '' }
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
/**
|
|
197
|
+
* The pictures a page in `language` shows. Legacy listed a picture on a page
|
|
198
|
+
* only where it had a row in that language, empty or not; a package carries
|
|
199
|
+
* those languages per picture (`languages`). A package exported before them
|
|
200
|
+
* has no such list, and every picture shows, as before.
|
|
201
|
+
*/
|
|
202
|
+
export function picturesInLanguage(pictures, language) {
|
|
203
|
+
return (pictures ?? []).filter((picture) => !Array.isArray(picture?.languages) || picture.languages.includes(language))
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* A credit line from its people (`credit_names[role]` in a package): each one
|
|
208
|
+
* as the galleries' sheet printed them, "Firstname LASTNAME" (dxa-api's SQL
|
|
209
|
+
* capitalised the surname; the data keeps it as written), or as written when
|
|
210
|
+
* the entry is a spelling of its own (`{ name }`: the original script on an
|
|
211
|
+
* Arabic page, or the record's own credit column). Joined with ", ".
|
|
212
|
+
*/
|
|
213
|
+
export function creditLine(names) {
|
|
214
|
+
return (names ?? [])
|
|
215
|
+
.map((entry) => {
|
|
216
|
+
if (typeof entry?.name === 'string') return entry.name.trim()
|
|
217
|
+
return [entry?.firstname?.trim(), entry?.lastname?.trim()?.toLocaleUpperCase()].filter(Boolean).join(' ')
|
|
218
|
+
})
|
|
219
|
+
.filter(Boolean)
|
|
220
|
+
.join(', ')
|
|
221
|
+
}
|
|
222
|
+
|
|
196
223
|
const lifePart = (value) => (typeof value === 'string' ? value.trim() : '')
|
|
197
224
|
|
|
198
225
|
/**
|