@canopy-iiif/app 1.6.16 → 1.6.18
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/lib/build/iiif.js +188 -6
- package/lib/components/metadata-index.js +32 -0
- package/package.json +1 -1
- package/ui/dist/server.mjs +373 -140
- package/ui/dist/server.mjs.map +4 -4
- package/ui/styles/components/_metadata-index.scss +69 -0
- package/ui/styles/components/index.scss +1 -0
- package/ui/styles/index.css +68 -0
package/lib/build/iiif.js
CHANGED
|
@@ -38,6 +38,10 @@ const IIIF_CACHE_DIR = path.resolve(".cache/iiif");
|
|
|
38
38
|
const IIIF_CACHE_MANIFESTS_DIR = path.join(IIIF_CACHE_DIR, "manifests");
|
|
39
39
|
const IIIF_CACHE_COLLECTIONS_DIR = path.join(IIIF_CACHE_DIR, "collections");
|
|
40
40
|
const IIIF_CACHE_COLLECTION = path.join(IIIF_CACHE_DIR, "collection.json");
|
|
41
|
+
const IIIF_METADATA_INDEX_CACHE = path.join(
|
|
42
|
+
IIIF_CACHE_DIR,
|
|
43
|
+
"metadata-index.json",
|
|
44
|
+
);
|
|
41
45
|
|
|
42
46
|
function relativeRuntimeScript(outPath, filename, versioned = false) {
|
|
43
47
|
if (!outPath || !filename) return null;
|
|
@@ -623,6 +627,117 @@ function extractMetadataValues(manifest, options = {}) {
|
|
|
623
627
|
return out;
|
|
624
628
|
}
|
|
625
629
|
|
|
630
|
+
function extractMetadataEntries(manifest, options = {}) {
|
|
631
|
+
const meta = Array.isArray(manifest && manifest.metadata)
|
|
632
|
+
? manifest.metadata
|
|
633
|
+
: [];
|
|
634
|
+
if (!meta.length) return [];
|
|
635
|
+
const includeAll = !!options.includeAll;
|
|
636
|
+
const labelsSet = includeAll
|
|
637
|
+
? null
|
|
638
|
+
: options && options.labelsSet instanceof Set
|
|
639
|
+
? options.labelsSet
|
|
640
|
+
: new Set();
|
|
641
|
+
const map = new Map();
|
|
642
|
+
const order = [];
|
|
643
|
+
for (const entry of meta) {
|
|
644
|
+
if (!entry) continue;
|
|
645
|
+
const label = firstLabelString(entry.label);
|
|
646
|
+
if (!label) continue;
|
|
647
|
+
const normalized = normalizeMetadataLabel(label);
|
|
648
|
+
if (!normalized) continue;
|
|
649
|
+
if (!includeAll && labelsSet && labelsSet.size && !labelsSet.has(normalized)) continue;
|
|
650
|
+
const values = [];
|
|
651
|
+
flattenMetadataValue(entry.value, values, 0);
|
|
652
|
+
const cleaned = [];
|
|
653
|
+
for (const val of values) {
|
|
654
|
+
const text = String(val || "").trim();
|
|
655
|
+
if (text) cleaned.push(text);
|
|
656
|
+
}
|
|
657
|
+
if (!cleaned.length) continue;
|
|
658
|
+
let record = map.get(normalized);
|
|
659
|
+
if (!record) {
|
|
660
|
+
record = {label, normalized, values: [], seen: new Set()};
|
|
661
|
+
map.set(normalized, record);
|
|
662
|
+
order.push(normalized);
|
|
663
|
+
} else if (!record.label && label) {
|
|
664
|
+
record.label = label;
|
|
665
|
+
}
|
|
666
|
+
for (const valueText of cleaned) {
|
|
667
|
+
if (record.seen.has(valueText)) continue;
|
|
668
|
+
record.seen.add(valueText);
|
|
669
|
+
record.values.push(valueText);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return order
|
|
673
|
+
.map((normalized) => map.get(normalized))
|
|
674
|
+
.filter(Boolean)
|
|
675
|
+
.map((record) => ({
|
|
676
|
+
label: record.label,
|
|
677
|
+
normalized: record.normalized,
|
|
678
|
+
values: record.values.slice(),
|
|
679
|
+
}));
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
function buildMetadataIndexPayload(map, explicitOrder, fallbackOrder) {
|
|
683
|
+
if (!map || !map.size) return [];
|
|
684
|
+
const ordered = [];
|
|
685
|
+
const seen = new Set();
|
|
686
|
+
const primaryOrder = Array.isArray(explicitOrder) ? explicitOrder : [];
|
|
687
|
+
const secondaryOrder = Array.isArray(fallbackOrder) ? fallbackOrder : [];
|
|
688
|
+
for (const normalized of primaryOrder) {
|
|
689
|
+
if (!normalized) continue;
|
|
690
|
+
const record = map.get(normalized);
|
|
691
|
+
if (!record) continue;
|
|
692
|
+
ordered.push(record);
|
|
693
|
+
seen.add(normalized);
|
|
694
|
+
}
|
|
695
|
+
for (const normalized of secondaryOrder) {
|
|
696
|
+
if (!normalized || seen.has(normalized)) continue;
|
|
697
|
+
const record = map.get(normalized);
|
|
698
|
+
if (!record) continue;
|
|
699
|
+
ordered.push(record);
|
|
700
|
+
seen.add(normalized);
|
|
701
|
+
}
|
|
702
|
+
map.forEach((record, normalized) => {
|
|
703
|
+
if (seen.has(normalized)) return;
|
|
704
|
+
ordered.push(record);
|
|
705
|
+
});
|
|
706
|
+
const payload = [];
|
|
707
|
+
for (const record of ordered) {
|
|
708
|
+
if (!record || !record.values) continue;
|
|
709
|
+
const values = Array.from(record.values.values()).sort((a, b) =>
|
|
710
|
+
String(a && a.value ? a.value : "").localeCompare(
|
|
711
|
+
String(b && b.value ? b.value : ""),
|
|
712
|
+
),
|
|
713
|
+
);
|
|
714
|
+
if (!values.length) continue;
|
|
715
|
+
payload.push({
|
|
716
|
+
label: record.label || record.slug || record.normalized,
|
|
717
|
+
slug: record.slug || record.normalized,
|
|
718
|
+
values,
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
return payload;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
async function writeMetadataIndexFile(payload) {
|
|
725
|
+
if (!payload || !payload.length) {
|
|
726
|
+
try {
|
|
727
|
+
await fsp.rm(IIIF_METADATA_INDEX_CACHE, {force: true});
|
|
728
|
+
} catch (_) {}
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
try {
|
|
732
|
+
ensureDirSync(path.dirname(IIIF_METADATA_INDEX_CACHE));
|
|
733
|
+
await fsp.writeFile(
|
|
734
|
+
IIIF_METADATA_INDEX_CACHE,
|
|
735
|
+
JSON.stringify(payload, null, 2),
|
|
736
|
+
"utf8",
|
|
737
|
+
);
|
|
738
|
+
} catch (_) {}
|
|
739
|
+
}
|
|
740
|
+
|
|
626
741
|
async function normalizeToV3(resource) {
|
|
627
742
|
try {
|
|
628
743
|
const helpers = await import("@iiif/helpers");
|
|
@@ -1521,11 +1636,10 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1521
1636
|
const metadataLabelsRaw = Array.isArray(cfg && cfg.metadata)
|
|
1522
1637
|
? cfg.metadata
|
|
1523
1638
|
: [];
|
|
1524
|
-
const
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
);
|
|
1639
|
+
const metadataLabelsNormalized = metadataLabelsRaw
|
|
1640
|
+
.map((label) => normalizeMetadataLabel(String(label || "")))
|
|
1641
|
+
.filter(Boolean);
|
|
1642
|
+
const metadataLabelSet = new Set(metadataLabelsNormalized);
|
|
1529
1643
|
const metadataFacetLabels = (() => {
|
|
1530
1644
|
if (!Array.isArray(metadataLabelsRaw) || !metadataLabelsRaw.length)
|
|
1531
1645
|
return [];
|
|
@@ -1544,6 +1658,11 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1544
1658
|
}
|
|
1545
1659
|
return entries;
|
|
1546
1660
|
})();
|
|
1661
|
+
const metadataLabelSlugMap = new Map();
|
|
1662
|
+
for (const entry of metadataFacetLabels) {
|
|
1663
|
+
if (!entry || !entry.normalized) continue;
|
|
1664
|
+
metadataLabelSlugMap.set(entry.normalized, entry.slug || "");
|
|
1665
|
+
}
|
|
1547
1666
|
const metadataOptions = {
|
|
1548
1667
|
enabled:
|
|
1549
1668
|
metadataEnabled &&
|
|
@@ -1551,6 +1670,51 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1551
1670
|
includeAll: metadataIncludeAll,
|
|
1552
1671
|
labelsSet: metadataIncludeAll ? null : metadataLabelSet,
|
|
1553
1672
|
};
|
|
1673
|
+
const metadataIndexMap = new Map();
|
|
1674
|
+
const metadataDynamicOrder = [];
|
|
1675
|
+
const metadataDynamicOrderSet = new Set();
|
|
1676
|
+
const metadataCollectAllLabels = metadataLabelsNormalized.length === 0;
|
|
1677
|
+
function recordMetadataIndexEntry(entry) {
|
|
1678
|
+
if (!entry || !entry.normalized || !Array.isArray(entry.values)) return;
|
|
1679
|
+
if (!entry.values.length) return;
|
|
1680
|
+
const normalized = entry.normalized;
|
|
1681
|
+
if (!metadataCollectAllLabels && !metadataLabelSet.has(normalized)) return;
|
|
1682
|
+
let record = metadataIndexMap.get(normalized);
|
|
1683
|
+
const labelText = entry.label || normalized;
|
|
1684
|
+
if (!record) {
|
|
1685
|
+
const labelSlug =
|
|
1686
|
+
metadataLabelSlugMap.get(normalized) ||
|
|
1687
|
+
slugify(labelText, {lower: true, strict: true, trim: true}) ||
|
|
1688
|
+
normalized;
|
|
1689
|
+
record = {
|
|
1690
|
+
normalized,
|
|
1691
|
+
label: labelText,
|
|
1692
|
+
slug: labelSlug || normalized,
|
|
1693
|
+
values: new Map(),
|
|
1694
|
+
};
|
|
1695
|
+
metadataIndexMap.set(normalized, record);
|
|
1696
|
+
if (metadataCollectAllLabels && !metadataDynamicOrderSet.has(normalized)) {
|
|
1697
|
+
metadataDynamicOrderSet.add(normalized);
|
|
1698
|
+
metadataDynamicOrder.push(normalized);
|
|
1699
|
+
}
|
|
1700
|
+
} else if (!record.label && labelText) {
|
|
1701
|
+
record.label = labelText;
|
|
1702
|
+
}
|
|
1703
|
+
const valueMap = record.values;
|
|
1704
|
+
for (const text of entry.values) {
|
|
1705
|
+
const trimmed = String(text || "").trim();
|
|
1706
|
+
if (!trimmed) continue;
|
|
1707
|
+
const valueSlug =
|
|
1708
|
+
slugify(trimmed, {lower: true, strict: true, trim: true}) ||
|
|
1709
|
+
trimmed.toLowerCase().replace(/[^a-z0-9]+/g, "-") ||
|
|
1710
|
+
trimmed;
|
|
1711
|
+
if (!valueSlug) continue;
|
|
1712
|
+
if (!valueMap.has(valueSlug)) {
|
|
1713
|
+
valueMap.set(valueSlug, {value: trimmed, slug: valueSlug});
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1554
1718
|
const summaryOptions = {
|
|
1555
1719
|
enabled: summaryEnabled,
|
|
1556
1720
|
};
|
|
@@ -2308,6 +2472,15 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2308
2472
|
let metadataValues = [];
|
|
2309
2473
|
let summaryValue = "";
|
|
2310
2474
|
let annotationValue = "";
|
|
2475
|
+
try {
|
|
2476
|
+
const metadataEntries = extractMetadataEntries(manifest, {
|
|
2477
|
+
includeAll: metadataCollectAllLabels,
|
|
2478
|
+
labelsSet: metadataLabelSet,
|
|
2479
|
+
});
|
|
2480
|
+
if (metadataEntries && metadataEntries.length) {
|
|
2481
|
+
for (const entry of metadataEntries) recordMetadataIndexEntry(entry);
|
|
2482
|
+
}
|
|
2483
|
+
} catch (_) {}
|
|
2311
2484
|
if (metadataOptions && metadataOptions.enabled) {
|
|
2312
2485
|
try {
|
|
2313
2486
|
metadataValues = extractMetadataValues(manifest, metadataOptions);
|
|
@@ -2436,10 +2609,18 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2436
2609
|
logLine(
|
|
2437
2610
|
`IIIF chunk summary: ${totalItems} Manifest(s) in ${formatDurationMs(totalDuration)} (avg chunk ${formatDurationMs(avgDuration)}, ${rateLabel})`,
|
|
2438
2611
|
"cyan",
|
|
2439
|
-
|
|
2612
|
+
{dim: true},
|
|
2440
2613
|
);
|
|
2441
2614
|
} catch (_) {}
|
|
2442
2615
|
}
|
|
2616
|
+
try {
|
|
2617
|
+
const metadataIndexPayload = buildMetadataIndexPayload(
|
|
2618
|
+
metadataIndexMap,
|
|
2619
|
+
metadataLabelsNormalized,
|
|
2620
|
+
metadataDynamicOrder,
|
|
2621
|
+
);
|
|
2622
|
+
await writeMetadataIndexFile(metadataIndexPayload);
|
|
2623
|
+
} catch (_) {}
|
|
2443
2624
|
try {
|
|
2444
2625
|
await navPlace.writeNavPlaceDataset(navPlaceRecords);
|
|
2445
2626
|
try {
|
|
@@ -2492,6 +2673,7 @@ module.exports.__TESTING__ = {
|
|
|
2492
2673
|
extractSummaryValues,
|
|
2493
2674
|
truncateSummary,
|
|
2494
2675
|
extractMetadataValues,
|
|
2676
|
+
extractMetadataEntries,
|
|
2495
2677
|
extractAnnotationText,
|
|
2496
2678
|
normalizeIiifId,
|
|
2497
2679
|
normalizeIiifType,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const METADATA_INDEX_PATH = path.resolve('.cache/iiif/metadata-index.json');
|
|
5
|
+
|
|
6
|
+
let cachedIndex = null;
|
|
7
|
+
|
|
8
|
+
function readMetadataIndexFromDisk() {
|
|
9
|
+
try {
|
|
10
|
+
if (!fs.existsSync(METADATA_INDEX_PATH)) return [];
|
|
11
|
+
const raw = fs.readFileSync(METADATA_INDEX_PATH, 'utf8');
|
|
12
|
+
const data = JSON.parse(raw);
|
|
13
|
+
return Array.isArray(data) ? data : [];
|
|
14
|
+
} catch (_) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getMetadataIndex() {
|
|
20
|
+
if (cachedIndex) return cachedIndex;
|
|
21
|
+
cachedIndex = readMetadataIndexFromDisk();
|
|
22
|
+
return cachedIndex;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function resetMetadataIndex() {
|
|
26
|
+
cachedIndex = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
getMetadataIndex,
|
|
31
|
+
resetMetadataIndex,
|
|
32
|
+
};
|