@canopy-iiif/app 1.5.17 → 1.6.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.
- package/lib/build/build.js +129 -69
- package/lib/build/iiif.js +366 -261
- package/lib/orchestrator.js +9 -0
- package/lib/search/search.js +27 -0
- package/package.json +1 -1
package/lib/build/iiif.js
CHANGED
|
@@ -17,7 +17,7 @@ const {
|
|
|
17
17
|
const {resolveCanopyConfigPath} = require("../config-path");
|
|
18
18
|
const mdx = require("./mdx");
|
|
19
19
|
const {log, logLine, logResponse} = require("./log");
|
|
20
|
-
const {
|
|
20
|
+
const {getPageContext} = require("../page-context");
|
|
21
21
|
const PageContext = getPageContext();
|
|
22
22
|
const referenced = require("../components/referenced");
|
|
23
23
|
const navPlace = require("../components/nav-place");
|
|
@@ -41,11 +41,11 @@ const IIIF_CACHE_INDEX = path.join(IIIF_CACHE_DIR, "index.json");
|
|
|
41
41
|
// Additional legacy locations kept for backward compatibility (read + optional write)
|
|
42
42
|
const IIIF_CACHE_INDEX_LEGACY = path.join(
|
|
43
43
|
IIIF_CACHE_DIR,
|
|
44
|
-
"manifest-index.json"
|
|
44
|
+
"manifest-index.json",
|
|
45
45
|
);
|
|
46
46
|
const IIIF_CACHE_INDEX_MANIFESTS = path.join(
|
|
47
47
|
IIIF_CACHE_MANIFESTS_DIR,
|
|
48
|
-
"manifest-index.json"
|
|
48
|
+
"manifest-index.json",
|
|
49
49
|
);
|
|
50
50
|
|
|
51
51
|
const DEFAULT_THUMBNAIL_SIZE = 400;
|
|
@@ -57,6 +57,14 @@ const OG_IMAGE_WIDTH = 1200;
|
|
|
57
57
|
const OG_IMAGE_HEIGHT = 630;
|
|
58
58
|
const HERO_REPRESENTATIVE_SIZE = Math.max(HERO_THUMBNAIL_SIZE, OG_IMAGE_WIDTH);
|
|
59
59
|
const MAX_ENTRY_SLUG_LENGTH = 50;
|
|
60
|
+
const DEBUG_IIIF = process.env.CANOPY_IIIF_DEBUG === "1";
|
|
61
|
+
|
|
62
|
+
function logDebug(message) {
|
|
63
|
+
if (!DEBUG_IIIF) return;
|
|
64
|
+
try {
|
|
65
|
+
logLine(`[IIIF][debug] ${message}`, "magenta", {dim: true});
|
|
66
|
+
} catch (_) {}
|
|
67
|
+
}
|
|
60
68
|
|
|
61
69
|
function resolvePositiveInteger(value, fallback, options = {}) {
|
|
62
70
|
const allowZero = Boolean(options && options.allowZero);
|
|
@@ -71,7 +79,7 @@ function resolvePositiveInteger(value, fallback, options = {}) {
|
|
|
71
79
|
}
|
|
72
80
|
|
|
73
81
|
function formatDurationMs(ms) {
|
|
74
|
-
if (!Number.isFinite(ms) || ms < 0) return
|
|
82
|
+
if (!Number.isFinite(ms) || ms < 0) return "0ms";
|
|
75
83
|
if (ms >= 1000) return `${(ms / 1000).toFixed(1)}s`;
|
|
76
84
|
return `${Math.round(ms)}ms`;
|
|
77
85
|
}
|
|
@@ -99,6 +107,32 @@ function normalizeCollectionUris(value) {
|
|
|
99
107
|
return uris;
|
|
100
108
|
}
|
|
101
109
|
|
|
110
|
+
function normalizeManifestConfig(cfg) {
|
|
111
|
+
if (!cfg || typeof cfg !== "object") return [];
|
|
112
|
+
const entries = [];
|
|
113
|
+
const push = (value) => {
|
|
114
|
+
if (value === undefined || value === null) return;
|
|
115
|
+
if (Array.isArray(value)) entries.push(...value);
|
|
116
|
+
else entries.push(value);
|
|
117
|
+
};
|
|
118
|
+
push(cfg.manifest);
|
|
119
|
+
push(cfg.manifests);
|
|
120
|
+
if (!entries.length) return [];
|
|
121
|
+
return normalizeCollectionUris(entries);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function resolveIiifSources(cfg) {
|
|
125
|
+
const safeCfg = cfg && typeof cfg === "object" ? cfg : {};
|
|
126
|
+
let collectionUris = normalizeCollectionUris(safeCfg.collection);
|
|
127
|
+
if (!collectionUris.length) {
|
|
128
|
+
collectionUris = normalizeCollectionUris(
|
|
129
|
+
process.env.CANOPY_COLLECTION_URI || "",
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const manifestUris = normalizeManifestConfig(safeCfg);
|
|
133
|
+
return {collections: collectionUris, manifests: manifestUris};
|
|
134
|
+
}
|
|
135
|
+
|
|
102
136
|
function clampSlugLength(slug, limit = MAX_ENTRY_SLUG_LENGTH) {
|
|
103
137
|
if (!slug) return "";
|
|
104
138
|
const max = Math.max(1, limit);
|
|
@@ -132,8 +166,8 @@ function extractHomepageId(resource) {
|
|
|
132
166
|
const list = Array.isArray(homepageRaw)
|
|
133
167
|
? homepageRaw
|
|
134
168
|
: homepageRaw
|
|
135
|
-
|
|
136
|
-
|
|
169
|
+
? [homepageRaw]
|
|
170
|
+
: [];
|
|
137
171
|
for (const entry of list) {
|
|
138
172
|
if (!entry) continue;
|
|
139
173
|
if (typeof entry === "string") {
|
|
@@ -207,7 +241,7 @@ function resolveThumbnailPreferences() {
|
|
|
207
241
|
return {
|
|
208
242
|
size: resolvePositiveInteger(
|
|
209
243
|
process.env.CANOPY_THUMBNAIL_SIZE,
|
|
210
|
-
DEFAULT_THUMBNAIL_SIZE
|
|
244
|
+
DEFAULT_THUMBNAIL_SIZE,
|
|
211
245
|
),
|
|
212
246
|
unsafe: resolveBoolean(process.env.CANOPY_THUMBNAILS_UNSAFE),
|
|
213
247
|
};
|
|
@@ -255,7 +289,7 @@ async function resolveHeroMedia(manifest) {
|
|
|
255
289
|
const manifestThumb = extractResourceThumbnail(manifest);
|
|
256
290
|
const heroSource = (() => {
|
|
257
291
|
if (manifest && manifest.thumbnail) {
|
|
258
|
-
const clone = {
|
|
292
|
+
const clone = {...manifest};
|
|
259
293
|
try {
|
|
260
294
|
delete clone.thumbnail;
|
|
261
295
|
} catch (_) {
|
|
@@ -268,53 +302,49 @@ async function resolveHeroMedia(manifest) {
|
|
|
268
302
|
const heroRep = await getRepresentativeImage(
|
|
269
303
|
heroSource || manifest,
|
|
270
304
|
HERO_REPRESENTATIVE_SIZE,
|
|
271
|
-
true
|
|
305
|
+
true,
|
|
272
306
|
);
|
|
273
307
|
const canvasImage = findPrimaryCanvasImage(manifest);
|
|
274
308
|
const heroService =
|
|
275
|
-
(canvasImage && canvasImage.service) ||
|
|
276
|
-
(heroRep && heroRep.service);
|
|
309
|
+
(canvasImage && canvasImage.service) || (heroRep && heroRep.service);
|
|
277
310
|
const serviceIsLevel0 = isLevel0Service(heroService);
|
|
278
311
|
const heroPreferred = buildIiifImageUrlFromService(
|
|
279
312
|
serviceIsLevel0 ? null : heroService,
|
|
280
|
-
HERO_THUMBNAIL_SIZE
|
|
313
|
+
HERO_THUMBNAIL_SIZE,
|
|
281
314
|
);
|
|
282
315
|
const heroWidth = (() => {
|
|
283
|
-
if (canvasImage && typeof canvasImage.width ===
|
|
316
|
+
if (canvasImage && typeof canvasImage.width === "number")
|
|
284
317
|
return canvasImage.width;
|
|
285
|
-
if (heroRep && typeof heroRep.width ===
|
|
318
|
+
if (heroRep && typeof heroRep.width === "number") return heroRep.width;
|
|
286
319
|
return undefined;
|
|
287
320
|
})();
|
|
288
321
|
const heroHeight = (() => {
|
|
289
|
-
if (canvasImage && typeof canvasImage.height ===
|
|
322
|
+
if (canvasImage && typeof canvasImage.height === "number")
|
|
290
323
|
return canvasImage.height;
|
|
291
|
-
if (heroRep && typeof heroRep.height ===
|
|
292
|
-
return heroRep.height;
|
|
324
|
+
if (heroRep && typeof heroRep.height === "number") return heroRep.height;
|
|
293
325
|
return undefined;
|
|
294
326
|
})();
|
|
295
|
-
const heroSrcset = serviceIsLevel0
|
|
296
|
-
? ''
|
|
297
|
-
: buildIiifImageSrcset(heroService);
|
|
327
|
+
const heroSrcset = serviceIsLevel0 ? "" : buildIiifImageSrcset(heroService);
|
|
298
328
|
const ogFromService =
|
|
299
329
|
!serviceIsLevel0 && heroService
|
|
300
330
|
? buildIiifImageUrlForDimensions(
|
|
301
331
|
heroService,
|
|
302
332
|
OG_IMAGE_WIDTH,
|
|
303
|
-
OG_IMAGE_HEIGHT
|
|
333
|
+
OG_IMAGE_HEIGHT,
|
|
304
334
|
)
|
|
305
|
-
:
|
|
335
|
+
: "";
|
|
306
336
|
const annotationImageId =
|
|
307
337
|
canvasImage && canvasImage.isImageBody && canvasImage.id
|
|
308
338
|
? String(canvasImage.id)
|
|
309
|
-
:
|
|
310
|
-
let heroThumbnail = heroPreferred ||
|
|
339
|
+
: "";
|
|
340
|
+
let heroThumbnail = heroPreferred || "";
|
|
311
341
|
let heroThumbWidth = heroWidth;
|
|
312
342
|
let heroThumbHeight = heroHeight;
|
|
313
343
|
if (!heroThumbnail && manifestThumb && manifestThumb.url) {
|
|
314
344
|
heroThumbnail = manifestThumb.url;
|
|
315
|
-
if (typeof manifestThumb.width ===
|
|
345
|
+
if (typeof manifestThumb.width === "number")
|
|
316
346
|
heroThumbWidth = manifestThumb.width;
|
|
317
|
-
if (typeof manifestThumb.height ===
|
|
347
|
+
if (typeof manifestThumb.height === "number")
|
|
318
348
|
heroThumbHeight = manifestThumb.height;
|
|
319
349
|
}
|
|
320
350
|
if (!heroThumbnail) {
|
|
@@ -324,7 +354,7 @@ async function resolveHeroMedia(manifest) {
|
|
|
324
354
|
heroThumbnail = String(heroRep.id);
|
|
325
355
|
}
|
|
326
356
|
}
|
|
327
|
-
let ogImage =
|
|
357
|
+
let ogImage = "";
|
|
328
358
|
let ogImageWidth;
|
|
329
359
|
let ogImageHeight;
|
|
330
360
|
if (ogFromService) {
|
|
@@ -333,16 +363,16 @@ async function resolveHeroMedia(manifest) {
|
|
|
333
363
|
ogImageHeight = OG_IMAGE_HEIGHT;
|
|
334
364
|
} else if (heroThumbnail) {
|
|
335
365
|
ogImage = heroThumbnail;
|
|
336
|
-
if (typeof heroThumbWidth ===
|
|
337
|
-
if (typeof heroThumbHeight ===
|
|
366
|
+
if (typeof heroThumbWidth === "number") ogImageWidth = heroThumbWidth;
|
|
367
|
+
if (typeof heroThumbHeight === "number") ogImageHeight = heroThumbHeight;
|
|
338
368
|
}
|
|
339
369
|
return {
|
|
340
|
-
heroThumbnail: heroThumbnail ||
|
|
370
|
+
heroThumbnail: heroThumbnail || "",
|
|
341
371
|
heroThumbnailWidth: heroThumbWidth,
|
|
342
372
|
heroThumbnailHeight: heroThumbHeight,
|
|
343
|
-
heroThumbnailSrcset: heroSrcset ||
|
|
344
|
-
heroThumbnailSizes: heroSrcset ? HERO_IMAGE_SIZES_ATTR :
|
|
345
|
-
ogImage: ogImage ||
|
|
373
|
+
heroThumbnailSrcset: heroSrcset || "",
|
|
374
|
+
heroThumbnailSizes: heroSrcset ? HERO_IMAGE_SIZES_ATTR : "",
|
|
375
|
+
ogImage: ogImage || "",
|
|
346
376
|
ogImageWidth,
|
|
347
377
|
ogImageHeight,
|
|
348
378
|
};
|
|
@@ -409,7 +439,7 @@ function extractSummaryValues(manifest) {
|
|
|
409
439
|
flattenMetadataValue(manifest && manifest.summary, values, 0);
|
|
410
440
|
} catch (_) {}
|
|
411
441
|
const unique = Array.from(
|
|
412
|
-
new Set(values.map((val) => String(val || "").trim()).filter(Boolean))
|
|
442
|
+
new Set(values.map((val) => String(val || "").trim()).filter(Boolean)),
|
|
413
443
|
);
|
|
414
444
|
if (!unique.length) return "";
|
|
415
445
|
return unique.join(" ");
|
|
@@ -627,7 +657,7 @@ function normalizeIiifId(raw) {
|
|
|
627
657
|
if (!/^https?:\/\//i.test(s)) return s;
|
|
628
658
|
const u = new URL(s);
|
|
629
659
|
const entries = Array.from(u.searchParams.entries()).sort(
|
|
630
|
-
(a, b) => a[0].localeCompare(b[0]) || a[1].localeCompare(b[1])
|
|
660
|
+
(a, b) => a[0].localeCompare(b[0]) || a[1].localeCompare(b[1]),
|
|
631
661
|
);
|
|
632
662
|
u.search = "";
|
|
633
663
|
for (const [k, v] of entries) u.searchParams.append(k, v);
|
|
@@ -841,18 +871,21 @@ function computeUniqueSlug(index, baseSlug, id, type) {
|
|
|
841
871
|
const byId = Array.isArray(index && index.byId) ? index.byId : [];
|
|
842
872
|
const normId = normalizeIiifId(String(id || ""));
|
|
843
873
|
const fallbackBase = type === "Manifest" ? "untitled" : "collection";
|
|
844
|
-
const normalizedBase = normalizeSlugBase(
|
|
874
|
+
const normalizedBase = normalizeSlugBase(
|
|
875
|
+
baseSlug || fallbackBase,
|
|
876
|
+
fallbackBase,
|
|
877
|
+
);
|
|
845
878
|
const used = new Set(
|
|
846
879
|
byId
|
|
847
880
|
.filter((e) => e && e.slug && e.type === type)
|
|
848
|
-
.map((e) => String(e.slug))
|
|
881
|
+
.map((e) => String(e.slug)),
|
|
849
882
|
);
|
|
850
883
|
const reserved = RESERVED_SLUGS[type] || new Set();
|
|
851
884
|
let slug = normalizedBase;
|
|
852
885
|
let i = 1;
|
|
853
886
|
for (;;) {
|
|
854
887
|
const existing = byId.find(
|
|
855
|
-
(e) => e && e.type === type && String(e.slug) === String(slug)
|
|
888
|
+
(e) => e && e.type === type && String(e.slug) === String(slug),
|
|
856
889
|
);
|
|
857
890
|
if (existing) {
|
|
858
891
|
// If this slug already maps to this id, reuse it and reserve.
|
|
@@ -874,9 +907,12 @@ function ensureBaseSlugFor(index, baseSlug, id, type) {
|
|
|
874
907
|
const byId = Array.isArray(index && index.byId) ? index.byId : [];
|
|
875
908
|
const normId = normalizeIiifId(String(id || ""));
|
|
876
909
|
const fallbackBase = type === "Manifest" ? "untitled" : "collection";
|
|
877
|
-
const normalizedBase = normalizeSlugBase(
|
|
910
|
+
const normalizedBase = normalizeSlugBase(
|
|
911
|
+
baseSlug || fallbackBase,
|
|
912
|
+
fallbackBase,
|
|
913
|
+
);
|
|
878
914
|
const existingWithBase = byId.find(
|
|
879
|
-
(e) => e && e.type === type && String(e.slug) === String(normalizedBase)
|
|
915
|
+
(e) => e && e.type === type && String(e.slug) === String(normalizedBase),
|
|
880
916
|
);
|
|
881
917
|
if (existingWithBase && normalizeIiifId(existingWithBase.id) !== normId) {
|
|
882
918
|
// Reassign the existing entry to the next available suffix to free the base
|
|
@@ -884,9 +920,10 @@ function ensureBaseSlugFor(index, baseSlug, id, type) {
|
|
|
884
920
|
index,
|
|
885
921
|
normalizedBase,
|
|
886
922
|
existingWithBase.id,
|
|
887
|
-
type
|
|
923
|
+
type,
|
|
888
924
|
);
|
|
889
|
-
if (newSlug && newSlug !== normalizedBase)
|
|
925
|
+
if (newSlug && newSlug !== normalizedBase)
|
|
926
|
+
existingWithBase.slug = newSlug;
|
|
890
927
|
}
|
|
891
928
|
} catch (_) {}
|
|
892
929
|
return baseSlug;
|
|
@@ -903,7 +940,7 @@ async function findSlugByIdFromDisk(id) {
|
|
|
903
940
|
const raw = await fsp.readFile(p, "utf8");
|
|
904
941
|
const obj = JSON.parse(raw);
|
|
905
942
|
const mid = normalizeIiifId(
|
|
906
|
-
String((obj && (obj.id || obj["@id"])) || "")
|
|
943
|
+
String((obj && (obj.id || obj["@id"])) || ""),
|
|
907
944
|
);
|
|
908
945
|
if (mid && mid === normalizeIiifId(String(id))) {
|
|
909
946
|
const slug = name.replace(/\.json$/i, "");
|
|
@@ -923,7 +960,7 @@ async function loadCachedManifestById(id) {
|
|
|
923
960
|
if (Array.isArray(index.byId)) {
|
|
924
961
|
const nid = normalizeIiifId(id);
|
|
925
962
|
const entry = index.byId.find(
|
|
926
|
-
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Manifest"
|
|
963
|
+
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Manifest",
|
|
927
964
|
);
|
|
928
965
|
slug = entry && entry.slug;
|
|
929
966
|
}
|
|
@@ -943,7 +980,8 @@ async function loadCachedManifestById(id) {
|
|
|
943
980
|
index.byId = Array.isArray(index.byId) ? index.byId : [];
|
|
944
981
|
const nid = normalizeIiifId(id);
|
|
945
982
|
const existingEntryIdx = index.byId.findIndex(
|
|
946
|
-
(e) =>
|
|
983
|
+
(e) =>
|
|
984
|
+
e && normalizeIiifId(e.id) === nid && e.type === "Manifest",
|
|
947
985
|
);
|
|
948
986
|
const entry = {
|
|
949
987
|
id: String(nid),
|
|
@@ -963,7 +1001,8 @@ async function loadCachedManifestById(id) {
|
|
|
963
1001
|
const p = path.join(IIIF_CACHE_MANIFESTS_DIR, slug + ".json");
|
|
964
1002
|
if (!fs.existsSync(p)) return null;
|
|
965
1003
|
const raw = await readJson(p);
|
|
966
|
-
const {manifest: normalized, changed} =
|
|
1004
|
+
const {manifest: normalized, changed} =
|
|
1005
|
+
await ensurePresentation3Manifest(raw);
|
|
967
1006
|
if (changed) {
|
|
968
1007
|
try {
|
|
969
1008
|
await fsp.writeFile(p, JSON.stringify(normalized, null, 2), "utf8");
|
|
@@ -973,12 +1012,17 @@ async function loadCachedManifestById(id) {
|
|
|
973
1012
|
index.byId = Array.isArray(index.byId) ? index.byId : [];
|
|
974
1013
|
const nid = normalizeIiifId(id);
|
|
975
1014
|
const existingEntryIdx = index.byId.findIndex(
|
|
976
|
-
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Manifest"
|
|
1015
|
+
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Manifest",
|
|
977
1016
|
);
|
|
978
1017
|
if (existingEntryIdx >= 0) {
|
|
979
1018
|
const entry = index.byId[existingEntryIdx];
|
|
980
|
-
const prevCanonical =
|
|
981
|
-
|
|
1019
|
+
const prevCanonical =
|
|
1020
|
+
entry && entry.canonical ? String(entry.canonical) : "";
|
|
1021
|
+
const nextCanonical = applyManifestEntryCanonical(
|
|
1022
|
+
entry,
|
|
1023
|
+
normalized,
|
|
1024
|
+
slug,
|
|
1025
|
+
);
|
|
982
1026
|
if (nextCanonical !== prevCanonical) {
|
|
983
1027
|
await saveManifestIndex(index);
|
|
984
1028
|
}
|
|
@@ -991,21 +1035,28 @@ async function loadCachedManifestById(id) {
|
|
|
991
1035
|
}
|
|
992
1036
|
|
|
993
1037
|
async function saveCachedManifest(manifest, id, parentId) {
|
|
994
|
-
const {manifest: normalizedManifest} =
|
|
1038
|
+
const {manifest: normalizedManifest} =
|
|
1039
|
+
await ensurePresentation3Manifest(manifest);
|
|
995
1040
|
try {
|
|
996
1041
|
const index = await loadManifestIndex();
|
|
997
|
-
const title = firstLabelString(
|
|
1042
|
+
const title = firstLabelString(
|
|
1043
|
+
normalizedManifest && normalizedManifest.label,
|
|
1044
|
+
);
|
|
998
1045
|
const baseSlug =
|
|
999
1046
|
slugify(title || "untitled", {lower: true, strict: true, trim: true}) ||
|
|
1000
1047
|
"untitled";
|
|
1001
1048
|
const slug = computeUniqueSlug(index, baseSlug, id, "Manifest");
|
|
1002
1049
|
ensureDirSync(IIIF_CACHE_MANIFESTS_DIR);
|
|
1003
1050
|
const dest = path.join(IIIF_CACHE_MANIFESTS_DIR, slug + ".json");
|
|
1004
|
-
await fsp.writeFile(
|
|
1051
|
+
await fsp.writeFile(
|
|
1052
|
+
dest,
|
|
1053
|
+
JSON.stringify(normalizedManifest, null, 2),
|
|
1054
|
+
"utf8",
|
|
1055
|
+
);
|
|
1005
1056
|
index.byId = Array.isArray(index.byId) ? index.byId : [];
|
|
1006
1057
|
const nid = normalizeIiifId(id);
|
|
1007
1058
|
const existingEntryIdx = index.byId.findIndex(
|
|
1008
|
-
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Manifest"
|
|
1059
|
+
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Manifest",
|
|
1009
1060
|
);
|
|
1010
1061
|
const entry = {
|
|
1011
1062
|
id: String(nid),
|
|
@@ -1056,7 +1107,7 @@ async function ensureFeaturedInCache(cfg) {
|
|
|
1056
1107
|
e &&
|
|
1057
1108
|
e.type === "Manifest" &&
|
|
1058
1109
|
normalizeIiifId(String(e.id)) ===
|
|
1059
|
-
normalizeIiifId(String(manifest.id))
|
|
1110
|
+
normalizeIiifId(String(manifest.id)),
|
|
1060
1111
|
);
|
|
1061
1112
|
if (!entry) continue;
|
|
1062
1113
|
|
|
@@ -1108,7 +1159,8 @@ async function ensureFeaturedInCache(cfg) {
|
|
|
1108
1159
|
if (entry.heroThumbnailSrcset !== heroMedia.heroThumbnailSrcset)
|
|
1109
1160
|
touched = true;
|
|
1110
1161
|
entry.heroThumbnailSrcset = heroMedia.heroThumbnailSrcset;
|
|
1111
|
-
if (entry.heroThumbnailSizes !== HERO_IMAGE_SIZES_ATTR)
|
|
1162
|
+
if (entry.heroThumbnailSizes !== HERO_IMAGE_SIZES_ATTR)
|
|
1163
|
+
touched = true;
|
|
1112
1164
|
entry.heroThumbnailSizes = HERO_IMAGE_SIZES_ATTR;
|
|
1113
1165
|
} else {
|
|
1114
1166
|
if (entry.heroThumbnailSrcset !== undefined) {
|
|
@@ -1125,15 +1177,16 @@ async function ensureFeaturedInCache(cfg) {
|
|
|
1125
1177
|
entry.ogImage = heroMedia.ogImage;
|
|
1126
1178
|
touched = true;
|
|
1127
1179
|
}
|
|
1128
|
-
if (typeof heroMedia.ogImageWidth ===
|
|
1180
|
+
if (typeof heroMedia.ogImageWidth === "number") {
|
|
1129
1181
|
if (entry.ogImageWidth !== heroMedia.ogImageWidth) touched = true;
|
|
1130
1182
|
entry.ogImageWidth = heroMedia.ogImageWidth;
|
|
1131
1183
|
} else if (entry.ogImageWidth !== undefined) {
|
|
1132
1184
|
delete entry.ogImageWidth;
|
|
1133
1185
|
touched = true;
|
|
1134
1186
|
}
|
|
1135
|
-
if (typeof heroMedia.ogImageHeight ===
|
|
1136
|
-
if (entry.ogImageHeight !== heroMedia.ogImageHeight)
|
|
1187
|
+
if (typeof heroMedia.ogImageHeight === "number") {
|
|
1188
|
+
if (entry.ogImageHeight !== heroMedia.ogImageHeight)
|
|
1189
|
+
touched = true;
|
|
1137
1190
|
entry.ogImageHeight = heroMedia.ogImageHeight;
|
|
1138
1191
|
} else if (entry.ogImageHeight !== undefined) {
|
|
1139
1192
|
delete entry.ogImageHeight;
|
|
@@ -1150,7 +1203,7 @@ async function ensureFeaturedInCache(cfg) {
|
|
|
1150
1203
|
entry,
|
|
1151
1204
|
heroMedia && heroMedia.heroThumbnail,
|
|
1152
1205
|
heroMedia && heroMedia.heroThumbnailWidth,
|
|
1153
|
-
heroMedia && heroMedia.heroThumbnailHeight
|
|
1206
|
+
heroMedia && heroMedia.heroThumbnailHeight,
|
|
1154
1207
|
)
|
|
1155
1208
|
) {
|
|
1156
1209
|
touched = true;
|
|
@@ -1187,7 +1240,7 @@ async function loadCachedCollectionById(id) {
|
|
|
1187
1240
|
if (Array.isArray(index.byId)) {
|
|
1188
1241
|
const nid = normalizeIiifId(id);
|
|
1189
1242
|
const entry = index.byId.find(
|
|
1190
|
-
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Collection"
|
|
1243
|
+
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Collection",
|
|
1191
1244
|
);
|
|
1192
1245
|
slug = entry && entry.slug;
|
|
1193
1246
|
}
|
|
@@ -1204,7 +1257,7 @@ async function loadCachedCollectionById(id) {
|
|
|
1204
1257
|
const raw = await fsp.readFile(p, "utf8");
|
|
1205
1258
|
const obj = JSON.parse(raw);
|
|
1206
1259
|
const cid = normalizeIiifId(
|
|
1207
|
-
String((obj && (obj.id || obj["@id"])) || "")
|
|
1260
|
+
String((obj && (obj.id || obj["@id"])) || ""),
|
|
1208
1261
|
);
|
|
1209
1262
|
if (cid && cid === normalizeIiifId(String(id))) {
|
|
1210
1263
|
const candidate = name.replace(/\.json$/i, "");
|
|
@@ -1221,7 +1274,7 @@ async function loadCachedCollectionById(id) {
|
|
|
1221
1274
|
(e) =>
|
|
1222
1275
|
e &&
|
|
1223
1276
|
normalizeIiifId(e.id) === nid &&
|
|
1224
|
-
e.type === "Collection"
|
|
1277
|
+
e.type === "Collection",
|
|
1225
1278
|
);
|
|
1226
1279
|
const entry = {
|
|
1227
1280
|
id: String(nid),
|
|
@@ -1249,11 +1302,12 @@ async function loadCachedCollectionById(id) {
|
|
|
1249
1302
|
index.byId = Array.isArray(index.byId) ? index.byId : [];
|
|
1250
1303
|
const nid = normalizeIiifId(id);
|
|
1251
1304
|
const existingEntryIdx = index.byId.findIndex(
|
|
1252
|
-
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Collection"
|
|
1305
|
+
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Collection",
|
|
1253
1306
|
);
|
|
1254
1307
|
if (existingEntryIdx >= 0) {
|
|
1255
1308
|
const entry = index.byId[existingEntryIdx];
|
|
1256
|
-
const prevCanonical =
|
|
1309
|
+
const prevCanonical =
|
|
1310
|
+
entry && entry.canonical ? String(entry.canonical) : "";
|
|
1257
1311
|
const nextCanonical = applyCollectionEntryCanonical(entry, data);
|
|
1258
1312
|
if (nextCanonical !== prevCanonical) {
|
|
1259
1313
|
await saveManifestIndex(index);
|
|
@@ -1271,7 +1325,9 @@ async function saveCachedCollection(collection, id, parentId) {
|
|
|
1271
1325
|
const normalizedCollection = await upgradeIiifResource(collection);
|
|
1272
1326
|
ensureDirSync(IIIF_CACHE_COLLECTIONS_DIR);
|
|
1273
1327
|
const index = await loadManifestIndex();
|
|
1274
|
-
const title = firstLabelString(
|
|
1328
|
+
const title = firstLabelString(
|
|
1329
|
+
normalizedCollection && normalizedCollection.label,
|
|
1330
|
+
);
|
|
1275
1331
|
const baseSlug =
|
|
1276
1332
|
slugify(title || "collection", {
|
|
1277
1333
|
lower: true,
|
|
@@ -1280,7 +1336,11 @@ async function saveCachedCollection(collection, id, parentId) {
|
|
|
1280
1336
|
}) || "collection";
|
|
1281
1337
|
const slug = computeUniqueSlug(index, baseSlug, id, "Collection");
|
|
1282
1338
|
const dest = path.join(IIIF_CACHE_COLLECTIONS_DIR, slug + ".json");
|
|
1283
|
-
await fsp.writeFile(
|
|
1339
|
+
await fsp.writeFile(
|
|
1340
|
+
dest,
|
|
1341
|
+
JSON.stringify(normalizedCollection, null, 2),
|
|
1342
|
+
"utf8",
|
|
1343
|
+
);
|
|
1284
1344
|
try {
|
|
1285
1345
|
if (process.env.CANOPY_IIIF_DEBUG === "1") {
|
|
1286
1346
|
const {logLine} = require("./log");
|
|
@@ -1290,7 +1350,7 @@ async function saveCachedCollection(collection, id, parentId) {
|
|
|
1290
1350
|
index.byId = Array.isArray(index.byId) ? index.byId : [];
|
|
1291
1351
|
const nid = normalizeIiifId(id);
|
|
1292
1352
|
const existingEntryIdx = index.byId.findIndex(
|
|
1293
|
-
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Collection"
|
|
1353
|
+
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Collection",
|
|
1294
1354
|
);
|
|
1295
1355
|
const entry = {
|
|
1296
1356
|
id: String(nid),
|
|
@@ -1305,133 +1365,96 @@ async function saveCachedCollection(collection, id, parentId) {
|
|
|
1305
1365
|
} catch (_) {}
|
|
1306
1366
|
}
|
|
1307
1367
|
|
|
1308
|
-
async function
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
.sort()
|
|
1327
|
-
: [];
|
|
1328
|
-
const manifestFiles = fs.existsSync(IIIF_CACHE_MANIFESTS_DIR)
|
|
1329
|
-
? (await fsp.readdir(IIIF_CACHE_MANIFESTS_DIR))
|
|
1330
|
-
.filter((name) => name && name.toLowerCase().endsWith(".json"))
|
|
1331
|
-
.sort()
|
|
1332
|
-
: [];
|
|
1333
|
-
const {size: thumbSize, unsafe: unsafeThumbs} =
|
|
1334
|
-
resolveThumbnailPreferences();
|
|
1335
|
-
|
|
1336
|
-
for (const name of collectionFiles) {
|
|
1337
|
-
const slug = name.replace(/\.json$/i, "");
|
|
1338
|
-
const fp = path.join(IIIF_CACHE_COLLECTIONS_DIR, name);
|
|
1339
|
-
let data = null;
|
|
1340
|
-
try {
|
|
1341
|
-
data = await readJson(fp);
|
|
1342
|
-
} catch (_) {
|
|
1343
|
-
data = null;
|
|
1344
|
-
}
|
|
1345
|
-
if (!data) continue;
|
|
1346
|
-
const id = data.id || data["@id"];
|
|
1347
|
-
if (!id) continue;
|
|
1348
|
-
const nid = normalizeIiifId(String(id));
|
|
1349
|
-
const key = `Collection:${nid}`;
|
|
1350
|
-
const fallback = priorMap.get(key) || {};
|
|
1351
|
-
const parent = resolveParentFromPartOf(data) || fallback.parent || "";
|
|
1352
|
-
const entry = {
|
|
1353
|
-
id: String(nid),
|
|
1354
|
-
type: "Collection",
|
|
1355
|
-
slug,
|
|
1356
|
-
parent,
|
|
1357
|
-
};
|
|
1358
|
-
applyCollectionEntryCanonical(entry, data);
|
|
1359
|
-
nextIndex.byId.push(entry);
|
|
1360
|
-
}
|
|
1368
|
+
async function cleanupIiifCache(options = {}) {
|
|
1369
|
+
const allowedManifestIds = Array.isArray(options.allowedManifestIds)
|
|
1370
|
+
? options.allowedManifestIds
|
|
1371
|
+
: [];
|
|
1372
|
+
const allowedCollectionIds = Array.isArray(options.allowedCollectionIds)
|
|
1373
|
+
? options.allowedCollectionIds
|
|
1374
|
+
: [];
|
|
1375
|
+
const manifestSet = new Set(
|
|
1376
|
+
allowedManifestIds
|
|
1377
|
+
.map((id) => normalizeIiifId(String(id || "")))
|
|
1378
|
+
.filter(Boolean),
|
|
1379
|
+
);
|
|
1380
|
+
const collectionSet = new Set(
|
|
1381
|
+
allowedCollectionIds
|
|
1382
|
+
.map((id) => normalizeIiifId(String(id || "")))
|
|
1383
|
+
.filter(Boolean),
|
|
1384
|
+
);
|
|
1385
|
+
if (!manifestSet.size && !collectionSet.size) return;
|
|
1361
1386
|
|
|
1362
|
-
|
|
1363
|
-
|
|
1387
|
+
let removedManifestFiles = 0;
|
|
1388
|
+
if (fs.existsSync(IIIF_CACHE_MANIFESTS_DIR)) {
|
|
1389
|
+
const files = await fsp.readdir(IIIF_CACHE_MANIFESTS_DIR);
|
|
1390
|
+
for (const name of files) {
|
|
1391
|
+
if (!name || !name.toLowerCase().endsWith(".json")) continue;
|
|
1364
1392
|
const fp = path.join(IIIF_CACHE_MANIFESTS_DIR, name);
|
|
1365
1393
|
let manifest = null;
|
|
1366
1394
|
try {
|
|
1367
1395
|
manifest = await readJson(fp);
|
|
1368
|
-
} catch (_) {
|
|
1369
|
-
manifest = null;
|
|
1370
|
-
}
|
|
1371
|
-
if (!manifest) continue;
|
|
1372
|
-
const id = manifest.id || manifest["@id"];
|
|
1373
|
-
if (!id) continue;
|
|
1374
|
-
const nid = normalizeIiifId(String(id));
|
|
1375
|
-
MEMO_ID_TO_SLUG.set(String(id), slug);
|
|
1376
|
-
const key = `Manifest:${nid}`;
|
|
1377
|
-
const fallback = priorMap.get(key) || {};
|
|
1378
|
-
const parent = resolveParentFromPartOf(manifest) || fallback.parent || "";
|
|
1379
|
-
const entry = {
|
|
1380
|
-
id: String(nid),
|
|
1381
|
-
type: "Manifest",
|
|
1382
|
-
slug,
|
|
1383
|
-
parent,
|
|
1384
|
-
};
|
|
1385
|
-
applyManifestEntryCanonical(entry, manifest, slug);
|
|
1386
|
-
try {
|
|
1387
|
-
const thumb = await getThumbnail(manifest, thumbSize, unsafeThumbs);
|
|
1388
|
-
if (thumb && thumb.url) {
|
|
1389
|
-
entry.thumbnail = String(thumb.url);
|
|
1390
|
-
if (typeof thumb.width === "number") entry.thumbnailWidth = thumb.width;
|
|
1391
|
-
if (typeof thumb.height === "number") entry.thumbnailHeight = thumb.height;
|
|
1392
|
-
}
|
|
1393
1396
|
} catch (_) {}
|
|
1397
|
+
const nid = normalizeIiifId(
|
|
1398
|
+
String(
|
|
1399
|
+
(manifest && (manifest.id || manifest["@id"])) ||
|
|
1400
|
+
name.replace(/\.json$/i, ""),
|
|
1401
|
+
),
|
|
1402
|
+
);
|
|
1403
|
+
if (!manifestSet.has(nid)) {
|
|
1404
|
+
try {
|
|
1405
|
+
await fsp.rm(fp, {force: true});
|
|
1406
|
+
removedManifestFiles += 1;
|
|
1407
|
+
} catch (_) {}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
let removedCollectionFiles = 0;
|
|
1413
|
+
if (fs.existsSync(IIIF_CACHE_COLLECTIONS_DIR)) {
|
|
1414
|
+
const files = await fsp.readdir(IIIF_CACHE_COLLECTIONS_DIR);
|
|
1415
|
+
for (const name of files) {
|
|
1416
|
+
if (!name || !name.toLowerCase().endsWith(".json")) continue;
|
|
1417
|
+
const fp = path.join(IIIF_CACHE_COLLECTIONS_DIR, name);
|
|
1418
|
+
let collection = null;
|
|
1394
1419
|
try {
|
|
1395
|
-
|
|
1396
|
-
if (heroMedia && heroMedia.heroThumbnail) {
|
|
1397
|
-
entry.heroThumbnail = heroMedia.heroThumbnail;
|
|
1398
|
-
if (typeof heroMedia.heroThumbnailWidth === "number")
|
|
1399
|
-
entry.heroThumbnailWidth = heroMedia.heroThumbnailWidth;
|
|
1400
|
-
if (typeof heroMedia.heroThumbnailHeight === "number")
|
|
1401
|
-
entry.heroThumbnailHeight = heroMedia.heroThumbnailHeight;
|
|
1402
|
-
if (heroMedia.heroThumbnailSrcset) {
|
|
1403
|
-
entry.heroThumbnailSrcset = heroMedia.heroThumbnailSrcset;
|
|
1404
|
-
entry.heroThumbnailSizes = HERO_IMAGE_SIZES_ATTR;
|
|
1405
|
-
}
|
|
1406
|
-
if (heroMedia.ogImage) {
|
|
1407
|
-
entry.ogImage = heroMedia.ogImage;
|
|
1408
|
-
if (typeof heroMedia.ogImageWidth === 'number')
|
|
1409
|
-
entry.ogImageWidth = heroMedia.ogImageWidth;
|
|
1410
|
-
else delete entry.ogImageWidth;
|
|
1411
|
-
if (typeof heroMedia.ogImageHeight === 'number')
|
|
1412
|
-
entry.ogImageHeight = heroMedia.ogImageHeight;
|
|
1413
|
-
else delete entry.ogImageHeight;
|
|
1414
|
-
}
|
|
1415
|
-
ensureThumbnailValue(
|
|
1416
|
-
entry,
|
|
1417
|
-
heroMedia.heroThumbnail,
|
|
1418
|
-
heroMedia.heroThumbnailWidth,
|
|
1419
|
-
heroMedia.heroThumbnailHeight
|
|
1420
|
-
);
|
|
1421
|
-
}
|
|
1420
|
+
collection = await readJson(fp);
|
|
1422
1421
|
} catch (_) {}
|
|
1423
|
-
|
|
1422
|
+
const nid = normalizeIiifId(
|
|
1423
|
+
String(
|
|
1424
|
+
(collection && (collection.id || collection["@id"])) ||
|
|
1425
|
+
name.replace(/\.json$/i, ""),
|
|
1426
|
+
),
|
|
1427
|
+
);
|
|
1428
|
+
if (!collectionSet.has(nid)) {
|
|
1429
|
+
try {
|
|
1430
|
+
await fsp.rm(fp, {force: true});
|
|
1431
|
+
removedCollectionFiles += 1;
|
|
1432
|
+
} catch (_) {}
|
|
1433
|
+
}
|
|
1424
1434
|
}
|
|
1425
|
-
|
|
1426
|
-
await saveManifestIndex(nextIndex);
|
|
1427
|
-
try {
|
|
1428
|
-
logLine("✓ Rebuilt IIIF cache index", "cyan");
|
|
1429
|
-
} catch (_) {}
|
|
1430
|
-
} catch (err) {
|
|
1431
|
-
try {
|
|
1432
|
-
logLine("! Skipped IIIF index rebuild", "yellow");
|
|
1433
|
-
} catch (_) {}
|
|
1434
1435
|
}
|
|
1436
|
+
|
|
1437
|
+
try {
|
|
1438
|
+
const index = await loadManifestIndex();
|
|
1439
|
+
if (Array.isArray(index.byId)) {
|
|
1440
|
+
index.byId = index.byId.filter((entry) => {
|
|
1441
|
+
if (!entry || !entry.id) return false;
|
|
1442
|
+
const nid = normalizeIiifId(String(entry.id));
|
|
1443
|
+
if (entry.type === "Manifest") return manifestSet.has(nid);
|
|
1444
|
+
if (entry.type === "Collection") return collectionSet.has(nid);
|
|
1445
|
+
return true;
|
|
1446
|
+
});
|
|
1447
|
+
await saveManifestIndex(index);
|
|
1448
|
+
}
|
|
1449
|
+
} catch (_) {}
|
|
1450
|
+
|
|
1451
|
+
try {
|
|
1452
|
+
logLine(
|
|
1453
|
+
`• Cleaned IIIF cache (${removedManifestFiles} Manifest file(s) removed, ${removedCollectionFiles} Collection file(s) removed)`,
|
|
1454
|
+
"blue",
|
|
1455
|
+
{dim: true},
|
|
1456
|
+
);
|
|
1457
|
+
} catch (_) {}
|
|
1435
1458
|
}
|
|
1436
1459
|
|
|
1437
1460
|
async function loadConfig() {
|
|
@@ -1451,13 +1474,9 @@ async function loadConfig() {
|
|
|
1451
1474
|
async function buildIiifCollectionPages(CONFIG) {
|
|
1452
1475
|
const cfg = CONFIG || (await loadConfig());
|
|
1453
1476
|
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
process.env.CANOPY_COLLECTION_URI || ""
|
|
1458
|
-
);
|
|
1459
|
-
}
|
|
1460
|
-
if (!collectionUris.length) return {searchRecords: []};
|
|
1477
|
+
const {collections: collectionUris, manifests: manifestUris} =
|
|
1478
|
+
resolveIiifSources(cfg);
|
|
1479
|
+
if (!collectionUris.length && !manifestUris.length) return {iiifRecords: []};
|
|
1461
1480
|
|
|
1462
1481
|
const searchIndexCfg = (cfg && cfg.search && cfg.search.index) || {};
|
|
1463
1482
|
const metadataCfg = (searchIndexCfg && searchIndexCfg.metadata) || {};
|
|
@@ -1483,7 +1502,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1483
1502
|
const metadataLabelSet = new Set(
|
|
1484
1503
|
metadataLabelsRaw
|
|
1485
1504
|
.map((label) => normalizeMetadataLabel(String(label || "")))
|
|
1486
|
-
.filter(Boolean)
|
|
1505
|
+
.filter(Boolean),
|
|
1487
1506
|
);
|
|
1488
1507
|
const metadataFacetLabels = (() => {
|
|
1489
1508
|
if (!Array.isArray(metadataLabelsRaw) || !metadataLabelsRaw.length)
|
|
@@ -1491,7 +1510,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1491
1510
|
const seen = new Set();
|
|
1492
1511
|
const entries = [];
|
|
1493
1512
|
for (const label of metadataLabelsRaw) {
|
|
1494
|
-
const raw =
|
|
1513
|
+
const raw =
|
|
1514
|
+
typeof label === "string" ? label.trim() : String(label || "");
|
|
1495
1515
|
if (!raw) continue;
|
|
1496
1516
|
const normalized = normalizeMetadataLabel(raw);
|
|
1497
1517
|
if (!normalized || seen.has(normalized)) continue;
|
|
@@ -1514,8 +1534,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1514
1534
|
};
|
|
1515
1535
|
const annotationMotivations = new Set(
|
|
1516
1536
|
normalizeStringList(annotationsCfg && annotationsCfg.motivation).map((m) =>
|
|
1517
|
-
m.toLowerCase()
|
|
1518
|
-
)
|
|
1537
|
+
m.toLowerCase(),
|
|
1538
|
+
),
|
|
1519
1539
|
);
|
|
1520
1540
|
const annotationsOptions = {
|
|
1521
1541
|
enabled: annotationsEnabled,
|
|
@@ -1524,7 +1544,11 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1524
1544
|
|
|
1525
1545
|
// Recursively traverse Collections and gather all Manifest tasks
|
|
1526
1546
|
const tasks = [];
|
|
1547
|
+
let manifestTasksFromCollections = 0;
|
|
1548
|
+
let manifestTasksFromConfig = 0;
|
|
1549
|
+
const queuedManifestIds = new Set();
|
|
1527
1550
|
const visitedCollections = new Set(); // normalized ids
|
|
1551
|
+
const renderedManifestIds = new Set();
|
|
1528
1552
|
const norm = (x) => {
|
|
1529
1553
|
try {
|
|
1530
1554
|
return normalizeIiifId(String(x || ""));
|
|
@@ -1550,9 +1574,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1550
1574
|
const ncol = await upgradeIiifResource(col);
|
|
1551
1575
|
const reportedId = String(
|
|
1552
1576
|
(ncol && (ncol.id || ncol["@id"])) ||
|
|
1553
|
-
(typeof colLike === "object" &&
|
|
1554
|
-
|
|
1555
|
-
""
|
|
1577
|
+
(typeof colLike === "object" && (colLike.id || colLike["@id"])) ||
|
|
1578
|
+
"",
|
|
1556
1579
|
);
|
|
1557
1580
|
const effectiveId = String(uri || reportedId || "");
|
|
1558
1581
|
const collectionKey = effectiveId || reportedId || uri || "";
|
|
@@ -1567,8 +1590,13 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1567
1590
|
const entryId = entry && entry.id;
|
|
1568
1591
|
if (!entryId) continue;
|
|
1569
1592
|
const entryType = normalizeIiifType(entry.type || entry.fallback || "");
|
|
1593
|
+
const dedupeKey = norm(entryId) || String(entryId || "");
|
|
1594
|
+
if (!dedupeKey) continue;
|
|
1570
1595
|
if (entryType === "manifest") {
|
|
1596
|
+
if (queuedManifestIds.has(dedupeKey)) continue;
|
|
1597
|
+
queuedManifestIds.add(dedupeKey);
|
|
1571
1598
|
tasks.push({id: entryId, parent: collectionKey});
|
|
1599
|
+
manifestTasksFromCollections += 1;
|
|
1572
1600
|
} else if (entryType === "collection") {
|
|
1573
1601
|
await gatherFromCollection(entry.raw || entryId, collectionKey);
|
|
1574
1602
|
}
|
|
@@ -1597,32 +1625,50 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1597
1625
|
} catch (_) {}
|
|
1598
1626
|
await gatherFromCollection(normalizedRoot, "");
|
|
1599
1627
|
}
|
|
1600
|
-
if (
|
|
1628
|
+
if (manifestUris.length) {
|
|
1629
|
+
for (const uri of manifestUris) {
|
|
1630
|
+
const dedupeKey = norm(uri) || String(uri || "");
|
|
1631
|
+
if (!dedupeKey || queuedManifestIds.has(dedupeKey)) continue;
|
|
1632
|
+
queuedManifestIds.add(dedupeKey);
|
|
1633
|
+
tasks.push({id: uri, parent: ""});
|
|
1634
|
+
manifestTasksFromConfig += 1;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
if (!tasks.length) return {iiifRecords: []};
|
|
1638
|
+
try {
|
|
1639
|
+
logLine(
|
|
1640
|
+
`• Processing ${tasks.length} Manifest(s) (${manifestTasksFromCollections} from collections, ${manifestTasksFromConfig} direct)`,
|
|
1641
|
+
"blue",
|
|
1642
|
+
{dim: true},
|
|
1643
|
+
);
|
|
1644
|
+
} catch (_) {}
|
|
1645
|
+
logDebug(
|
|
1646
|
+
`Queued ${tasks.length} Manifest task(s) (${manifestTasksFromCollections} from collections, ${manifestTasksFromConfig} direct)`,
|
|
1647
|
+
);
|
|
1601
1648
|
|
|
1602
1649
|
// Split into chunks and process with limited concurrency
|
|
1603
1650
|
const chunkSize = resolvePositiveInteger(
|
|
1604
1651
|
process.env.CANOPY_CHUNK_SIZE,
|
|
1605
|
-
DEFAULT_CHUNK_SIZE
|
|
1652
|
+
DEFAULT_CHUNK_SIZE,
|
|
1606
1653
|
);
|
|
1607
1654
|
const chunks = Math.ceil(tasks.length / chunkSize);
|
|
1608
1655
|
const requestedConcurrency = resolvePositiveInteger(
|
|
1609
1656
|
process.env.CANOPY_FETCH_CONCURRENCY,
|
|
1610
1657
|
DEFAULT_FETCH_CONCURRENCY,
|
|
1611
|
-
{allowZero: true}
|
|
1658
|
+
{allowZero: true},
|
|
1612
1659
|
);
|
|
1613
1660
|
// Summary before processing chunks
|
|
1614
1661
|
try {
|
|
1615
|
-
const collectionsCount = visitedCollections.size || 0;
|
|
1616
1662
|
logLine(
|
|
1617
|
-
`• Fetching ${tasks.length} Manifest(s) in ${chunks} chunk(s)
|
|
1663
|
+
`• Fetching ${tasks.length} Manifest(s) in ${chunks} chunk(s)`,
|
|
1618
1664
|
"blue",
|
|
1619
|
-
{dim: true}
|
|
1665
|
+
{dim: true},
|
|
1620
1666
|
);
|
|
1621
1667
|
const concurrencySummary =
|
|
1622
1668
|
requestedConcurrency === 0
|
|
1623
1669
|
? "auto (no explicit cap)"
|
|
1624
1670
|
: String(requestedConcurrency);
|
|
1625
|
-
logLine(`• Fetch concurrency: ${concurrencySummary}`, "blue", {
|
|
1671
|
+
logLine(`• Fetch concurrency: ${concurrencySummary}`, "blue", {dim: true});
|
|
1626
1672
|
} catch (_) {}
|
|
1627
1673
|
const iiifRecords = [];
|
|
1628
1674
|
const navPlaceRecords = [];
|
|
@@ -1632,7 +1678,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1632
1678
|
const worksLayoutPath = path.join(CONTENT_DIR, "works", "_layout.mdx");
|
|
1633
1679
|
if (!fs.existsSync(worksLayoutPath)) {
|
|
1634
1680
|
throw new Error(
|
|
1635
|
-
"IIIF build requires content/works/_layout.mdx. Create the layout instead of relying on generated output."
|
|
1681
|
+
"IIIF build requires content/works/_layout.mdx. Create the layout instead of relying on generated output.",
|
|
1636
1682
|
);
|
|
1637
1683
|
}
|
|
1638
1684
|
let WorksLayoutComp = null;
|
|
@@ -1652,7 +1698,9 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1652
1698
|
const chunkStart = Date.now();
|
|
1653
1699
|
|
|
1654
1700
|
const concurrency =
|
|
1655
|
-
requestedConcurrency === 0
|
|
1701
|
+
requestedConcurrency === 0
|
|
1702
|
+
? Math.max(1, chunk.length)
|
|
1703
|
+
: requestedConcurrency;
|
|
1656
1704
|
let next = 0;
|
|
1657
1705
|
const logs = new Array(chunk.length);
|
|
1658
1706
|
let nextPrint = 0;
|
|
@@ -1692,7 +1740,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1692
1740
|
const saved = await saveCachedManifest(
|
|
1693
1741
|
manifest,
|
|
1694
1742
|
String(id),
|
|
1695
|
-
String(it.parent || "")
|
|
1743
|
+
String(it.parent || ""),
|
|
1696
1744
|
);
|
|
1697
1745
|
manifest = saved || manifest;
|
|
1698
1746
|
const cached = await loadCachedManifestById(String(id));
|
|
@@ -1719,7 +1767,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1719
1767
|
const saved = await saveCachedManifest(
|
|
1720
1768
|
manifest,
|
|
1721
1769
|
String(id),
|
|
1722
|
-
String(it.parent || "")
|
|
1770
|
+
String(it.parent || ""),
|
|
1723
1771
|
);
|
|
1724
1772
|
manifest = saved || manifest;
|
|
1725
1773
|
const cached = await loadCachedManifestById(String(id));
|
|
@@ -1737,11 +1785,13 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1737
1785
|
const ensured = await ensurePresentation3Manifest(manifest);
|
|
1738
1786
|
manifest = ensured.manifest;
|
|
1739
1787
|
const title = firstLabelString(manifest.label);
|
|
1740
|
-
|
|
1788
|
+
const manifestLabel = title || String(manifest.id || id);
|
|
1789
|
+
logDebug(`Preparing manifest ${manifestLabel}`);
|
|
1790
|
+
let summaryRaw = "";
|
|
1741
1791
|
try {
|
|
1742
1792
|
summaryRaw = extractSummaryValues(manifest);
|
|
1743
1793
|
} catch (_) {
|
|
1744
|
-
summaryRaw =
|
|
1794
|
+
summaryRaw = "";
|
|
1745
1795
|
}
|
|
1746
1796
|
const summaryForMeta = truncateSummary(summaryRaw || title);
|
|
1747
1797
|
const baseSlug =
|
|
@@ -1754,7 +1804,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1754
1804
|
let idxMap = await loadManifestIndex();
|
|
1755
1805
|
idxMap.byId = Array.isArray(idxMap.byId) ? idxMap.byId : [];
|
|
1756
1806
|
let mEntry = idxMap.byId.find(
|
|
1757
|
-
(e) => e && e.type === "Manifest" && normalizeIiifId(e.id) === nid
|
|
1807
|
+
(e) => e && e.type === "Manifest" && normalizeIiifId(e.id) === nid,
|
|
1758
1808
|
);
|
|
1759
1809
|
let slug = mEntry && mEntry.slug;
|
|
1760
1810
|
if (isSlugTooLong(slug)) slug = null;
|
|
@@ -1769,7 +1819,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1769
1819
|
};
|
|
1770
1820
|
applyManifestEntryCanonical(newEntry, manifest, slug);
|
|
1771
1821
|
const existingIdx = idxMap.byId.findIndex(
|
|
1772
|
-
(e) => e && e.type === "Manifest" && normalizeIiifId(e.id) === nid
|
|
1822
|
+
(e) => e && e.type === "Manifest" && normalizeIiifId(e.id) === nid,
|
|
1773
1823
|
);
|
|
1774
1824
|
if (existingIdx >= 0) idxMap.byId[existingIdx] = newEntry;
|
|
1775
1825
|
else idxMap.byId.push(newEntry);
|
|
@@ -1777,12 +1827,19 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1777
1827
|
mEntry = newEntry;
|
|
1778
1828
|
} else if (mEntry) {
|
|
1779
1829
|
const prevCanonical = mEntry.canonical || "";
|
|
1780
|
-
const nextCanonical = applyManifestEntryCanonical(
|
|
1830
|
+
const nextCanonical = applyManifestEntryCanonical(
|
|
1831
|
+
mEntry,
|
|
1832
|
+
manifest,
|
|
1833
|
+
slug,
|
|
1834
|
+
);
|
|
1781
1835
|
if (nextCanonical !== prevCanonical) {
|
|
1782
1836
|
await saveManifestIndex(idxMap);
|
|
1783
1837
|
}
|
|
1784
1838
|
}
|
|
1785
1839
|
const manifestId = manifest && manifest.id ? manifest.id : id;
|
|
1840
|
+
const normalizedManifestId = normalizeIiifId(String(manifestId || id));
|
|
1841
|
+
if (normalizedManifestId) renderedManifestIds.add(normalizedManifestId);
|
|
1842
|
+
logDebug(`Resolved slug ${slug} for ${manifestLabel}`);
|
|
1786
1843
|
const references = referenced.getReferencesForManifest(manifestId);
|
|
1787
1844
|
const href = path.join("works", slug + ".html");
|
|
1788
1845
|
const outPath = path.join(OUT_DIR, href);
|
|
@@ -1844,7 +1901,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1844
1901
|
canonical,
|
|
1845
1902
|
},
|
|
1846
1903
|
};
|
|
1847
|
-
const ogImageForPage =
|
|
1904
|
+
const ogImageForPage =
|
|
1905
|
+
heroMedia && heroMedia.ogImage ? heroMedia.ogImage : "";
|
|
1848
1906
|
if (ogImageForPage) {
|
|
1849
1907
|
pageDetails.image = ogImageForPage;
|
|
1850
1908
|
pageDetails.ogImage = ogImageForPage;
|
|
@@ -1852,11 +1910,19 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1852
1910
|
pageDetails.meta.ogImage = ogImageForPage;
|
|
1853
1911
|
}
|
|
1854
1912
|
const navigationRoots = navigation.buildNavigationRoots(slug || "");
|
|
1855
|
-
const navigationContext =
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1913
|
+
const navigationContext =
|
|
1914
|
+
navigationRoots && Object.keys(navigationRoots).length
|
|
1915
|
+
? {allRoots: navigationRoots}
|
|
1916
|
+
: null;
|
|
1917
|
+
const pageContextValue = {
|
|
1918
|
+
navigation: navigationContext,
|
|
1919
|
+
page: pageDetails,
|
|
1920
|
+
};
|
|
1921
|
+
if (
|
|
1922
|
+
metadataFacetLabels.length &&
|
|
1923
|
+
manifest &&
|
|
1924
|
+
typeof manifest === "object"
|
|
1925
|
+
) {
|
|
1860
1926
|
try {
|
|
1861
1927
|
Object.defineProperty(manifest, "__canopyMetadataFacets", {
|
|
1862
1928
|
configurable: true,
|
|
@@ -1882,15 +1948,15 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1882
1948
|
PageContext && pageContextValue
|
|
1883
1949
|
? React.createElement(
|
|
1884
1950
|
PageContext.Provider,
|
|
1885
|
-
{
|
|
1886
|
-
wrappedApp
|
|
1951
|
+
{value: pageContextValue},
|
|
1952
|
+
wrappedApp,
|
|
1887
1953
|
)
|
|
1888
1954
|
: wrappedApp;
|
|
1889
1955
|
const page = MDXProvider
|
|
1890
1956
|
? React.createElement(
|
|
1891
1957
|
MDXProvider,
|
|
1892
1958
|
{components: compMap},
|
|
1893
|
-
withContext
|
|
1959
|
+
withContext,
|
|
1894
1960
|
)
|
|
1895
1961
|
: withContext;
|
|
1896
1962
|
const body = ReactDOMServer.renderToStaticMarkup(page);
|
|
@@ -1903,8 +1969,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1903
1969
|
const wrappedHead = PageContext
|
|
1904
1970
|
? React.createElement(
|
|
1905
1971
|
PageContext.Provider,
|
|
1906
|
-
{
|
|
1907
|
-
headElement
|
|
1972
|
+
{value: pageContextValue},
|
|
1973
|
+
headElement,
|
|
1908
1974
|
)
|
|
1909
1975
|
: headElement;
|
|
1910
1976
|
head = ReactDOMServer.renderToStaticMarkup(wrappedHead);
|
|
@@ -1928,7 +1994,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1928
1994
|
? path
|
|
1929
1995
|
.relative(
|
|
1930
1996
|
path.dirname(outPath),
|
|
1931
|
-
path.join(OUT_DIR, "scripts", "canopy-viewer.js")
|
|
1997
|
+
path.join(OUT_DIR, "scripts", "canopy-viewer.js"),
|
|
1932
1998
|
)
|
|
1933
1999
|
.split(path.sep)
|
|
1934
2000
|
.join("/")
|
|
@@ -1937,7 +2003,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1937
2003
|
? path
|
|
1938
2004
|
.relative(
|
|
1939
2005
|
path.dirname(outPath),
|
|
1940
|
-
path.join(OUT_DIR, "scripts", "canopy-slider.js")
|
|
2006
|
+
path.join(OUT_DIR, "scripts", "canopy-slider.js"),
|
|
1941
2007
|
)
|
|
1942
2008
|
.split(path.sep)
|
|
1943
2009
|
.join("/")
|
|
@@ -1946,7 +2012,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1946
2012
|
? path
|
|
1947
2013
|
.relative(
|
|
1948
2014
|
path.dirname(outPath),
|
|
1949
|
-
path.join(OUT_DIR, "scripts", "canopy-timeline.js")
|
|
2015
|
+
path.join(OUT_DIR, "scripts", "canopy-timeline.js"),
|
|
1950
2016
|
)
|
|
1951
2017
|
.split(path.sep)
|
|
1952
2018
|
.join("/")
|
|
@@ -1955,7 +2021,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1955
2021
|
? path
|
|
1956
2022
|
.relative(
|
|
1957
2023
|
path.dirname(outPath),
|
|
1958
|
-
path.join(OUT_DIR, "scripts", "canopy-map.js")
|
|
2024
|
+
path.join(OUT_DIR, "scripts", "canopy-map.js"),
|
|
1959
2025
|
)
|
|
1960
2026
|
.split(path.sep)
|
|
1961
2027
|
.join("/")
|
|
@@ -1964,7 +2030,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1964
2030
|
? path
|
|
1965
2031
|
.relative(
|
|
1966
2032
|
path.dirname(outPath),
|
|
1967
|
-
path.join(OUT_DIR, "scripts", "canopy-map.css")
|
|
2033
|
+
path.join(OUT_DIR, "scripts", "canopy-map.css"),
|
|
1968
2034
|
)
|
|
1969
2035
|
.split(path.sep)
|
|
1970
2036
|
.join("/")
|
|
@@ -1973,7 +2039,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1973
2039
|
? path
|
|
1974
2040
|
.relative(
|
|
1975
2041
|
path.dirname(outPath),
|
|
1976
|
-
path.join(OUT_DIR, "scripts", "canopy-hero-slider.js")
|
|
2042
|
+
path.join(OUT_DIR, "scripts", "canopy-hero-slider.js"),
|
|
1977
2043
|
)
|
|
1978
2044
|
.split(path.sep)
|
|
1979
2045
|
.join("/")
|
|
@@ -1982,7 +2048,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1982
2048
|
? path
|
|
1983
2049
|
.relative(
|
|
1984
2050
|
path.dirname(outPath),
|
|
1985
|
-
path.join(OUT_DIR, "scripts", "canopy-related-items.js")
|
|
2051
|
+
path.join(OUT_DIR, "scripts", "canopy-related-items.js"),
|
|
1986
2052
|
)
|
|
1987
2053
|
.split(path.sep)
|
|
1988
2054
|
.join("/")
|
|
@@ -1991,7 +2057,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1991
2057
|
? path
|
|
1992
2058
|
.relative(
|
|
1993
2059
|
path.dirname(outPath),
|
|
1994
|
-
path.join(OUT_DIR, "scripts", "canopy-search-form.js")
|
|
2060
|
+
path.join(OUT_DIR, "scripts", "canopy-search-form.js"),
|
|
1995
2061
|
)
|
|
1996
2062
|
.split(path.sep)
|
|
1997
2063
|
.join("/")
|
|
@@ -2012,7 +2078,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2012
2078
|
jsRel = primaryClassicScripts.shift();
|
|
2013
2079
|
}
|
|
2014
2080
|
const classicScriptRels = primaryClassicScripts.concat(
|
|
2015
|
-
secondaryClassicScripts
|
|
2081
|
+
secondaryClassicScripts,
|
|
2016
2082
|
);
|
|
2017
2083
|
|
|
2018
2084
|
const headSegments = [head];
|
|
@@ -2028,7 +2094,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2028
2094
|
const vendorAbs = path.join(
|
|
2029
2095
|
OUT_DIR,
|
|
2030
2096
|
"scripts",
|
|
2031
|
-
"react-globals.js"
|
|
2097
|
+
"react-globals.js",
|
|
2032
2098
|
);
|
|
2033
2099
|
let vendorRel = path
|
|
2034
2100
|
.relative(path.dirname(outPath), vendorAbs)
|
|
@@ -2057,7 +2123,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2057
2123
|
if (BASE_PATH)
|
|
2058
2124
|
vendorTag =
|
|
2059
2125
|
`<script>window.CANOPY_BASE_PATH=${JSON.stringify(
|
|
2060
|
-
BASE_PATH
|
|
2126
|
+
BASE_PATH,
|
|
2061
2127
|
)}</script>` + vendorTag;
|
|
2062
2128
|
} catch (_) {}
|
|
2063
2129
|
let pageBody = body;
|
|
@@ -2089,6 +2155,9 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2089
2155
|
html = require("../common").applyBaseToHtml(html);
|
|
2090
2156
|
} catch (_) {}
|
|
2091
2157
|
await fsp.writeFile(outPath, html, "utf8");
|
|
2158
|
+
logDebug(
|
|
2159
|
+
`Wrote work page → ${path.relative(process.cwd(), outPath)}`,
|
|
2160
|
+
);
|
|
2092
2161
|
lns.push([
|
|
2093
2162
|
`✔ Created ${path.relative(process.cwd(), outPath)}`,
|
|
2094
2163
|
"green",
|
|
@@ -2102,6 +2171,9 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2102
2171
|
thumbUrl = String(t.url);
|
|
2103
2172
|
thumbWidth = typeof t.width === "number" ? t.width : undefined;
|
|
2104
2173
|
thumbHeight = typeof t.height === "number" ? t.height : undefined;
|
|
2174
|
+
logDebug(
|
|
2175
|
+
`Thumbnail resolved for ${manifestLabel}: ${thumbUrl} (${thumbWidth || "auto"}×${thumbHeight || "auto"})`,
|
|
2176
|
+
);
|
|
2105
2177
|
}
|
|
2106
2178
|
} catch (_) {}
|
|
2107
2179
|
try {
|
|
@@ -2111,7 +2183,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2111
2183
|
(e) =>
|
|
2112
2184
|
e &&
|
|
2113
2185
|
e.id === String(manifest.id || id) &&
|
|
2114
|
-
e.type === "Manifest"
|
|
2186
|
+
e.type === "Manifest",
|
|
2115
2187
|
);
|
|
2116
2188
|
if (entry) {
|
|
2117
2189
|
let touched = false;
|
|
@@ -2137,6 +2209,9 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2137
2209
|
}
|
|
2138
2210
|
}
|
|
2139
2211
|
if (heroMedia && heroMedia.heroThumbnail) {
|
|
2212
|
+
logDebug(
|
|
2213
|
+
`Hero thumbnail cached for ${manifestLabel}: ${heroMedia.heroThumbnail}`,
|
|
2214
|
+
);
|
|
2140
2215
|
if (entry.heroThumbnail !== heroMedia.heroThumbnail) {
|
|
2141
2216
|
entry.heroThumbnail = heroMedia.heroThumbnail;
|
|
2142
2217
|
touched = true;
|
|
@@ -2157,7 +2232,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2157
2232
|
}
|
|
2158
2233
|
if (heroMedia.heroThumbnailSrcset) {
|
|
2159
2234
|
if (
|
|
2160
|
-
entry.heroThumbnailSrcset !==
|
|
2235
|
+
entry.heroThumbnailSrcset !==
|
|
2236
|
+
heroMedia.heroThumbnailSrcset
|
|
2161
2237
|
) {
|
|
2162
2238
|
entry.heroThumbnailSrcset = heroMedia.heroThumbnailSrcset;
|
|
2163
2239
|
touched = true;
|
|
@@ -2166,6 +2242,9 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2166
2242
|
entry.heroThumbnailSizes = HERO_IMAGE_SIZES_ATTR;
|
|
2167
2243
|
touched = true;
|
|
2168
2244
|
}
|
|
2245
|
+
logDebug(
|
|
2246
|
+
`Hero srcset cached for ${manifestLabel} (${heroMedia.heroThumbnailSrcset.length} chars)`,
|
|
2247
|
+
);
|
|
2169
2248
|
}
|
|
2170
2249
|
} else {
|
|
2171
2250
|
if (entry.heroThumbnail !== undefined) {
|
|
@@ -2190,19 +2269,24 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2190
2269
|
}
|
|
2191
2270
|
}
|
|
2192
2271
|
if (heroMedia && heroMedia.ogImage) {
|
|
2272
|
+
logDebug(
|
|
2273
|
+
`OG image cached for ${manifestLabel}: ${heroMedia.ogImage}`,
|
|
2274
|
+
);
|
|
2193
2275
|
if (entry.ogImage !== heroMedia.ogImage) {
|
|
2194
2276
|
entry.ogImage = heroMedia.ogImage;
|
|
2195
2277
|
touched = true;
|
|
2196
2278
|
}
|
|
2197
|
-
if (typeof heroMedia.ogImageWidth ===
|
|
2198
|
-
if (entry.ogImageWidth !== heroMedia.ogImageWidth)
|
|
2279
|
+
if (typeof heroMedia.ogImageWidth === "number") {
|
|
2280
|
+
if (entry.ogImageWidth !== heroMedia.ogImageWidth)
|
|
2281
|
+
touched = true;
|
|
2199
2282
|
entry.ogImageWidth = heroMedia.ogImageWidth;
|
|
2200
2283
|
} else if (entry.ogImageWidth !== undefined) {
|
|
2201
2284
|
delete entry.ogImageWidth;
|
|
2202
2285
|
touched = true;
|
|
2203
2286
|
}
|
|
2204
|
-
if (typeof heroMedia.ogImageHeight ===
|
|
2205
|
-
if (entry.ogImageHeight !== heroMedia.ogImageHeight)
|
|
2287
|
+
if (typeof heroMedia.ogImageHeight === "number") {
|
|
2288
|
+
if (entry.ogImageHeight !== heroMedia.ogImageHeight)
|
|
2289
|
+
touched = true;
|
|
2206
2290
|
entry.ogImageHeight = heroMedia.ogImageHeight;
|
|
2207
2291
|
} else if (entry.ogImageHeight !== undefined) {
|
|
2208
2292
|
delete entry.ogImageHeight;
|
|
@@ -2229,7 +2313,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2229
2313
|
entry,
|
|
2230
2314
|
heroMedia && heroMedia.heroThumbnail,
|
|
2231
2315
|
heroMedia && heroMedia.heroThumbnailWidth,
|
|
2232
|
-
heroMedia && heroMedia.heroThumbnailHeight
|
|
2316
|
+
heroMedia && heroMedia.heroThumbnailHeight,
|
|
2233
2317
|
)
|
|
2234
2318
|
) {
|
|
2235
2319
|
touched = true;
|
|
@@ -2255,7 +2339,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2255
2339
|
try {
|
|
2256
2340
|
annotationValue = extractAnnotationText(
|
|
2257
2341
|
manifest,
|
|
2258
|
-
annotationsOptions
|
|
2342
|
+
annotationsOptions,
|
|
2259
2343
|
);
|
|
2260
2344
|
} catch (_) {
|
|
2261
2345
|
annotationValue = "";
|
|
@@ -2298,9 +2382,13 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2298
2382
|
type: "work",
|
|
2299
2383
|
thumbnail: recordThumbnail || undefined,
|
|
2300
2384
|
thumbnailWidth:
|
|
2301
|
-
typeof recordThumbWidth === "number"
|
|
2385
|
+
typeof recordThumbWidth === "number"
|
|
2386
|
+
? recordThumbWidth
|
|
2387
|
+
: undefined,
|
|
2302
2388
|
thumbnailHeight:
|
|
2303
|
-
typeof recordThumbHeight === "number"
|
|
2389
|
+
typeof recordThumbHeight === "number"
|
|
2390
|
+
? recordThumbHeight
|
|
2391
|
+
: undefined,
|
|
2304
2392
|
searchMetadataValues:
|
|
2305
2393
|
metadataValues && metadataValues.length
|
|
2306
2394
|
? metadataValues
|
|
@@ -2312,6 +2400,11 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2312
2400
|
? annotationValue
|
|
2313
2401
|
: undefined,
|
|
2314
2402
|
});
|
|
2403
|
+
logDebug(
|
|
2404
|
+
`Search record queued for ${manifestLabel}: ${pageHref} (metadata values ${
|
|
2405
|
+
metadataValues ? metadataValues.length : 0
|
|
2406
|
+
})`,
|
|
2407
|
+
);
|
|
2315
2408
|
} catch (e) {
|
|
2316
2409
|
lns.push([
|
|
2317
2410
|
`IIIF: failed to render for ${id || "<unknown>"} — ${e.message}`,
|
|
@@ -2324,7 +2417,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2324
2417
|
}
|
|
2325
2418
|
const workers = Array.from(
|
|
2326
2419
|
{length: Math.min(concurrency, chunk.length)},
|
|
2327
|
-
() => worker()
|
|
2420
|
+
() => worker(),
|
|
2328
2421
|
);
|
|
2329
2422
|
await Promise.all(workers);
|
|
2330
2423
|
tryFlush();
|
|
@@ -2333,48 +2426,58 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2333
2426
|
index: ci + 1,
|
|
2334
2427
|
count: chunk.length,
|
|
2335
2428
|
durationMs: chunkDuration,
|
|
2336
|
-
concurrency,
|
|
2337
2429
|
});
|
|
2338
2430
|
try {
|
|
2339
|
-
const concurrencyLabel =
|
|
2340
|
-
requestedConcurrency === 0 ? `${concurrency} (auto)` : String(concurrency);
|
|
2341
2431
|
logLine(
|
|
2342
|
-
`⏱ Chunk ${ci + 1}/${chunks}: processed ${chunk.length} Manifest(s) in ${formatDurationMs(chunkDuration)}
|
|
2432
|
+
`⏱ Chunk ${ci + 1}/${chunks}: processed ${chunk.length} Manifest(s) in ${formatDurationMs(chunkDuration)}`,
|
|
2343
2433
|
"cyan",
|
|
2344
|
-
{
|
|
2434
|
+
{dim: true},
|
|
2345
2435
|
);
|
|
2346
2436
|
} catch (_) {}
|
|
2347
2437
|
}
|
|
2348
2438
|
if (chunkMetrics.length) {
|
|
2349
2439
|
const totalDuration = chunkMetrics.reduce(
|
|
2350
2440
|
(sum, entry) => sum + (entry.durationMs || 0),
|
|
2351
|
-
0
|
|
2441
|
+
0,
|
|
2442
|
+
);
|
|
2443
|
+
const totalItems = chunkMetrics.reduce(
|
|
2444
|
+
(sum, entry) => sum + (entry.count || 0),
|
|
2445
|
+
0,
|
|
2352
2446
|
);
|
|
2353
|
-
const totalItems = chunkMetrics.reduce((sum, entry) => sum + (entry.count || 0), 0);
|
|
2354
2447
|
const avgDuration = chunkMetrics.length
|
|
2355
2448
|
? totalDuration / chunkMetrics.length
|
|
2356
2449
|
: 0;
|
|
2357
2450
|
const rate = totalDuration > 0 ? totalItems / (totalDuration / 1000) : 0;
|
|
2358
2451
|
try {
|
|
2359
|
-
const rateLabel = rate ? `${rate.toFixed(1)} manifest(s)/s` :
|
|
2452
|
+
const rateLabel = rate ? `${rate.toFixed(1)} manifest(s)/s` : "n/a";
|
|
2360
2453
|
logLine(
|
|
2361
2454
|
`IIIF chunk summary: ${totalItems} Manifest(s) in ${formatDurationMs(totalDuration)} (avg chunk ${formatDurationMs(avgDuration)}, ${rateLabel})`,
|
|
2362
2455
|
"cyan",
|
|
2363
|
-
{
|
|
2456
|
+
{dim: true},
|
|
2364
2457
|
);
|
|
2365
2458
|
} catch (_) {}
|
|
2366
2459
|
}
|
|
2367
2460
|
try {
|
|
2368
2461
|
await navPlace.writeNavPlaceDataset(navPlaceRecords);
|
|
2462
|
+
try {
|
|
2463
|
+
logLine(
|
|
2464
|
+
`✓ Wrote navPlace dataset (${navPlaceRecords.length} record(s))`,
|
|
2465
|
+
"cyan",
|
|
2466
|
+
);
|
|
2467
|
+
} catch (_) {}
|
|
2369
2468
|
} catch (error) {
|
|
2370
2469
|
try {
|
|
2371
2470
|
console.warn(
|
|
2372
|
-
|
|
2373
|
-
error && error.message ? error.message : error
|
|
2471
|
+
"[canopy][navPlace] failed to write dataset:",
|
|
2472
|
+
error && error.message ? error.message : error,
|
|
2374
2473
|
);
|
|
2375
2474
|
} catch (_) {}
|
|
2376
2475
|
}
|
|
2377
|
-
return {
|
|
2476
|
+
return {
|
|
2477
|
+
iiifRecords,
|
|
2478
|
+
manifestIds: Array.from(renderedManifestIds),
|
|
2479
|
+
collectionIds: Array.from(visitedCollections),
|
|
2480
|
+
};
|
|
2378
2481
|
}
|
|
2379
2482
|
|
|
2380
2483
|
module.exports = {
|
|
@@ -2382,11 +2485,12 @@ module.exports = {
|
|
|
2382
2485
|
loadConfig,
|
|
2383
2486
|
loadManifestIndex,
|
|
2384
2487
|
saveManifestIndex,
|
|
2488
|
+
resolveIiifSources,
|
|
2385
2489
|
// Expose helpers used by build for cache warming
|
|
2386
2490
|
loadCachedManifestById,
|
|
2387
2491
|
saveCachedManifest,
|
|
2388
2492
|
ensureFeaturedInCache,
|
|
2389
|
-
|
|
2493
|
+
cleanupIiifCache,
|
|
2390
2494
|
};
|
|
2391
2495
|
|
|
2392
2496
|
// Expose a stable set of pure helper utilities for unit testing.
|
|
@@ -2395,6 +2499,7 @@ module.exports.__TESTING__ = {
|
|
|
2395
2499
|
formatDurationMs,
|
|
2396
2500
|
resolveBoolean,
|
|
2397
2501
|
normalizeCollectionUris,
|
|
2502
|
+
normalizeManifestConfig,
|
|
2398
2503
|
clampSlugLength,
|
|
2399
2504
|
isSlugTooLong,
|
|
2400
2505
|
normalizeSlugBase,
|
|
@@ -2436,7 +2541,7 @@ try {
|
|
|
2436
2541
|
`IIIF: cache/collections (end): ${files.length} file(s)` +
|
|
2437
2542
|
(head ? ` [${head}${files.length > 8 ? ", …" : ""}]` : ""),
|
|
2438
2543
|
"blue",
|
|
2439
|
-
{dim: true}
|
|
2544
|
+
{dim: true},
|
|
2440
2545
|
);
|
|
2441
2546
|
} catch (_) {}
|
|
2442
2547
|
}
|