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