@museumwnf/viewer-core 1.14.0 → 1.15.1

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.
Files changed (43) hide show
  1. package/LICENSE.md +44 -44
  2. package/README.md +658 -635
  3. package/package.json +50 -50
  4. package/src/AppRoot.vue +58 -58
  5. package/src/catalogue/dates.js +209 -209
  6. package/src/catalogue/facets.js +61 -61
  7. package/src/catalogue/keywordIndex.js +289 -289
  8. package/src/catalogue/listQuery.js +100 -100
  9. package/src/catalogue/pagination.js +57 -57
  10. package/src/catalogue/searchLanguage.js +32 -32
  11. package/src/catalogue/timeline.js +220 -220
  12. package/src/composables/useCatalogueData.js +141 -141
  13. package/src/composables/useDataPackage.js +98 -98
  14. package/src/composables/useEntities.js +100 -100
  15. package/src/composables/useRecordLanguage.js +71 -71
  16. package/src/conventions.js +252 -148
  17. package/src/createViewer.js +29 -29
  18. package/src/i18n/I18nText.vue +17 -17
  19. package/src/i18n/I18nTextInline.vue +17 -17
  20. package/src/i18n/index.js +88 -88
  21. package/src/i18n/language.js +127 -127
  22. package/src/i18n/markdown.js +265 -265
  23. package/src/index.js +51 -50
  24. package/src/injectionKeys.js +1 -1
  25. package/src/languages.js +49 -49
  26. package/src/legacy/index.js +58 -58
  27. package/src/record/collectionTree.js +307 -307
  28. package/src/record/derivations.js +154 -154
  29. package/src/record/partners.js +87 -87
  30. package/src/record/recordSheet.js +231 -231
  31. package/src/rights.js +53 -53
  32. package/src/router/index.js +138 -138
  33. package/src/siteConfig.js +37 -37
  34. package/src/styles/base.css +49 -49
  35. package/src/testing/checkOfferedLanguages.js +67 -67
  36. package/src/testing/index.js +3 -3
  37. package/src/testing/smoke.js +103 -103
  38. package/src/testing/viteConfig.js +72 -90
  39. package/src/views/DetailView.vue +53 -53
  40. package/src/views/HomeView.vue +20 -20
  41. package/src/views/ListView.vue +61 -61
  42. package/src/views/NotFoundView.vue +8 -8
  43. package/src/vite.js +8 -8
@@ -1,100 +1,100 @@
1
- import { computed, shallowRef } from 'vue'
2
- import { useDataPackage } from './useDataPackage.js'
3
-
4
- // The records of an entity are one thing: loaded once, from the chunk Vite
5
- // emits for that one file, and shared by every page that asks for them. A
6
- // website reads them through here and keeps no copy of its own — the alias
7
- // `@inventory-data` is this package's, and a site file importing from it
8
- // directly ships the whole dataset in its first bundle.
9
-
10
- const records = new Map() // entity name → shallowRef(null | records)
11
- const pending = new Map() // entity name → the load in flight
12
- const indexes = new Map() // `${entity}.${key}` → computed Map
13
-
14
- function refFor(name) {
15
- let entry = records.get(name)
16
- if (!entry) {
17
- entry = shallowRef(null)
18
- records.set(name, entry)
19
- }
20
- return entry
21
- }
22
-
23
- /**
24
- * The shared ref of one entity, without asking for its load — `null` until a
25
- * route's `meta.entities` or `loadEntities` brings the chunk in. This is what
26
- * a website's data composable exports at module level, so that importing the
27
- * composable loads nothing: the pages that read an entity are the ones that
28
- * declare it.
29
- */
30
- export function entityRef(name) {
31
- return refFor(name)
32
- }
33
-
34
- /**
35
- * Load the named entities, once each. Resolves with their records in the
36
- * order asked for; a name the package does not have rejects, as `loadEntity`
37
- * does.
38
- */
39
- export function loadEntities(names) {
40
- const { loadEntity } = useDataPackage()
41
- return Promise.all(
42
- names.map((name) => {
43
- const entry = refFor(name)
44
- if (entry.value !== null) return entry.value
45
- let load = pending.get(name)
46
- if (!load) {
47
- load = loadEntity(name)
48
- .then((data) => {
49
- entry.value = data
50
- pending.delete(name)
51
- return data
52
- })
53
- .catch((error) => {
54
- pending.delete(name)
55
- throw error
56
- })
57
- pending.set(name, load)
58
- }
59
- return load
60
- }),
61
- )
62
- }
63
-
64
- /**
65
- * A record map for one entity, keyed on `key` (the package id by default) —
66
- * empty until the entity is loaded, and rebuilt if it ever reloads.
67
- */
68
- export function byId(name, key = 'id') {
69
- const cacheKey = `${name}.${key}`
70
- let index = indexes.get(cacheKey)
71
- if (!index) {
72
- const entry = refFor(name)
73
- index = computed(() => new Map((entry.value ?? []).map((record) => [record[key], record])))
74
- indexes.set(cacheKey, index)
75
- }
76
- return index
77
- }
78
-
79
- /**
80
- * The standard way a website reads its records.
81
- *
82
- * `useEntities(['items', 'partners'])` returns one reactive ref per name —
83
- * `null` until that entity's chunk has arrived, the records after — plus
84
- * `ready`, a promise for all of them, `loaded`, the same as a boolean, and
85
- * `byId`. The refs are the same objects for every caller, so a composable
86
- * may export them at module level and a view may read them at any time.
87
- *
88
- * A route that declares `meta: { entities: [...] }` has them loaded by the
89
- * router before its view is created, so that view never sees `null`.
90
- */
91
- export function useEntities(names) {
92
- const list = Array.isArray(names) ? names : [names]
93
- const result = { ready: loadEntities(list) }
94
- for (const name of list) {
95
- result[name] = refFor(name)
96
- }
97
- result.loaded = computed(() => list.every((name) => refFor(name).value !== null))
98
- result.byId = byId
99
- return result
100
- }
1
+ import { computed, shallowRef } from 'vue'
2
+ import { useDataPackage } from './useDataPackage.js'
3
+
4
+ // The records of an entity are one thing: loaded once, from the chunk Vite
5
+ // emits for that one file, and shared by every page that asks for them. A
6
+ // website reads them through here and keeps no copy of its own — the alias
7
+ // `@inventory-data` is this package's, and a site file importing from it
8
+ // directly ships the whole dataset in its first bundle.
9
+
10
+ const records = new Map() // entity name → shallowRef(null | records)
11
+ const pending = new Map() // entity name → the load in flight
12
+ const indexes = new Map() // `${entity}.${key}` → computed Map
13
+
14
+ function refFor(name) {
15
+ let entry = records.get(name)
16
+ if (!entry) {
17
+ entry = shallowRef(null)
18
+ records.set(name, entry)
19
+ }
20
+ return entry
21
+ }
22
+
23
+ /**
24
+ * The shared ref of one entity, without asking for its load — `null` until a
25
+ * route's `meta.entities` or `loadEntities` brings the chunk in. This is what
26
+ * a website's data composable exports at module level, so that importing the
27
+ * composable loads nothing: the pages that read an entity are the ones that
28
+ * declare it.
29
+ */
30
+ export function entityRef(name) {
31
+ return refFor(name)
32
+ }
33
+
34
+ /**
35
+ * Load the named entities, once each. Resolves with their records in the
36
+ * order asked for; a name the package does not have rejects, as `loadEntity`
37
+ * does.
38
+ */
39
+ export function loadEntities(names) {
40
+ const { loadEntity } = useDataPackage()
41
+ return Promise.all(
42
+ names.map((name) => {
43
+ const entry = refFor(name)
44
+ if (entry.value !== null) return entry.value
45
+ let load = pending.get(name)
46
+ if (!load) {
47
+ load = loadEntity(name)
48
+ .then((data) => {
49
+ entry.value = data
50
+ pending.delete(name)
51
+ return data
52
+ })
53
+ .catch((error) => {
54
+ pending.delete(name)
55
+ throw error
56
+ })
57
+ pending.set(name, load)
58
+ }
59
+ return load
60
+ }),
61
+ )
62
+ }
63
+
64
+ /**
65
+ * A record map for one entity, keyed on `key` (the package id by default) —
66
+ * empty until the entity is loaded, and rebuilt if it ever reloads.
67
+ */
68
+ export function byId(name, key = 'id') {
69
+ const cacheKey = `${name}.${key}`
70
+ let index = indexes.get(cacheKey)
71
+ if (!index) {
72
+ const entry = refFor(name)
73
+ index = computed(() => new Map((entry.value ?? []).map((record) => [record[key], record])))
74
+ indexes.set(cacheKey, index)
75
+ }
76
+ return index
77
+ }
78
+
79
+ /**
80
+ * The standard way a website reads its records.
81
+ *
82
+ * `useEntities(['items', 'partners'])` returns one reactive ref per name —
83
+ * `null` until that entity's chunk has arrived, the records after — plus
84
+ * `ready`, a promise for all of them, `loaded`, the same as a boolean, and
85
+ * `byId`. The refs are the same objects for every caller, so a composable
86
+ * may export them at module level and a view may read them at any time.
87
+ *
88
+ * A route that declares `meta: { entities: [...] }` has them loaded by the
89
+ * router before its view is created, so that view never sees `null`.
90
+ */
91
+ export function useEntities(names) {
92
+ const list = Array.isArray(names) ? names : [names]
93
+ const result = { ready: loadEntities(list) }
94
+ for (const name of list) {
95
+ result[name] = refFor(name)
96
+ }
97
+ result.loaded = computed(() => list.every((name) => refFor(name).value !== null))
98
+ result.byId = byId
99
+ return result
100
+ }
@@ -1,71 +1,71 @@
1
- import { computed, ref, toValue, watch } from 'vue'
2
- import { useI18n } from '../i18n/index.js'
3
- import { isRtl } from '../i18n/language.js'
4
- import { useDataPackage } from './useDataPackage.js'
5
-
6
- const BASE_LANGUAGE = 'en'
7
-
8
- /**
9
- * The language a record is read in, given the languages it carries and the
10
- * language the website is in: the site language when the record has it,
11
- * English when it does not, the record's first language when it has neither.
12
- */
13
- export function resolveRecordLanguage(languages, siteLanguage) {
14
- const carried = languages ?? []
15
- if (carried.length === 0) return siteLanguage
16
- return (
17
- carried.find((code) => code === siteLanguage) ??
18
- carried.find((code) => code === BASE_LANGUAGE) ??
19
- carried[0]
20
- )
21
- }
22
-
23
- /**
24
- * The site language and the record language are two different things. The
25
- * site language is negotiated once and holds for the whole visit; a record
26
- * may carry languages the site does not offer, and the visitor may read the
27
- * record they are on in any of them. That choice is view state: it is not in
28
- * the URL, it does not touch the site language, and it is forgotten when the
29
- * site language changes or another record is shown.
30
- *
31
- * `record` is a ref, a computed or a getter to the record; its `languages`
32
- * field says what it carries. A record without one carries every language
33
- * `entity` has a translation file for, when `entity` is given; `languages`
34
- * (a getter) overrides both.
35
- */
36
- export function useRecordLanguage(record, { entity, languages: given } = {}) {
37
- const { locale } = useI18n()
38
- const { availableLanguages } = useDataPackage()
39
-
40
- const languages = computed(() => {
41
- const own = given ? toValue(given) : toValue(record)?.languages
42
- if (Array.isArray(own) && own.length > 0) return own
43
- return entity ? availableLanguages(entity) : []
44
- })
45
-
46
- const chosen = ref(null)
47
- const language = computed(() =>
48
- chosen.value && languages.value.includes(chosen.value)
49
- ? chosen.value
50
- : resolveRecordLanguage(languages.value, locale.value),
51
- )
52
- const dir = computed(() => (isRtl(language.value) ? 'rtl' : 'ltr'))
53
-
54
- function select(code) {
55
- if (languages.value.includes(code)) chosen.value = code
56
- }
57
- function reset() {
58
- chosen.value = null
59
- }
60
-
61
- watch(locale, reset)
62
- watch(
63
- () => {
64
- const current = toValue(record)
65
- return current?.id ?? current
66
- },
67
- reset,
68
- )
69
-
70
- return { language, languages, dir, select, reset }
71
- }
1
+ import { computed, ref, toValue, watch } from 'vue'
2
+ import { useI18n } from '../i18n/index.js'
3
+ import { isRtl } from '../i18n/language.js'
4
+ import { useDataPackage } from './useDataPackage.js'
5
+
6
+ const BASE_LANGUAGE = 'en'
7
+
8
+ /**
9
+ * The language a record is read in, given the languages it carries and the
10
+ * language the website is in: the site language when the record has it,
11
+ * English when it does not, the record's first language when it has neither.
12
+ */
13
+ export function resolveRecordLanguage(languages, siteLanguage) {
14
+ const carried = languages ?? []
15
+ if (carried.length === 0) return siteLanguage
16
+ return (
17
+ carried.find((code) => code === siteLanguage) ??
18
+ carried.find((code) => code === BASE_LANGUAGE) ??
19
+ carried[0]
20
+ )
21
+ }
22
+
23
+ /**
24
+ * The site language and the record language are two different things. The
25
+ * site language is negotiated once and holds for the whole visit; a record
26
+ * may carry languages the site does not offer, and the visitor may read the
27
+ * record they are on in any of them. That choice is view state: it is not in
28
+ * the URL, it does not touch the site language, and it is forgotten when the
29
+ * site language changes or another record is shown.
30
+ *
31
+ * `record` is a ref, a computed or a getter to the record; its `languages`
32
+ * field says what it carries. A record without one carries every language
33
+ * `entity` has a translation file for, when `entity` is given; `languages`
34
+ * (a getter) overrides both.
35
+ */
36
+ export function useRecordLanguage(record, { entity, languages: given } = {}) {
37
+ const { locale } = useI18n()
38
+ const { availableLanguages } = useDataPackage()
39
+
40
+ const languages = computed(() => {
41
+ const own = given ? toValue(given) : toValue(record)?.languages
42
+ if (Array.isArray(own) && own.length > 0) return own
43
+ return entity ? availableLanguages(entity) : []
44
+ })
45
+
46
+ const chosen = ref(null)
47
+ const language = computed(() =>
48
+ chosen.value && languages.value.includes(chosen.value)
49
+ ? chosen.value
50
+ : resolveRecordLanguage(languages.value, locale.value),
51
+ )
52
+ const dir = computed(() => (isRtl(language.value) ? 'rtl' : 'ltr'))
53
+
54
+ function select(code) {
55
+ if (languages.value.includes(code)) chosen.value = code
56
+ }
57
+ function reset() {
58
+ chosen.value = null
59
+ }
60
+
61
+ watch(locale, reset)
62
+ watch(
63
+ () => {
64
+ const current = toValue(record)
65
+ return current?.id ?? current
66
+ },
67
+ reset,
68
+ )
69
+
70
+ return { language, languages, dir, select, reset }
71
+ }