@canopy-iiif/app 1.6.0 → 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 +338 -262
- 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
|
}
|
|
@@ -113,6 +121,18 @@ function normalizeManifestConfig(cfg) {
|
|
|
113
121
|
return normalizeCollectionUris(entries);
|
|
114
122
|
}
|
|
115
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
|
+
|
|
116
136
|
function clampSlugLength(slug, limit = MAX_ENTRY_SLUG_LENGTH) {
|
|
117
137
|
if (!slug) return "";
|
|
118
138
|
const max = Math.max(1, limit);
|
|
@@ -146,8 +166,8 @@ function extractHomepageId(resource) {
|
|
|
146
166
|
const list = Array.isArray(homepageRaw)
|
|
147
167
|
? homepageRaw
|
|
148
168
|
: homepageRaw
|
|
149
|
-
|
|
150
|
-
|
|
169
|
+
? [homepageRaw]
|
|
170
|
+
: [];
|
|
151
171
|
for (const entry of list) {
|
|
152
172
|
if (!entry) continue;
|
|
153
173
|
if (typeof entry === "string") {
|
|
@@ -221,7 +241,7 @@ function resolveThumbnailPreferences() {
|
|
|
221
241
|
return {
|
|
222
242
|
size: resolvePositiveInteger(
|
|
223
243
|
process.env.CANOPY_THUMBNAIL_SIZE,
|
|
224
|
-
DEFAULT_THUMBNAIL_SIZE
|
|
244
|
+
DEFAULT_THUMBNAIL_SIZE,
|
|
225
245
|
),
|
|
226
246
|
unsafe: resolveBoolean(process.env.CANOPY_THUMBNAILS_UNSAFE),
|
|
227
247
|
};
|
|
@@ -269,7 +289,7 @@ async function resolveHeroMedia(manifest) {
|
|
|
269
289
|
const manifestThumb = extractResourceThumbnail(manifest);
|
|
270
290
|
const heroSource = (() => {
|
|
271
291
|
if (manifest && manifest.thumbnail) {
|
|
272
|
-
const clone = {
|
|
292
|
+
const clone = {...manifest};
|
|
273
293
|
try {
|
|
274
294
|
delete clone.thumbnail;
|
|
275
295
|
} catch (_) {
|
|
@@ -282,53 +302,49 @@ async function resolveHeroMedia(manifest) {
|
|
|
282
302
|
const heroRep = await getRepresentativeImage(
|
|
283
303
|
heroSource || manifest,
|
|
284
304
|
HERO_REPRESENTATIVE_SIZE,
|
|
285
|
-
true
|
|
305
|
+
true,
|
|
286
306
|
);
|
|
287
307
|
const canvasImage = findPrimaryCanvasImage(manifest);
|
|
288
308
|
const heroService =
|
|
289
|
-
(canvasImage && canvasImage.service) ||
|
|
290
|
-
(heroRep && heroRep.service);
|
|
309
|
+
(canvasImage && canvasImage.service) || (heroRep && heroRep.service);
|
|
291
310
|
const serviceIsLevel0 = isLevel0Service(heroService);
|
|
292
311
|
const heroPreferred = buildIiifImageUrlFromService(
|
|
293
312
|
serviceIsLevel0 ? null : heroService,
|
|
294
|
-
HERO_THUMBNAIL_SIZE
|
|
313
|
+
HERO_THUMBNAIL_SIZE,
|
|
295
314
|
);
|
|
296
315
|
const heroWidth = (() => {
|
|
297
|
-
if (canvasImage && typeof canvasImage.width ===
|
|
316
|
+
if (canvasImage && typeof canvasImage.width === "number")
|
|
298
317
|
return canvasImage.width;
|
|
299
|
-
if (heroRep && typeof heroRep.width ===
|
|
318
|
+
if (heroRep && typeof heroRep.width === "number") return heroRep.width;
|
|
300
319
|
return undefined;
|
|
301
320
|
})();
|
|
302
321
|
const heroHeight = (() => {
|
|
303
|
-
if (canvasImage && typeof canvasImage.height ===
|
|
322
|
+
if (canvasImage && typeof canvasImage.height === "number")
|
|
304
323
|
return canvasImage.height;
|
|
305
|
-
if (heroRep && typeof heroRep.height ===
|
|
306
|
-
return heroRep.height;
|
|
324
|
+
if (heroRep && typeof heroRep.height === "number") return heroRep.height;
|
|
307
325
|
return undefined;
|
|
308
326
|
})();
|
|
309
|
-
const heroSrcset = serviceIsLevel0
|
|
310
|
-
? ''
|
|
311
|
-
: buildIiifImageSrcset(heroService);
|
|
327
|
+
const heroSrcset = serviceIsLevel0 ? "" : buildIiifImageSrcset(heroService);
|
|
312
328
|
const ogFromService =
|
|
313
329
|
!serviceIsLevel0 && heroService
|
|
314
330
|
? buildIiifImageUrlForDimensions(
|
|
315
331
|
heroService,
|
|
316
332
|
OG_IMAGE_WIDTH,
|
|
317
|
-
OG_IMAGE_HEIGHT
|
|
333
|
+
OG_IMAGE_HEIGHT,
|
|
318
334
|
)
|
|
319
|
-
:
|
|
335
|
+
: "";
|
|
320
336
|
const annotationImageId =
|
|
321
337
|
canvasImage && canvasImage.isImageBody && canvasImage.id
|
|
322
338
|
? String(canvasImage.id)
|
|
323
|
-
:
|
|
324
|
-
let heroThumbnail = heroPreferred ||
|
|
339
|
+
: "";
|
|
340
|
+
let heroThumbnail = heroPreferred || "";
|
|
325
341
|
let heroThumbWidth = heroWidth;
|
|
326
342
|
let heroThumbHeight = heroHeight;
|
|
327
343
|
if (!heroThumbnail && manifestThumb && manifestThumb.url) {
|
|
328
344
|
heroThumbnail = manifestThumb.url;
|
|
329
|
-
if (typeof manifestThumb.width ===
|
|
345
|
+
if (typeof manifestThumb.width === "number")
|
|
330
346
|
heroThumbWidth = manifestThumb.width;
|
|
331
|
-
if (typeof manifestThumb.height ===
|
|
347
|
+
if (typeof manifestThumb.height === "number")
|
|
332
348
|
heroThumbHeight = manifestThumb.height;
|
|
333
349
|
}
|
|
334
350
|
if (!heroThumbnail) {
|
|
@@ -338,7 +354,7 @@ async function resolveHeroMedia(manifest) {
|
|
|
338
354
|
heroThumbnail = String(heroRep.id);
|
|
339
355
|
}
|
|
340
356
|
}
|
|
341
|
-
let ogImage =
|
|
357
|
+
let ogImage = "";
|
|
342
358
|
let ogImageWidth;
|
|
343
359
|
let ogImageHeight;
|
|
344
360
|
if (ogFromService) {
|
|
@@ -347,16 +363,16 @@ async function resolveHeroMedia(manifest) {
|
|
|
347
363
|
ogImageHeight = OG_IMAGE_HEIGHT;
|
|
348
364
|
} else if (heroThumbnail) {
|
|
349
365
|
ogImage = heroThumbnail;
|
|
350
|
-
if (typeof heroThumbWidth ===
|
|
351
|
-
if (typeof heroThumbHeight ===
|
|
366
|
+
if (typeof heroThumbWidth === "number") ogImageWidth = heroThumbWidth;
|
|
367
|
+
if (typeof heroThumbHeight === "number") ogImageHeight = heroThumbHeight;
|
|
352
368
|
}
|
|
353
369
|
return {
|
|
354
|
-
heroThumbnail: heroThumbnail ||
|
|
370
|
+
heroThumbnail: heroThumbnail || "",
|
|
355
371
|
heroThumbnailWidth: heroThumbWidth,
|
|
356
372
|
heroThumbnailHeight: heroThumbHeight,
|
|
357
|
-
heroThumbnailSrcset: heroSrcset ||
|
|
358
|
-
heroThumbnailSizes: heroSrcset ? HERO_IMAGE_SIZES_ATTR :
|
|
359
|
-
ogImage: ogImage ||
|
|
373
|
+
heroThumbnailSrcset: heroSrcset || "",
|
|
374
|
+
heroThumbnailSizes: heroSrcset ? HERO_IMAGE_SIZES_ATTR : "",
|
|
375
|
+
ogImage: ogImage || "",
|
|
360
376
|
ogImageWidth,
|
|
361
377
|
ogImageHeight,
|
|
362
378
|
};
|
|
@@ -423,7 +439,7 @@ function extractSummaryValues(manifest) {
|
|
|
423
439
|
flattenMetadataValue(manifest && manifest.summary, values, 0);
|
|
424
440
|
} catch (_) {}
|
|
425
441
|
const unique = Array.from(
|
|
426
|
-
new Set(values.map((val) => String(val || "").trim()).filter(Boolean))
|
|
442
|
+
new Set(values.map((val) => String(val || "").trim()).filter(Boolean)),
|
|
427
443
|
);
|
|
428
444
|
if (!unique.length) return "";
|
|
429
445
|
return unique.join(" ");
|
|
@@ -641,7 +657,7 @@ function normalizeIiifId(raw) {
|
|
|
641
657
|
if (!/^https?:\/\//i.test(s)) return s;
|
|
642
658
|
const u = new URL(s);
|
|
643
659
|
const entries = Array.from(u.searchParams.entries()).sort(
|
|
644
|
-
(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]),
|
|
645
661
|
);
|
|
646
662
|
u.search = "";
|
|
647
663
|
for (const [k, v] of entries) u.searchParams.append(k, v);
|
|
@@ -855,18 +871,21 @@ function computeUniqueSlug(index, baseSlug, id, type) {
|
|
|
855
871
|
const byId = Array.isArray(index && index.byId) ? index.byId : [];
|
|
856
872
|
const normId = normalizeIiifId(String(id || ""));
|
|
857
873
|
const fallbackBase = type === "Manifest" ? "untitled" : "collection";
|
|
858
|
-
const normalizedBase = normalizeSlugBase(
|
|
874
|
+
const normalizedBase = normalizeSlugBase(
|
|
875
|
+
baseSlug || fallbackBase,
|
|
876
|
+
fallbackBase,
|
|
877
|
+
);
|
|
859
878
|
const used = new Set(
|
|
860
879
|
byId
|
|
861
880
|
.filter((e) => e && e.slug && e.type === type)
|
|
862
|
-
.map((e) => String(e.slug))
|
|
881
|
+
.map((e) => String(e.slug)),
|
|
863
882
|
);
|
|
864
883
|
const reserved = RESERVED_SLUGS[type] || new Set();
|
|
865
884
|
let slug = normalizedBase;
|
|
866
885
|
let i = 1;
|
|
867
886
|
for (;;) {
|
|
868
887
|
const existing = byId.find(
|
|
869
|
-
(e) => e && e.type === type && String(e.slug) === String(slug)
|
|
888
|
+
(e) => e && e.type === type && String(e.slug) === String(slug),
|
|
870
889
|
);
|
|
871
890
|
if (existing) {
|
|
872
891
|
// If this slug already maps to this id, reuse it and reserve.
|
|
@@ -888,9 +907,12 @@ function ensureBaseSlugFor(index, baseSlug, id, type) {
|
|
|
888
907
|
const byId = Array.isArray(index && index.byId) ? index.byId : [];
|
|
889
908
|
const normId = normalizeIiifId(String(id || ""));
|
|
890
909
|
const fallbackBase = type === "Manifest" ? "untitled" : "collection";
|
|
891
|
-
const normalizedBase = normalizeSlugBase(
|
|
910
|
+
const normalizedBase = normalizeSlugBase(
|
|
911
|
+
baseSlug || fallbackBase,
|
|
912
|
+
fallbackBase,
|
|
913
|
+
);
|
|
892
914
|
const existingWithBase = byId.find(
|
|
893
|
-
(e) => e && e.type === type && String(e.slug) === String(normalizedBase)
|
|
915
|
+
(e) => e && e.type === type && String(e.slug) === String(normalizedBase),
|
|
894
916
|
);
|
|
895
917
|
if (existingWithBase && normalizeIiifId(existingWithBase.id) !== normId) {
|
|
896
918
|
// Reassign the existing entry to the next available suffix to free the base
|
|
@@ -898,9 +920,10 @@ function ensureBaseSlugFor(index, baseSlug, id, type) {
|
|
|
898
920
|
index,
|
|
899
921
|
normalizedBase,
|
|
900
922
|
existingWithBase.id,
|
|
901
|
-
type
|
|
923
|
+
type,
|
|
902
924
|
);
|
|
903
|
-
if (newSlug && newSlug !== normalizedBase)
|
|
925
|
+
if (newSlug && newSlug !== normalizedBase)
|
|
926
|
+
existingWithBase.slug = newSlug;
|
|
904
927
|
}
|
|
905
928
|
} catch (_) {}
|
|
906
929
|
return baseSlug;
|
|
@@ -917,7 +940,7 @@ async function findSlugByIdFromDisk(id) {
|
|
|
917
940
|
const raw = await fsp.readFile(p, "utf8");
|
|
918
941
|
const obj = JSON.parse(raw);
|
|
919
942
|
const mid = normalizeIiifId(
|
|
920
|
-
String((obj && (obj.id || obj["@id"])) || "")
|
|
943
|
+
String((obj && (obj.id || obj["@id"])) || ""),
|
|
921
944
|
);
|
|
922
945
|
if (mid && mid === normalizeIiifId(String(id))) {
|
|
923
946
|
const slug = name.replace(/\.json$/i, "");
|
|
@@ -937,7 +960,7 @@ async function loadCachedManifestById(id) {
|
|
|
937
960
|
if (Array.isArray(index.byId)) {
|
|
938
961
|
const nid = normalizeIiifId(id);
|
|
939
962
|
const entry = index.byId.find(
|
|
940
|
-
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Manifest"
|
|
963
|
+
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Manifest",
|
|
941
964
|
);
|
|
942
965
|
slug = entry && entry.slug;
|
|
943
966
|
}
|
|
@@ -957,7 +980,8 @@ async function loadCachedManifestById(id) {
|
|
|
957
980
|
index.byId = Array.isArray(index.byId) ? index.byId : [];
|
|
958
981
|
const nid = normalizeIiifId(id);
|
|
959
982
|
const existingEntryIdx = index.byId.findIndex(
|
|
960
|
-
(e) =>
|
|
983
|
+
(e) =>
|
|
984
|
+
e && normalizeIiifId(e.id) === nid && e.type === "Manifest",
|
|
961
985
|
);
|
|
962
986
|
const entry = {
|
|
963
987
|
id: String(nid),
|
|
@@ -977,7 +1001,8 @@ async function loadCachedManifestById(id) {
|
|
|
977
1001
|
const p = path.join(IIIF_CACHE_MANIFESTS_DIR, slug + ".json");
|
|
978
1002
|
if (!fs.existsSync(p)) return null;
|
|
979
1003
|
const raw = await readJson(p);
|
|
980
|
-
const {manifest: normalized, changed} =
|
|
1004
|
+
const {manifest: normalized, changed} =
|
|
1005
|
+
await ensurePresentation3Manifest(raw);
|
|
981
1006
|
if (changed) {
|
|
982
1007
|
try {
|
|
983
1008
|
await fsp.writeFile(p, JSON.stringify(normalized, null, 2), "utf8");
|
|
@@ -987,12 +1012,17 @@ async function loadCachedManifestById(id) {
|
|
|
987
1012
|
index.byId = Array.isArray(index.byId) ? index.byId : [];
|
|
988
1013
|
const nid = normalizeIiifId(id);
|
|
989
1014
|
const existingEntryIdx = index.byId.findIndex(
|
|
990
|
-
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Manifest"
|
|
1015
|
+
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Manifest",
|
|
991
1016
|
);
|
|
992
1017
|
if (existingEntryIdx >= 0) {
|
|
993
1018
|
const entry = index.byId[existingEntryIdx];
|
|
994
|
-
const prevCanonical =
|
|
995
|
-
|
|
1019
|
+
const prevCanonical =
|
|
1020
|
+
entry && entry.canonical ? String(entry.canonical) : "";
|
|
1021
|
+
const nextCanonical = applyManifestEntryCanonical(
|
|
1022
|
+
entry,
|
|
1023
|
+
normalized,
|
|
1024
|
+
slug,
|
|
1025
|
+
);
|
|
996
1026
|
if (nextCanonical !== prevCanonical) {
|
|
997
1027
|
await saveManifestIndex(index);
|
|
998
1028
|
}
|
|
@@ -1005,21 +1035,28 @@ async function loadCachedManifestById(id) {
|
|
|
1005
1035
|
}
|
|
1006
1036
|
|
|
1007
1037
|
async function saveCachedManifest(manifest, id, parentId) {
|
|
1008
|
-
const {manifest: normalizedManifest} =
|
|
1038
|
+
const {manifest: normalizedManifest} =
|
|
1039
|
+
await ensurePresentation3Manifest(manifest);
|
|
1009
1040
|
try {
|
|
1010
1041
|
const index = await loadManifestIndex();
|
|
1011
|
-
const title = firstLabelString(
|
|
1042
|
+
const title = firstLabelString(
|
|
1043
|
+
normalizedManifest && normalizedManifest.label,
|
|
1044
|
+
);
|
|
1012
1045
|
const baseSlug =
|
|
1013
1046
|
slugify(title || "untitled", {lower: true, strict: true, trim: true}) ||
|
|
1014
1047
|
"untitled";
|
|
1015
1048
|
const slug = computeUniqueSlug(index, baseSlug, id, "Manifest");
|
|
1016
1049
|
ensureDirSync(IIIF_CACHE_MANIFESTS_DIR);
|
|
1017
1050
|
const dest = path.join(IIIF_CACHE_MANIFESTS_DIR, slug + ".json");
|
|
1018
|
-
await fsp.writeFile(
|
|
1051
|
+
await fsp.writeFile(
|
|
1052
|
+
dest,
|
|
1053
|
+
JSON.stringify(normalizedManifest, null, 2),
|
|
1054
|
+
"utf8",
|
|
1055
|
+
);
|
|
1019
1056
|
index.byId = Array.isArray(index.byId) ? index.byId : [];
|
|
1020
1057
|
const nid = normalizeIiifId(id);
|
|
1021
1058
|
const existingEntryIdx = index.byId.findIndex(
|
|
1022
|
-
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Manifest"
|
|
1059
|
+
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Manifest",
|
|
1023
1060
|
);
|
|
1024
1061
|
const entry = {
|
|
1025
1062
|
id: String(nid),
|
|
@@ -1070,7 +1107,7 @@ async function ensureFeaturedInCache(cfg) {
|
|
|
1070
1107
|
e &&
|
|
1071
1108
|
e.type === "Manifest" &&
|
|
1072
1109
|
normalizeIiifId(String(e.id)) ===
|
|
1073
|
-
normalizeIiifId(String(manifest.id))
|
|
1110
|
+
normalizeIiifId(String(manifest.id)),
|
|
1074
1111
|
);
|
|
1075
1112
|
if (!entry) continue;
|
|
1076
1113
|
|
|
@@ -1122,7 +1159,8 @@ async function ensureFeaturedInCache(cfg) {
|
|
|
1122
1159
|
if (entry.heroThumbnailSrcset !== heroMedia.heroThumbnailSrcset)
|
|
1123
1160
|
touched = true;
|
|
1124
1161
|
entry.heroThumbnailSrcset = heroMedia.heroThumbnailSrcset;
|
|
1125
|
-
if (entry.heroThumbnailSizes !== HERO_IMAGE_SIZES_ATTR)
|
|
1162
|
+
if (entry.heroThumbnailSizes !== HERO_IMAGE_SIZES_ATTR)
|
|
1163
|
+
touched = true;
|
|
1126
1164
|
entry.heroThumbnailSizes = HERO_IMAGE_SIZES_ATTR;
|
|
1127
1165
|
} else {
|
|
1128
1166
|
if (entry.heroThumbnailSrcset !== undefined) {
|
|
@@ -1139,15 +1177,16 @@ async function ensureFeaturedInCache(cfg) {
|
|
|
1139
1177
|
entry.ogImage = heroMedia.ogImage;
|
|
1140
1178
|
touched = true;
|
|
1141
1179
|
}
|
|
1142
|
-
if (typeof heroMedia.ogImageWidth ===
|
|
1180
|
+
if (typeof heroMedia.ogImageWidth === "number") {
|
|
1143
1181
|
if (entry.ogImageWidth !== heroMedia.ogImageWidth) touched = true;
|
|
1144
1182
|
entry.ogImageWidth = heroMedia.ogImageWidth;
|
|
1145
1183
|
} else if (entry.ogImageWidth !== undefined) {
|
|
1146
1184
|
delete entry.ogImageWidth;
|
|
1147
1185
|
touched = true;
|
|
1148
1186
|
}
|
|
1149
|
-
if (typeof heroMedia.ogImageHeight ===
|
|
1150
|
-
if (entry.ogImageHeight !== heroMedia.ogImageHeight)
|
|
1187
|
+
if (typeof heroMedia.ogImageHeight === "number") {
|
|
1188
|
+
if (entry.ogImageHeight !== heroMedia.ogImageHeight)
|
|
1189
|
+
touched = true;
|
|
1151
1190
|
entry.ogImageHeight = heroMedia.ogImageHeight;
|
|
1152
1191
|
} else if (entry.ogImageHeight !== undefined) {
|
|
1153
1192
|
delete entry.ogImageHeight;
|
|
@@ -1164,7 +1203,7 @@ async function ensureFeaturedInCache(cfg) {
|
|
|
1164
1203
|
entry,
|
|
1165
1204
|
heroMedia && heroMedia.heroThumbnail,
|
|
1166
1205
|
heroMedia && heroMedia.heroThumbnailWidth,
|
|
1167
|
-
heroMedia && heroMedia.heroThumbnailHeight
|
|
1206
|
+
heroMedia && heroMedia.heroThumbnailHeight,
|
|
1168
1207
|
)
|
|
1169
1208
|
) {
|
|
1170
1209
|
touched = true;
|
|
@@ -1201,7 +1240,7 @@ async function loadCachedCollectionById(id) {
|
|
|
1201
1240
|
if (Array.isArray(index.byId)) {
|
|
1202
1241
|
const nid = normalizeIiifId(id);
|
|
1203
1242
|
const entry = index.byId.find(
|
|
1204
|
-
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Collection"
|
|
1243
|
+
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Collection",
|
|
1205
1244
|
);
|
|
1206
1245
|
slug = entry && entry.slug;
|
|
1207
1246
|
}
|
|
@@ -1218,7 +1257,7 @@ async function loadCachedCollectionById(id) {
|
|
|
1218
1257
|
const raw = await fsp.readFile(p, "utf8");
|
|
1219
1258
|
const obj = JSON.parse(raw);
|
|
1220
1259
|
const cid = normalizeIiifId(
|
|
1221
|
-
String((obj && (obj.id || obj["@id"])) || "")
|
|
1260
|
+
String((obj && (obj.id || obj["@id"])) || ""),
|
|
1222
1261
|
);
|
|
1223
1262
|
if (cid && cid === normalizeIiifId(String(id))) {
|
|
1224
1263
|
const candidate = name.replace(/\.json$/i, "");
|
|
@@ -1235,7 +1274,7 @@ async function loadCachedCollectionById(id) {
|
|
|
1235
1274
|
(e) =>
|
|
1236
1275
|
e &&
|
|
1237
1276
|
normalizeIiifId(e.id) === nid &&
|
|
1238
|
-
e.type === "Collection"
|
|
1277
|
+
e.type === "Collection",
|
|
1239
1278
|
);
|
|
1240
1279
|
const entry = {
|
|
1241
1280
|
id: String(nid),
|
|
@@ -1263,11 +1302,12 @@ async function loadCachedCollectionById(id) {
|
|
|
1263
1302
|
index.byId = Array.isArray(index.byId) ? index.byId : [];
|
|
1264
1303
|
const nid = normalizeIiifId(id);
|
|
1265
1304
|
const existingEntryIdx = index.byId.findIndex(
|
|
1266
|
-
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Collection"
|
|
1305
|
+
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Collection",
|
|
1267
1306
|
);
|
|
1268
1307
|
if (existingEntryIdx >= 0) {
|
|
1269
1308
|
const entry = index.byId[existingEntryIdx];
|
|
1270
|
-
const prevCanonical =
|
|
1309
|
+
const prevCanonical =
|
|
1310
|
+
entry && entry.canonical ? String(entry.canonical) : "";
|
|
1271
1311
|
const nextCanonical = applyCollectionEntryCanonical(entry, data);
|
|
1272
1312
|
if (nextCanonical !== prevCanonical) {
|
|
1273
1313
|
await saveManifestIndex(index);
|
|
@@ -1285,7 +1325,9 @@ async function saveCachedCollection(collection, id, parentId) {
|
|
|
1285
1325
|
const normalizedCollection = await upgradeIiifResource(collection);
|
|
1286
1326
|
ensureDirSync(IIIF_CACHE_COLLECTIONS_DIR);
|
|
1287
1327
|
const index = await loadManifestIndex();
|
|
1288
|
-
const title = firstLabelString(
|
|
1328
|
+
const title = firstLabelString(
|
|
1329
|
+
normalizedCollection && normalizedCollection.label,
|
|
1330
|
+
);
|
|
1289
1331
|
const baseSlug =
|
|
1290
1332
|
slugify(title || "collection", {
|
|
1291
1333
|
lower: true,
|
|
@@ -1294,7 +1336,11 @@ async function saveCachedCollection(collection, id, parentId) {
|
|
|
1294
1336
|
}) || "collection";
|
|
1295
1337
|
const slug = computeUniqueSlug(index, baseSlug, id, "Collection");
|
|
1296
1338
|
const dest = path.join(IIIF_CACHE_COLLECTIONS_DIR, slug + ".json");
|
|
1297
|
-
await fsp.writeFile(
|
|
1339
|
+
await fsp.writeFile(
|
|
1340
|
+
dest,
|
|
1341
|
+
JSON.stringify(normalizedCollection, null, 2),
|
|
1342
|
+
"utf8",
|
|
1343
|
+
);
|
|
1298
1344
|
try {
|
|
1299
1345
|
if (process.env.CANOPY_IIIF_DEBUG === "1") {
|
|
1300
1346
|
const {logLine} = require("./log");
|
|
@@ -1304,7 +1350,7 @@ async function saveCachedCollection(collection, id, parentId) {
|
|
|
1304
1350
|
index.byId = Array.isArray(index.byId) ? index.byId : [];
|
|
1305
1351
|
const nid = normalizeIiifId(id);
|
|
1306
1352
|
const existingEntryIdx = index.byId.findIndex(
|
|
1307
|
-
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Collection"
|
|
1353
|
+
(e) => e && normalizeIiifId(e.id) === nid && e.type === "Collection",
|
|
1308
1354
|
);
|
|
1309
1355
|
const entry = {
|
|
1310
1356
|
id: String(nid),
|
|
@@ -1319,133 +1365,96 @@ async function saveCachedCollection(collection, id, parentId) {
|
|
|
1319
1365
|
} catch (_) {}
|
|
1320
1366
|
}
|
|
1321
1367
|
|
|
1322
|
-
async function
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
.sort()
|
|
1341
|
-
: [];
|
|
1342
|
-
const manifestFiles = fs.existsSync(IIIF_CACHE_MANIFESTS_DIR)
|
|
1343
|
-
? (await fsp.readdir(IIIF_CACHE_MANIFESTS_DIR))
|
|
1344
|
-
.filter((name) => name && name.toLowerCase().endsWith(".json"))
|
|
1345
|
-
.sort()
|
|
1346
|
-
: [];
|
|
1347
|
-
const {size: thumbSize, unsafe: unsafeThumbs} =
|
|
1348
|
-
resolveThumbnailPreferences();
|
|
1349
|
-
|
|
1350
|
-
for (const name of collectionFiles) {
|
|
1351
|
-
const slug = name.replace(/\.json$/i, "");
|
|
1352
|
-
const fp = path.join(IIIF_CACHE_COLLECTIONS_DIR, name);
|
|
1353
|
-
let data = null;
|
|
1354
|
-
try {
|
|
1355
|
-
data = await readJson(fp);
|
|
1356
|
-
} catch (_) {
|
|
1357
|
-
data = null;
|
|
1358
|
-
}
|
|
1359
|
-
if (!data) continue;
|
|
1360
|
-
const id = data.id || data["@id"];
|
|
1361
|
-
if (!id) continue;
|
|
1362
|
-
const nid = normalizeIiifId(String(id));
|
|
1363
|
-
const key = `Collection:${nid}`;
|
|
1364
|
-
const fallback = priorMap.get(key) || {};
|
|
1365
|
-
const parent = resolveParentFromPartOf(data) || fallback.parent || "";
|
|
1366
|
-
const entry = {
|
|
1367
|
-
id: String(nid),
|
|
1368
|
-
type: "Collection",
|
|
1369
|
-
slug,
|
|
1370
|
-
parent,
|
|
1371
|
-
};
|
|
1372
|
-
applyCollectionEntryCanonical(entry, data);
|
|
1373
|
-
nextIndex.byId.push(entry);
|
|
1374
|
-
}
|
|
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;
|
|
1375
1386
|
|
|
1376
|
-
|
|
1377
|
-
|
|
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;
|
|
1378
1392
|
const fp = path.join(IIIF_CACHE_MANIFESTS_DIR, name);
|
|
1379
1393
|
let manifest = null;
|
|
1380
1394
|
try {
|
|
1381
1395
|
manifest = await readJson(fp);
|
|
1382
|
-
} catch (_) {
|
|
1383
|
-
manifest = null;
|
|
1384
|
-
}
|
|
1385
|
-
if (!manifest) continue;
|
|
1386
|
-
const id = manifest.id || manifest["@id"];
|
|
1387
|
-
if (!id) continue;
|
|
1388
|
-
const nid = normalizeIiifId(String(id));
|
|
1389
|
-
MEMO_ID_TO_SLUG.set(String(id), slug);
|
|
1390
|
-
const key = `Manifest:${nid}`;
|
|
1391
|
-
const fallback = priorMap.get(key) || {};
|
|
1392
|
-
const parent = resolveParentFromPartOf(manifest) || fallback.parent || "";
|
|
1393
|
-
const entry = {
|
|
1394
|
-
id: String(nid),
|
|
1395
|
-
type: "Manifest",
|
|
1396
|
-
slug,
|
|
1397
|
-
parent,
|
|
1398
|
-
};
|
|
1399
|
-
applyManifestEntryCanonical(entry, manifest, slug);
|
|
1400
|
-
try {
|
|
1401
|
-
const thumb = await getThumbnail(manifest, thumbSize, unsafeThumbs);
|
|
1402
|
-
if (thumb && thumb.url) {
|
|
1403
|
-
entry.thumbnail = String(thumb.url);
|
|
1404
|
-
if (typeof thumb.width === "number") entry.thumbnailWidth = thumb.width;
|
|
1405
|
-
if (typeof thumb.height === "number") entry.thumbnailHeight = thumb.height;
|
|
1406
|
-
}
|
|
1407
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;
|
|
1408
1419
|
try {
|
|
1409
|
-
|
|
1410
|
-
if (heroMedia && heroMedia.heroThumbnail) {
|
|
1411
|
-
entry.heroThumbnail = heroMedia.heroThumbnail;
|
|
1412
|
-
if (typeof heroMedia.heroThumbnailWidth === "number")
|
|
1413
|
-
entry.heroThumbnailWidth = heroMedia.heroThumbnailWidth;
|
|
1414
|
-
if (typeof heroMedia.heroThumbnailHeight === "number")
|
|
1415
|
-
entry.heroThumbnailHeight = heroMedia.heroThumbnailHeight;
|
|
1416
|
-
if (heroMedia.heroThumbnailSrcset) {
|
|
1417
|
-
entry.heroThumbnailSrcset = heroMedia.heroThumbnailSrcset;
|
|
1418
|
-
entry.heroThumbnailSizes = HERO_IMAGE_SIZES_ATTR;
|
|
1419
|
-
}
|
|
1420
|
-
if (heroMedia.ogImage) {
|
|
1421
|
-
entry.ogImage = heroMedia.ogImage;
|
|
1422
|
-
if (typeof heroMedia.ogImageWidth === 'number')
|
|
1423
|
-
entry.ogImageWidth = heroMedia.ogImageWidth;
|
|
1424
|
-
else delete entry.ogImageWidth;
|
|
1425
|
-
if (typeof heroMedia.ogImageHeight === 'number')
|
|
1426
|
-
entry.ogImageHeight = heroMedia.ogImageHeight;
|
|
1427
|
-
else delete entry.ogImageHeight;
|
|
1428
|
-
}
|
|
1429
|
-
ensureThumbnailValue(
|
|
1430
|
-
entry,
|
|
1431
|
-
heroMedia.heroThumbnail,
|
|
1432
|
-
heroMedia.heroThumbnailWidth,
|
|
1433
|
-
heroMedia.heroThumbnailHeight
|
|
1434
|
-
);
|
|
1435
|
-
}
|
|
1420
|
+
collection = await readJson(fp);
|
|
1436
1421
|
} catch (_) {}
|
|
1437
|
-
|
|
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
|
+
}
|
|
1438
1434
|
}
|
|
1439
|
-
|
|
1440
|
-
await saveManifestIndex(nextIndex);
|
|
1441
|
-
try {
|
|
1442
|
-
logLine("✓ Rebuilt IIIF cache index", "cyan");
|
|
1443
|
-
} catch (_) {}
|
|
1444
|
-
} catch (err) {
|
|
1445
|
-
try {
|
|
1446
|
-
logLine("! Skipped IIIF index rebuild", "yellow");
|
|
1447
|
-
} catch (_) {}
|
|
1448
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 (_) {}
|
|
1449
1458
|
}
|
|
1450
1459
|
|
|
1451
1460
|
async function loadConfig() {
|
|
@@ -1465,14 +1474,9 @@ async function loadConfig() {
|
|
|
1465
1474
|
async function buildIiifCollectionPages(CONFIG) {
|
|
1466
1475
|
const cfg = CONFIG || (await loadConfig());
|
|
1467
1476
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
process.env.CANOPY_COLLECTION_URI || ""
|
|
1472
|
-
);
|
|
1473
|
-
}
|
|
1474
|
-
const manifestUris = normalizeManifestConfig(cfg);
|
|
1475
|
-
if (!collectionUris.length && !manifestUris.length) return {searchRecords: []};
|
|
1477
|
+
const {collections: collectionUris, manifests: manifestUris} =
|
|
1478
|
+
resolveIiifSources(cfg);
|
|
1479
|
+
if (!collectionUris.length && !manifestUris.length) return {iiifRecords: []};
|
|
1476
1480
|
|
|
1477
1481
|
const searchIndexCfg = (cfg && cfg.search && cfg.search.index) || {};
|
|
1478
1482
|
const metadataCfg = (searchIndexCfg && searchIndexCfg.metadata) || {};
|
|
@@ -1498,7 +1502,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1498
1502
|
const metadataLabelSet = new Set(
|
|
1499
1503
|
metadataLabelsRaw
|
|
1500
1504
|
.map((label) => normalizeMetadataLabel(String(label || "")))
|
|
1501
|
-
.filter(Boolean)
|
|
1505
|
+
.filter(Boolean),
|
|
1502
1506
|
);
|
|
1503
1507
|
const metadataFacetLabels = (() => {
|
|
1504
1508
|
if (!Array.isArray(metadataLabelsRaw) || !metadataLabelsRaw.length)
|
|
@@ -1506,7 +1510,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1506
1510
|
const seen = new Set();
|
|
1507
1511
|
const entries = [];
|
|
1508
1512
|
for (const label of metadataLabelsRaw) {
|
|
1509
|
-
const raw =
|
|
1513
|
+
const raw =
|
|
1514
|
+
typeof label === "string" ? label.trim() : String(label || "");
|
|
1510
1515
|
if (!raw) continue;
|
|
1511
1516
|
const normalized = normalizeMetadataLabel(raw);
|
|
1512
1517
|
if (!normalized || seen.has(normalized)) continue;
|
|
@@ -1529,8 +1534,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1529
1534
|
};
|
|
1530
1535
|
const annotationMotivations = new Set(
|
|
1531
1536
|
normalizeStringList(annotationsCfg && annotationsCfg.motivation).map((m) =>
|
|
1532
|
-
m.toLowerCase()
|
|
1533
|
-
)
|
|
1537
|
+
m.toLowerCase(),
|
|
1538
|
+
),
|
|
1534
1539
|
);
|
|
1535
1540
|
const annotationsOptions = {
|
|
1536
1541
|
enabled: annotationsEnabled,
|
|
@@ -1539,8 +1544,11 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1539
1544
|
|
|
1540
1545
|
// Recursively traverse Collections and gather all Manifest tasks
|
|
1541
1546
|
const tasks = [];
|
|
1547
|
+
let manifestTasksFromCollections = 0;
|
|
1548
|
+
let manifestTasksFromConfig = 0;
|
|
1542
1549
|
const queuedManifestIds = new Set();
|
|
1543
1550
|
const visitedCollections = new Set(); // normalized ids
|
|
1551
|
+
const renderedManifestIds = new Set();
|
|
1544
1552
|
const norm = (x) => {
|
|
1545
1553
|
try {
|
|
1546
1554
|
return normalizeIiifId(String(x || ""));
|
|
@@ -1566,9 +1574,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1566
1574
|
const ncol = await upgradeIiifResource(col);
|
|
1567
1575
|
const reportedId = String(
|
|
1568
1576
|
(ncol && (ncol.id || ncol["@id"])) ||
|
|
1569
|
-
(typeof colLike === "object" &&
|
|
1570
|
-
|
|
1571
|
-
""
|
|
1577
|
+
(typeof colLike === "object" && (colLike.id || colLike["@id"])) ||
|
|
1578
|
+
"",
|
|
1572
1579
|
);
|
|
1573
1580
|
const effectiveId = String(uri || reportedId || "");
|
|
1574
1581
|
const collectionKey = effectiveId || reportedId || uri || "";
|
|
@@ -1589,6 +1596,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1589
1596
|
if (queuedManifestIds.has(dedupeKey)) continue;
|
|
1590
1597
|
queuedManifestIds.add(dedupeKey);
|
|
1591
1598
|
tasks.push({id: entryId, parent: collectionKey});
|
|
1599
|
+
manifestTasksFromCollections += 1;
|
|
1592
1600
|
} else if (entryType === "collection") {
|
|
1593
1601
|
await gatherFromCollection(entry.raw || entryId, collectionKey);
|
|
1594
1602
|
}
|
|
@@ -1623,34 +1631,44 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1623
1631
|
if (!dedupeKey || queuedManifestIds.has(dedupeKey)) continue;
|
|
1624
1632
|
queuedManifestIds.add(dedupeKey);
|
|
1625
1633
|
tasks.push({id: uri, parent: ""});
|
|
1634
|
+
manifestTasksFromConfig += 1;
|
|
1626
1635
|
}
|
|
1627
1636
|
}
|
|
1628
|
-
if (!tasks.length) return {
|
|
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
|
+
);
|
|
1629
1648
|
|
|
1630
1649
|
// Split into chunks and process with limited concurrency
|
|
1631
1650
|
const chunkSize = resolvePositiveInteger(
|
|
1632
1651
|
process.env.CANOPY_CHUNK_SIZE,
|
|
1633
|
-
DEFAULT_CHUNK_SIZE
|
|
1652
|
+
DEFAULT_CHUNK_SIZE,
|
|
1634
1653
|
);
|
|
1635
1654
|
const chunks = Math.ceil(tasks.length / chunkSize);
|
|
1636
1655
|
const requestedConcurrency = resolvePositiveInteger(
|
|
1637
1656
|
process.env.CANOPY_FETCH_CONCURRENCY,
|
|
1638
1657
|
DEFAULT_FETCH_CONCURRENCY,
|
|
1639
|
-
{allowZero: true}
|
|
1658
|
+
{allowZero: true},
|
|
1640
1659
|
);
|
|
1641
1660
|
// Summary before processing chunks
|
|
1642
1661
|
try {
|
|
1643
|
-
const collectionsCount = visitedCollections.size || 0;
|
|
1644
1662
|
logLine(
|
|
1645
|
-
`• Fetching ${tasks.length} Manifest(s) in ${chunks} chunk(s)
|
|
1663
|
+
`• Fetching ${tasks.length} Manifest(s) in ${chunks} chunk(s)`,
|
|
1646
1664
|
"blue",
|
|
1647
|
-
{dim: true}
|
|
1665
|
+
{dim: true},
|
|
1648
1666
|
);
|
|
1649
1667
|
const concurrencySummary =
|
|
1650
1668
|
requestedConcurrency === 0
|
|
1651
1669
|
? "auto (no explicit cap)"
|
|
1652
1670
|
: String(requestedConcurrency);
|
|
1653
|
-
logLine(`• Fetch concurrency: ${concurrencySummary}`, "blue", {
|
|
1671
|
+
logLine(`• Fetch concurrency: ${concurrencySummary}`, "blue", {dim: true});
|
|
1654
1672
|
} catch (_) {}
|
|
1655
1673
|
const iiifRecords = [];
|
|
1656
1674
|
const navPlaceRecords = [];
|
|
@@ -1660,7 +1678,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1660
1678
|
const worksLayoutPath = path.join(CONTENT_DIR, "works", "_layout.mdx");
|
|
1661
1679
|
if (!fs.existsSync(worksLayoutPath)) {
|
|
1662
1680
|
throw new Error(
|
|
1663
|
-
"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.",
|
|
1664
1682
|
);
|
|
1665
1683
|
}
|
|
1666
1684
|
let WorksLayoutComp = null;
|
|
@@ -1680,7 +1698,9 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1680
1698
|
const chunkStart = Date.now();
|
|
1681
1699
|
|
|
1682
1700
|
const concurrency =
|
|
1683
|
-
requestedConcurrency === 0
|
|
1701
|
+
requestedConcurrency === 0
|
|
1702
|
+
? Math.max(1, chunk.length)
|
|
1703
|
+
: requestedConcurrency;
|
|
1684
1704
|
let next = 0;
|
|
1685
1705
|
const logs = new Array(chunk.length);
|
|
1686
1706
|
let nextPrint = 0;
|
|
@@ -1720,7 +1740,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1720
1740
|
const saved = await saveCachedManifest(
|
|
1721
1741
|
manifest,
|
|
1722
1742
|
String(id),
|
|
1723
|
-
String(it.parent || "")
|
|
1743
|
+
String(it.parent || ""),
|
|
1724
1744
|
);
|
|
1725
1745
|
manifest = saved || manifest;
|
|
1726
1746
|
const cached = await loadCachedManifestById(String(id));
|
|
@@ -1747,7 +1767,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1747
1767
|
const saved = await saveCachedManifest(
|
|
1748
1768
|
manifest,
|
|
1749
1769
|
String(id),
|
|
1750
|
-
String(it.parent || "")
|
|
1770
|
+
String(it.parent || ""),
|
|
1751
1771
|
);
|
|
1752
1772
|
manifest = saved || manifest;
|
|
1753
1773
|
const cached = await loadCachedManifestById(String(id));
|
|
@@ -1765,11 +1785,13 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1765
1785
|
const ensured = await ensurePresentation3Manifest(manifest);
|
|
1766
1786
|
manifest = ensured.manifest;
|
|
1767
1787
|
const title = firstLabelString(manifest.label);
|
|
1768
|
-
|
|
1788
|
+
const manifestLabel = title || String(manifest.id || id);
|
|
1789
|
+
logDebug(`Preparing manifest ${manifestLabel}`);
|
|
1790
|
+
let summaryRaw = "";
|
|
1769
1791
|
try {
|
|
1770
1792
|
summaryRaw = extractSummaryValues(manifest);
|
|
1771
1793
|
} catch (_) {
|
|
1772
|
-
summaryRaw =
|
|
1794
|
+
summaryRaw = "";
|
|
1773
1795
|
}
|
|
1774
1796
|
const summaryForMeta = truncateSummary(summaryRaw || title);
|
|
1775
1797
|
const baseSlug =
|
|
@@ -1782,7 +1804,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1782
1804
|
let idxMap = await loadManifestIndex();
|
|
1783
1805
|
idxMap.byId = Array.isArray(idxMap.byId) ? idxMap.byId : [];
|
|
1784
1806
|
let mEntry = idxMap.byId.find(
|
|
1785
|
-
(e) => e && e.type === "Manifest" && normalizeIiifId(e.id) === nid
|
|
1807
|
+
(e) => e && e.type === "Manifest" && normalizeIiifId(e.id) === nid,
|
|
1786
1808
|
);
|
|
1787
1809
|
let slug = mEntry && mEntry.slug;
|
|
1788
1810
|
if (isSlugTooLong(slug)) slug = null;
|
|
@@ -1797,7 +1819,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1797
1819
|
};
|
|
1798
1820
|
applyManifestEntryCanonical(newEntry, manifest, slug);
|
|
1799
1821
|
const existingIdx = idxMap.byId.findIndex(
|
|
1800
|
-
(e) => e && e.type === "Manifest" && normalizeIiifId(e.id) === nid
|
|
1822
|
+
(e) => e && e.type === "Manifest" && normalizeIiifId(e.id) === nid,
|
|
1801
1823
|
);
|
|
1802
1824
|
if (existingIdx >= 0) idxMap.byId[existingIdx] = newEntry;
|
|
1803
1825
|
else idxMap.byId.push(newEntry);
|
|
@@ -1805,12 +1827,19 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1805
1827
|
mEntry = newEntry;
|
|
1806
1828
|
} else if (mEntry) {
|
|
1807
1829
|
const prevCanonical = mEntry.canonical || "";
|
|
1808
|
-
const nextCanonical = applyManifestEntryCanonical(
|
|
1830
|
+
const nextCanonical = applyManifestEntryCanonical(
|
|
1831
|
+
mEntry,
|
|
1832
|
+
manifest,
|
|
1833
|
+
slug,
|
|
1834
|
+
);
|
|
1809
1835
|
if (nextCanonical !== prevCanonical) {
|
|
1810
1836
|
await saveManifestIndex(idxMap);
|
|
1811
1837
|
}
|
|
1812
1838
|
}
|
|
1813
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}`);
|
|
1814
1843
|
const references = referenced.getReferencesForManifest(manifestId);
|
|
1815
1844
|
const href = path.join("works", slug + ".html");
|
|
1816
1845
|
const outPath = path.join(OUT_DIR, href);
|
|
@@ -1872,7 +1901,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1872
1901
|
canonical,
|
|
1873
1902
|
},
|
|
1874
1903
|
};
|
|
1875
|
-
const ogImageForPage =
|
|
1904
|
+
const ogImageForPage =
|
|
1905
|
+
heroMedia && heroMedia.ogImage ? heroMedia.ogImage : "";
|
|
1876
1906
|
if (ogImageForPage) {
|
|
1877
1907
|
pageDetails.image = ogImageForPage;
|
|
1878
1908
|
pageDetails.ogImage = ogImageForPage;
|
|
@@ -1880,11 +1910,19 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1880
1910
|
pageDetails.meta.ogImage = ogImageForPage;
|
|
1881
1911
|
}
|
|
1882
1912
|
const navigationRoots = navigation.buildNavigationRoots(slug || "");
|
|
1883
|
-
const navigationContext =
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
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
|
+
) {
|
|
1888
1926
|
try {
|
|
1889
1927
|
Object.defineProperty(manifest, "__canopyMetadataFacets", {
|
|
1890
1928
|
configurable: true,
|
|
@@ -1910,15 +1948,15 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1910
1948
|
PageContext && pageContextValue
|
|
1911
1949
|
? React.createElement(
|
|
1912
1950
|
PageContext.Provider,
|
|
1913
|
-
{
|
|
1914
|
-
wrappedApp
|
|
1951
|
+
{value: pageContextValue},
|
|
1952
|
+
wrappedApp,
|
|
1915
1953
|
)
|
|
1916
1954
|
: wrappedApp;
|
|
1917
1955
|
const page = MDXProvider
|
|
1918
1956
|
? React.createElement(
|
|
1919
1957
|
MDXProvider,
|
|
1920
1958
|
{components: compMap},
|
|
1921
|
-
withContext
|
|
1959
|
+
withContext,
|
|
1922
1960
|
)
|
|
1923
1961
|
: withContext;
|
|
1924
1962
|
const body = ReactDOMServer.renderToStaticMarkup(page);
|
|
@@ -1931,8 +1969,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1931
1969
|
const wrappedHead = PageContext
|
|
1932
1970
|
? React.createElement(
|
|
1933
1971
|
PageContext.Provider,
|
|
1934
|
-
{
|
|
1935
|
-
headElement
|
|
1972
|
+
{value: pageContextValue},
|
|
1973
|
+
headElement,
|
|
1936
1974
|
)
|
|
1937
1975
|
: headElement;
|
|
1938
1976
|
head = ReactDOMServer.renderToStaticMarkup(wrappedHead);
|
|
@@ -1956,7 +1994,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1956
1994
|
? path
|
|
1957
1995
|
.relative(
|
|
1958
1996
|
path.dirname(outPath),
|
|
1959
|
-
path.join(OUT_DIR, "scripts", "canopy-viewer.js")
|
|
1997
|
+
path.join(OUT_DIR, "scripts", "canopy-viewer.js"),
|
|
1960
1998
|
)
|
|
1961
1999
|
.split(path.sep)
|
|
1962
2000
|
.join("/")
|
|
@@ -1965,7 +2003,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1965
2003
|
? path
|
|
1966
2004
|
.relative(
|
|
1967
2005
|
path.dirname(outPath),
|
|
1968
|
-
path.join(OUT_DIR, "scripts", "canopy-slider.js")
|
|
2006
|
+
path.join(OUT_DIR, "scripts", "canopy-slider.js"),
|
|
1969
2007
|
)
|
|
1970
2008
|
.split(path.sep)
|
|
1971
2009
|
.join("/")
|
|
@@ -1974,7 +2012,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1974
2012
|
? path
|
|
1975
2013
|
.relative(
|
|
1976
2014
|
path.dirname(outPath),
|
|
1977
|
-
path.join(OUT_DIR, "scripts", "canopy-timeline.js")
|
|
2015
|
+
path.join(OUT_DIR, "scripts", "canopy-timeline.js"),
|
|
1978
2016
|
)
|
|
1979
2017
|
.split(path.sep)
|
|
1980
2018
|
.join("/")
|
|
@@ -1983,7 +2021,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1983
2021
|
? path
|
|
1984
2022
|
.relative(
|
|
1985
2023
|
path.dirname(outPath),
|
|
1986
|
-
path.join(OUT_DIR, "scripts", "canopy-map.js")
|
|
2024
|
+
path.join(OUT_DIR, "scripts", "canopy-map.js"),
|
|
1987
2025
|
)
|
|
1988
2026
|
.split(path.sep)
|
|
1989
2027
|
.join("/")
|
|
@@ -1992,7 +2030,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1992
2030
|
? path
|
|
1993
2031
|
.relative(
|
|
1994
2032
|
path.dirname(outPath),
|
|
1995
|
-
path.join(OUT_DIR, "scripts", "canopy-map.css")
|
|
2033
|
+
path.join(OUT_DIR, "scripts", "canopy-map.css"),
|
|
1996
2034
|
)
|
|
1997
2035
|
.split(path.sep)
|
|
1998
2036
|
.join("/")
|
|
@@ -2001,7 +2039,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2001
2039
|
? path
|
|
2002
2040
|
.relative(
|
|
2003
2041
|
path.dirname(outPath),
|
|
2004
|
-
path.join(OUT_DIR, "scripts", "canopy-hero-slider.js")
|
|
2042
|
+
path.join(OUT_DIR, "scripts", "canopy-hero-slider.js"),
|
|
2005
2043
|
)
|
|
2006
2044
|
.split(path.sep)
|
|
2007
2045
|
.join("/")
|
|
@@ -2010,7 +2048,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2010
2048
|
? path
|
|
2011
2049
|
.relative(
|
|
2012
2050
|
path.dirname(outPath),
|
|
2013
|
-
path.join(OUT_DIR, "scripts", "canopy-related-items.js")
|
|
2051
|
+
path.join(OUT_DIR, "scripts", "canopy-related-items.js"),
|
|
2014
2052
|
)
|
|
2015
2053
|
.split(path.sep)
|
|
2016
2054
|
.join("/")
|
|
@@ -2019,7 +2057,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2019
2057
|
? path
|
|
2020
2058
|
.relative(
|
|
2021
2059
|
path.dirname(outPath),
|
|
2022
|
-
path.join(OUT_DIR, "scripts", "canopy-search-form.js")
|
|
2060
|
+
path.join(OUT_DIR, "scripts", "canopy-search-form.js"),
|
|
2023
2061
|
)
|
|
2024
2062
|
.split(path.sep)
|
|
2025
2063
|
.join("/")
|
|
@@ -2040,7 +2078,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2040
2078
|
jsRel = primaryClassicScripts.shift();
|
|
2041
2079
|
}
|
|
2042
2080
|
const classicScriptRels = primaryClassicScripts.concat(
|
|
2043
|
-
secondaryClassicScripts
|
|
2081
|
+
secondaryClassicScripts,
|
|
2044
2082
|
);
|
|
2045
2083
|
|
|
2046
2084
|
const headSegments = [head];
|
|
@@ -2056,7 +2094,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2056
2094
|
const vendorAbs = path.join(
|
|
2057
2095
|
OUT_DIR,
|
|
2058
2096
|
"scripts",
|
|
2059
|
-
"react-globals.js"
|
|
2097
|
+
"react-globals.js",
|
|
2060
2098
|
);
|
|
2061
2099
|
let vendorRel = path
|
|
2062
2100
|
.relative(path.dirname(outPath), vendorAbs)
|
|
@@ -2085,7 +2123,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2085
2123
|
if (BASE_PATH)
|
|
2086
2124
|
vendorTag =
|
|
2087
2125
|
`<script>window.CANOPY_BASE_PATH=${JSON.stringify(
|
|
2088
|
-
BASE_PATH
|
|
2126
|
+
BASE_PATH,
|
|
2089
2127
|
)}</script>` + vendorTag;
|
|
2090
2128
|
} catch (_) {}
|
|
2091
2129
|
let pageBody = body;
|
|
@@ -2117,6 +2155,9 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2117
2155
|
html = require("../common").applyBaseToHtml(html);
|
|
2118
2156
|
} catch (_) {}
|
|
2119
2157
|
await fsp.writeFile(outPath, html, "utf8");
|
|
2158
|
+
logDebug(
|
|
2159
|
+
`Wrote work page → ${path.relative(process.cwd(), outPath)}`,
|
|
2160
|
+
);
|
|
2120
2161
|
lns.push([
|
|
2121
2162
|
`✔ Created ${path.relative(process.cwd(), outPath)}`,
|
|
2122
2163
|
"green",
|
|
@@ -2130,6 +2171,9 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2130
2171
|
thumbUrl = String(t.url);
|
|
2131
2172
|
thumbWidth = typeof t.width === "number" ? t.width : undefined;
|
|
2132
2173
|
thumbHeight = typeof t.height === "number" ? t.height : undefined;
|
|
2174
|
+
logDebug(
|
|
2175
|
+
`Thumbnail resolved for ${manifestLabel}: ${thumbUrl} (${thumbWidth || "auto"}×${thumbHeight || "auto"})`,
|
|
2176
|
+
);
|
|
2133
2177
|
}
|
|
2134
2178
|
} catch (_) {}
|
|
2135
2179
|
try {
|
|
@@ -2139,7 +2183,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2139
2183
|
(e) =>
|
|
2140
2184
|
e &&
|
|
2141
2185
|
e.id === String(manifest.id || id) &&
|
|
2142
|
-
e.type === "Manifest"
|
|
2186
|
+
e.type === "Manifest",
|
|
2143
2187
|
);
|
|
2144
2188
|
if (entry) {
|
|
2145
2189
|
let touched = false;
|
|
@@ -2165,6 +2209,9 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2165
2209
|
}
|
|
2166
2210
|
}
|
|
2167
2211
|
if (heroMedia && heroMedia.heroThumbnail) {
|
|
2212
|
+
logDebug(
|
|
2213
|
+
`Hero thumbnail cached for ${manifestLabel}: ${heroMedia.heroThumbnail}`,
|
|
2214
|
+
);
|
|
2168
2215
|
if (entry.heroThumbnail !== heroMedia.heroThumbnail) {
|
|
2169
2216
|
entry.heroThumbnail = heroMedia.heroThumbnail;
|
|
2170
2217
|
touched = true;
|
|
@@ -2185,7 +2232,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2185
2232
|
}
|
|
2186
2233
|
if (heroMedia.heroThumbnailSrcset) {
|
|
2187
2234
|
if (
|
|
2188
|
-
entry.heroThumbnailSrcset !==
|
|
2235
|
+
entry.heroThumbnailSrcset !==
|
|
2236
|
+
heroMedia.heroThumbnailSrcset
|
|
2189
2237
|
) {
|
|
2190
2238
|
entry.heroThumbnailSrcset = heroMedia.heroThumbnailSrcset;
|
|
2191
2239
|
touched = true;
|
|
@@ -2194,6 +2242,9 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2194
2242
|
entry.heroThumbnailSizes = HERO_IMAGE_SIZES_ATTR;
|
|
2195
2243
|
touched = true;
|
|
2196
2244
|
}
|
|
2245
|
+
logDebug(
|
|
2246
|
+
`Hero srcset cached for ${manifestLabel} (${heroMedia.heroThumbnailSrcset.length} chars)`,
|
|
2247
|
+
);
|
|
2197
2248
|
}
|
|
2198
2249
|
} else {
|
|
2199
2250
|
if (entry.heroThumbnail !== undefined) {
|
|
@@ -2218,19 +2269,24 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2218
2269
|
}
|
|
2219
2270
|
}
|
|
2220
2271
|
if (heroMedia && heroMedia.ogImage) {
|
|
2272
|
+
logDebug(
|
|
2273
|
+
`OG image cached for ${manifestLabel}: ${heroMedia.ogImage}`,
|
|
2274
|
+
);
|
|
2221
2275
|
if (entry.ogImage !== heroMedia.ogImage) {
|
|
2222
2276
|
entry.ogImage = heroMedia.ogImage;
|
|
2223
2277
|
touched = true;
|
|
2224
2278
|
}
|
|
2225
|
-
if (typeof heroMedia.ogImageWidth ===
|
|
2226
|
-
if (entry.ogImageWidth !== heroMedia.ogImageWidth)
|
|
2279
|
+
if (typeof heroMedia.ogImageWidth === "number") {
|
|
2280
|
+
if (entry.ogImageWidth !== heroMedia.ogImageWidth)
|
|
2281
|
+
touched = true;
|
|
2227
2282
|
entry.ogImageWidth = heroMedia.ogImageWidth;
|
|
2228
2283
|
} else if (entry.ogImageWidth !== undefined) {
|
|
2229
2284
|
delete entry.ogImageWidth;
|
|
2230
2285
|
touched = true;
|
|
2231
2286
|
}
|
|
2232
|
-
if (typeof heroMedia.ogImageHeight ===
|
|
2233
|
-
if (entry.ogImageHeight !== heroMedia.ogImageHeight)
|
|
2287
|
+
if (typeof heroMedia.ogImageHeight === "number") {
|
|
2288
|
+
if (entry.ogImageHeight !== heroMedia.ogImageHeight)
|
|
2289
|
+
touched = true;
|
|
2234
2290
|
entry.ogImageHeight = heroMedia.ogImageHeight;
|
|
2235
2291
|
} else if (entry.ogImageHeight !== undefined) {
|
|
2236
2292
|
delete entry.ogImageHeight;
|
|
@@ -2257,7 +2313,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2257
2313
|
entry,
|
|
2258
2314
|
heroMedia && heroMedia.heroThumbnail,
|
|
2259
2315
|
heroMedia && heroMedia.heroThumbnailWidth,
|
|
2260
|
-
heroMedia && heroMedia.heroThumbnailHeight
|
|
2316
|
+
heroMedia && heroMedia.heroThumbnailHeight,
|
|
2261
2317
|
)
|
|
2262
2318
|
) {
|
|
2263
2319
|
touched = true;
|
|
@@ -2283,7 +2339,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2283
2339
|
try {
|
|
2284
2340
|
annotationValue = extractAnnotationText(
|
|
2285
2341
|
manifest,
|
|
2286
|
-
annotationsOptions
|
|
2342
|
+
annotationsOptions,
|
|
2287
2343
|
);
|
|
2288
2344
|
} catch (_) {
|
|
2289
2345
|
annotationValue = "";
|
|
@@ -2326,9 +2382,13 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2326
2382
|
type: "work",
|
|
2327
2383
|
thumbnail: recordThumbnail || undefined,
|
|
2328
2384
|
thumbnailWidth:
|
|
2329
|
-
typeof recordThumbWidth === "number"
|
|
2385
|
+
typeof recordThumbWidth === "number"
|
|
2386
|
+
? recordThumbWidth
|
|
2387
|
+
: undefined,
|
|
2330
2388
|
thumbnailHeight:
|
|
2331
|
-
typeof recordThumbHeight === "number"
|
|
2389
|
+
typeof recordThumbHeight === "number"
|
|
2390
|
+
? recordThumbHeight
|
|
2391
|
+
: undefined,
|
|
2332
2392
|
searchMetadataValues:
|
|
2333
2393
|
metadataValues && metadataValues.length
|
|
2334
2394
|
? metadataValues
|
|
@@ -2340,6 +2400,11 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2340
2400
|
? annotationValue
|
|
2341
2401
|
: undefined,
|
|
2342
2402
|
});
|
|
2403
|
+
logDebug(
|
|
2404
|
+
`Search record queued for ${manifestLabel}: ${pageHref} (metadata values ${
|
|
2405
|
+
metadataValues ? metadataValues.length : 0
|
|
2406
|
+
})`,
|
|
2407
|
+
);
|
|
2343
2408
|
} catch (e) {
|
|
2344
2409
|
lns.push([
|
|
2345
2410
|
`IIIF: failed to render for ${id || "<unknown>"} — ${e.message}`,
|
|
@@ -2352,7 +2417,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2352
2417
|
}
|
|
2353
2418
|
const workers = Array.from(
|
|
2354
2419
|
{length: Math.min(concurrency, chunk.length)},
|
|
2355
|
-
() => worker()
|
|
2420
|
+
() => worker(),
|
|
2356
2421
|
);
|
|
2357
2422
|
await Promise.all(workers);
|
|
2358
2423
|
tryFlush();
|
|
@@ -2361,48 +2426,58 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
2361
2426
|
index: ci + 1,
|
|
2362
2427
|
count: chunk.length,
|
|
2363
2428
|
durationMs: chunkDuration,
|
|
2364
|
-
concurrency,
|
|
2365
2429
|
});
|
|
2366
2430
|
try {
|
|
2367
|
-
const concurrencyLabel =
|
|
2368
|
-
requestedConcurrency === 0 ? `${concurrency} (auto)` : String(concurrency);
|
|
2369
2431
|
logLine(
|
|
2370
|
-
`⏱ 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)}`,
|
|
2371
2433
|
"cyan",
|
|
2372
|
-
{
|
|
2434
|
+
{dim: true},
|
|
2373
2435
|
);
|
|
2374
2436
|
} catch (_) {}
|
|
2375
2437
|
}
|
|
2376
2438
|
if (chunkMetrics.length) {
|
|
2377
2439
|
const totalDuration = chunkMetrics.reduce(
|
|
2378
2440
|
(sum, entry) => sum + (entry.durationMs || 0),
|
|
2379
|
-
0
|
|
2441
|
+
0,
|
|
2442
|
+
);
|
|
2443
|
+
const totalItems = chunkMetrics.reduce(
|
|
2444
|
+
(sum, entry) => sum + (entry.count || 0),
|
|
2445
|
+
0,
|
|
2380
2446
|
);
|
|
2381
|
-
const totalItems = chunkMetrics.reduce((sum, entry) => sum + (entry.count || 0), 0);
|
|
2382
2447
|
const avgDuration = chunkMetrics.length
|
|
2383
2448
|
? totalDuration / chunkMetrics.length
|
|
2384
2449
|
: 0;
|
|
2385
2450
|
const rate = totalDuration > 0 ? totalItems / (totalDuration / 1000) : 0;
|
|
2386
2451
|
try {
|
|
2387
|
-
const rateLabel = rate ? `${rate.toFixed(1)} manifest(s)/s` :
|
|
2452
|
+
const rateLabel = rate ? `${rate.toFixed(1)} manifest(s)/s` : "n/a";
|
|
2388
2453
|
logLine(
|
|
2389
2454
|
`IIIF chunk summary: ${totalItems} Manifest(s) in ${formatDurationMs(totalDuration)} (avg chunk ${formatDurationMs(avgDuration)}, ${rateLabel})`,
|
|
2390
2455
|
"cyan",
|
|
2391
|
-
{
|
|
2456
|
+
{dim: true},
|
|
2392
2457
|
);
|
|
2393
2458
|
} catch (_) {}
|
|
2394
2459
|
}
|
|
2395
2460
|
try {
|
|
2396
2461
|
await navPlace.writeNavPlaceDataset(navPlaceRecords);
|
|
2462
|
+
try {
|
|
2463
|
+
logLine(
|
|
2464
|
+
`✓ Wrote navPlace dataset (${navPlaceRecords.length} record(s))`,
|
|
2465
|
+
"cyan",
|
|
2466
|
+
);
|
|
2467
|
+
} catch (_) {}
|
|
2397
2468
|
} catch (error) {
|
|
2398
2469
|
try {
|
|
2399
2470
|
console.warn(
|
|
2400
|
-
|
|
2401
|
-
error && error.message ? error.message : error
|
|
2471
|
+
"[canopy][navPlace] failed to write dataset:",
|
|
2472
|
+
error && error.message ? error.message : error,
|
|
2402
2473
|
);
|
|
2403
2474
|
} catch (_) {}
|
|
2404
2475
|
}
|
|
2405
|
-
return {
|
|
2476
|
+
return {
|
|
2477
|
+
iiifRecords,
|
|
2478
|
+
manifestIds: Array.from(renderedManifestIds),
|
|
2479
|
+
collectionIds: Array.from(visitedCollections),
|
|
2480
|
+
};
|
|
2406
2481
|
}
|
|
2407
2482
|
|
|
2408
2483
|
module.exports = {
|
|
@@ -2410,11 +2485,12 @@ module.exports = {
|
|
|
2410
2485
|
loadConfig,
|
|
2411
2486
|
loadManifestIndex,
|
|
2412
2487
|
saveManifestIndex,
|
|
2488
|
+
resolveIiifSources,
|
|
2413
2489
|
// Expose helpers used by build for cache warming
|
|
2414
2490
|
loadCachedManifestById,
|
|
2415
2491
|
saveCachedManifest,
|
|
2416
2492
|
ensureFeaturedInCache,
|
|
2417
|
-
|
|
2493
|
+
cleanupIiifCache,
|
|
2418
2494
|
};
|
|
2419
2495
|
|
|
2420
2496
|
// Expose a stable set of pure helper utilities for unit testing.
|
|
@@ -2465,7 +2541,7 @@ try {
|
|
|
2465
2541
|
`IIIF: cache/collections (end): ${files.length} file(s)` +
|
|
2466
2542
|
(head ? ` [${head}${files.length > 8 ? ", …" : ""}]` : ""),
|
|
2467
2543
|
"blue",
|
|
2468
|
-
{dim: true}
|
|
2544
|
+
{dim: true},
|
|
2469
2545
|
);
|
|
2470
2546
|
} catch (_) {}
|
|
2471
2547
|
}
|