@canopy-iiif/app 1.5.4 → 1.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/build/iiif.js CHANGED
@@ -28,6 +28,7 @@ const {
28
28
  buildIiifImageUrlForDimensions,
29
29
  findPrimaryCanvasImage,
30
30
  buildIiifImageSrcset,
31
+ isLevel0Service,
31
32
  } = require("../iiif/thumbnail");
32
33
 
33
34
  const IIIF_CACHE_DIR = path.resolve(".cache/iiif");
@@ -167,9 +168,29 @@ function ensureThumbnailValue(target, url, width, height) {
167
168
  return true;
168
169
  }
169
170
 
171
+ function extractResourceThumbnail(resource) {
172
+ try {
173
+ const rawThumb = resource && resource.thumbnail;
174
+ const first = Array.isArray(rawThumb) ? rawThumb[0] : rawThumb;
175
+ if (!first) return null;
176
+ if (typeof first === "string") {
177
+ const trimmed = first.trim();
178
+ return trimmed ? {url: trimmed} : null;
179
+ }
180
+ const id = first.id || first["@id"];
181
+ if (!id) return null;
182
+ const width = typeof first.width === "number" ? first.width : undefined;
183
+ const height = typeof first.height === "number" ? first.height : undefined;
184
+ return {url: String(id), width, height};
185
+ } catch (_) {
186
+ return null;
187
+ }
188
+ }
189
+
170
190
  async function resolveHeroMedia(manifest) {
171
191
  if (!manifest) return null;
172
192
  try {
193
+ const manifestThumb = extractResourceThumbnail(manifest);
173
194
  const heroSource = (() => {
174
195
  if (manifest && manifest.thumbnail) {
175
196
  const clone = { ...manifest };
@@ -191,15 +212,11 @@ async function resolveHeroMedia(manifest) {
191
212
  const heroService =
192
213
  (canvasImage && canvasImage.service) ||
193
214
  (heroRep && heroRep.service);
215
+ const serviceIsLevel0 = isLevel0Service(heroService);
194
216
  const heroPreferred = buildIiifImageUrlFromService(
195
- heroService,
217
+ serviceIsLevel0 ? null : heroService,
196
218
  HERO_THUMBNAIL_SIZE
197
219
  );
198
- const heroFallbackId = (() => {
199
- if (canvasImage && canvasImage.id) return String(canvasImage.id);
200
- if (heroRep && heroRep.id) return String(heroRep.id);
201
- return '';
202
- })();
203
220
  const heroWidth = (() => {
204
221
  if (canvasImage && typeof canvasImage.width === 'number')
205
222
  return canvasImage.width;
@@ -213,21 +230,59 @@ async function resolveHeroMedia(manifest) {
213
230
  return heroRep.height;
214
231
  return undefined;
215
232
  })();
216
- const heroSrcset = buildIiifImageSrcset(heroService);
217
- const ogImage = heroService
218
- ? buildIiifImageUrlForDimensions(
219
- heroService,
220
- OG_IMAGE_WIDTH,
221
- OG_IMAGE_HEIGHT
222
- )
223
- : '';
233
+ const heroSrcset = serviceIsLevel0
234
+ ? ''
235
+ : buildIiifImageSrcset(heroService);
236
+ const ogFromService =
237
+ !serviceIsLevel0 && heroService
238
+ ? buildIiifImageUrlForDimensions(
239
+ heroService,
240
+ OG_IMAGE_WIDTH,
241
+ OG_IMAGE_HEIGHT
242
+ )
243
+ : '';
244
+ const annotationImageId =
245
+ canvasImage && canvasImage.isImageBody && canvasImage.id
246
+ ? String(canvasImage.id)
247
+ : '';
248
+ let heroThumbnail = heroPreferred || '';
249
+ let heroThumbWidth = heroWidth;
250
+ let heroThumbHeight = heroHeight;
251
+ if (!heroThumbnail && manifestThumb && manifestThumb.url) {
252
+ heroThumbnail = manifestThumb.url;
253
+ if (typeof manifestThumb.width === 'number')
254
+ heroThumbWidth = manifestThumb.width;
255
+ if (typeof manifestThumb.height === 'number')
256
+ heroThumbHeight = manifestThumb.height;
257
+ }
258
+ if (!heroThumbnail) {
259
+ if (annotationImageId) {
260
+ heroThumbnail = annotationImageId;
261
+ } else if (!serviceIsLevel0 && heroRep && heroRep.id) {
262
+ heroThumbnail = String(heroRep.id);
263
+ }
264
+ }
265
+ let ogImage = '';
266
+ let ogImageWidth;
267
+ let ogImageHeight;
268
+ if (ogFromService) {
269
+ ogImage = ogFromService;
270
+ ogImageWidth = OG_IMAGE_WIDTH;
271
+ ogImageHeight = OG_IMAGE_HEIGHT;
272
+ } else if (heroThumbnail) {
273
+ ogImage = heroThumbnail;
274
+ if (typeof heroThumbWidth === 'number') ogImageWidth = heroThumbWidth;
275
+ if (typeof heroThumbHeight === 'number') ogImageHeight = heroThumbHeight;
276
+ }
224
277
  return {
225
- heroThumbnail: heroPreferred || heroFallbackId || '',
226
- heroThumbnailWidth: heroWidth,
227
- heroThumbnailHeight: heroHeight,
278
+ heroThumbnail: heroThumbnail || '',
279
+ heroThumbnailWidth: heroThumbWidth,
280
+ heroThumbnailHeight: heroThumbHeight,
228
281
  heroThumbnailSrcset: heroSrcset || '',
229
282
  heroThumbnailSizes: heroSrcset ? HERO_IMAGE_SIZES_ATTR : '',
230
283
  ogImage: ogImage || '',
284
+ ogImageWidth,
285
+ ogImageHeight,
231
286
  };
232
287
  } catch (_) {
233
288
  return null;
@@ -987,14 +1042,28 @@ async function ensureFeaturedInCache(cfg) {
987
1042
  }
988
1043
  }
989
1044
  if (heroMedia && heroMedia.ogImage) {
990
- if (entry.ogImage !== heroMedia.ogImage) touched = true;
991
- entry.ogImage = heroMedia.ogImage;
992
- entry.ogImageWidth = OG_IMAGE_WIDTH;
993
- entry.ogImageHeight = OG_IMAGE_HEIGHT;
1045
+ if (entry.ogImage !== heroMedia.ogImage) {
1046
+ entry.ogImage = heroMedia.ogImage;
1047
+ touched = true;
1048
+ }
1049
+ if (typeof heroMedia.ogImageWidth === 'number') {
1050
+ if (entry.ogImageWidth !== heroMedia.ogImageWidth) touched = true;
1051
+ entry.ogImageWidth = heroMedia.ogImageWidth;
1052
+ } else if (entry.ogImageWidth !== undefined) {
1053
+ delete entry.ogImageWidth;
1054
+ touched = true;
1055
+ }
1056
+ if (typeof heroMedia.ogImageHeight === 'number') {
1057
+ if (entry.ogImageHeight !== heroMedia.ogImageHeight) touched = true;
1058
+ entry.ogImageHeight = heroMedia.ogImageHeight;
1059
+ } else if (entry.ogImageHeight !== undefined) {
1060
+ delete entry.ogImageHeight;
1061
+ touched = true;
1062
+ }
994
1063
  } else if (entry.ogImage !== undefined) {
995
1064
  delete entry.ogImage;
996
- delete entry.ogImageWidth;
997
- delete entry.ogImageHeight;
1065
+ if (entry.ogImageWidth !== undefined) delete entry.ogImageWidth;
1066
+ if (entry.ogImageHeight !== undefined) delete entry.ogImageHeight;
998
1067
  touched = true;
999
1068
  }
1000
1069
  if (
@@ -1236,8 +1305,12 @@ async function rebuildManifestIndexFromCache() {
1236
1305
  }
1237
1306
  if (heroMedia.ogImage) {
1238
1307
  entry.ogImage = heroMedia.ogImage;
1239
- entry.ogImageWidth = OG_IMAGE_WIDTH;
1240
- entry.ogImageHeight = OG_IMAGE_HEIGHT;
1308
+ if (typeof heroMedia.ogImageWidth === 'number')
1309
+ entry.ogImageWidth = heroMedia.ogImageWidth;
1310
+ else delete entry.ogImageWidth;
1311
+ if (typeof heroMedia.ogImageHeight === 'number')
1312
+ entry.ogImageHeight = heroMedia.ogImageHeight;
1313
+ else delete entry.ogImageHeight;
1241
1314
  }
1242
1315
  ensureThumbnailValue(
1243
1316
  entry,
@@ -2006,12 +2079,18 @@ async function buildIiifCollectionPages(CONFIG) {
2006
2079
  entry.ogImage = heroMedia.ogImage;
2007
2080
  touched = true;
2008
2081
  }
2009
- if (entry.ogImageWidth !== OG_IMAGE_WIDTH) {
2010
- entry.ogImageWidth = OG_IMAGE_WIDTH;
2082
+ if (typeof heroMedia.ogImageWidth === 'number') {
2083
+ if (entry.ogImageWidth !== heroMedia.ogImageWidth) touched = true;
2084
+ entry.ogImageWidth = heroMedia.ogImageWidth;
2085
+ } else if (entry.ogImageWidth !== undefined) {
2086
+ delete entry.ogImageWidth;
2011
2087
  touched = true;
2012
2088
  }
2013
- if (entry.ogImageHeight !== OG_IMAGE_HEIGHT) {
2014
- entry.ogImageHeight = OG_IMAGE_HEIGHT;
2089
+ if (typeof heroMedia.ogImageHeight === 'number') {
2090
+ if (entry.ogImageHeight !== heroMedia.ogImageHeight) touched = true;
2091
+ entry.ogImageHeight = heroMedia.ogImageHeight;
2092
+ } else if (entry.ogImageHeight !== undefined) {
2093
+ delete entry.ogImageHeight;
2015
2094
  touched = true;
2016
2095
  }
2017
2096
  } else {
@@ -12,6 +12,30 @@ function arrayify(value) {
12
12
  return Array.isArray(value) ? value : [value];
13
13
  }
14
14
 
15
+ function normalizeBodyType(value) {
16
+ if (!value) return '';
17
+ if (typeof value === 'string') {
18
+ const trimmed = value.trim();
19
+ return trimmed;
20
+ }
21
+ if (Array.isArray(value)) {
22
+ for (const entry of value) {
23
+ const normalized = normalizeBodyType(entry);
24
+ if (normalized) return normalized;
25
+ }
26
+ }
27
+ return '';
28
+ }
29
+
30
+ function isImageBodyType(value) {
31
+ if (!value) return false;
32
+ try {
33
+ return /image/i.test(String(value));
34
+ } catch (_) {
35
+ return false;
36
+ }
37
+ }
38
+
15
39
  function normalizeImageServiceCandidate(candidate) {
16
40
  if (!candidate || typeof candidate !== 'object') return null;
17
41
  const id = candidate.id || candidate['@id'];
@@ -41,6 +65,23 @@ function normalizeImageServiceCandidate(candidate) {
41
65
  };
42
66
  }
43
67
 
68
+ function isLevel0Profile(profile) {
69
+ if (!profile) return false;
70
+ try {
71
+ return /level0/i.test(String(profile));
72
+ } catch (_) {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ function isLevel0Service(candidate) {
78
+ if (!candidate || typeof candidate !== 'object') return false;
79
+ if (candidate.profile && isLevel0Profile(candidate.profile)) return true;
80
+ const normalized = normalizeImageServiceCandidate(candidate);
81
+ if (!normalized) return false;
82
+ return Boolean(normalized.profile && isLevel0Profile(normalized.profile));
83
+ }
84
+
44
85
  function isIiifImageService(candidate) {
45
86
  if (!candidate) return false;
46
87
  const {type, profile} = candidate;
@@ -77,6 +118,7 @@ function extractImageService(value, seen = new Set()) {
77
118
  function normalizeImagePayload(body, canvas) {
78
119
  if (!body || typeof body !== 'object') return null;
79
120
  const id = body.id || body['@id'];
121
+ const bodyType = normalizeBodyType(body.type || body['@type']);
80
122
  const width =
81
123
  typeof body.width === 'number'
82
124
  ? body.width
@@ -95,6 +137,8 @@ function normalizeImagePayload(body, canvas) {
95
137
  width,
96
138
  height,
97
139
  service: service || undefined,
140
+ bodyType: bodyType || undefined,
141
+ isImageBody: Boolean(bodyType && isImageBodyType(bodyType)),
98
142
  };
99
143
  }
100
144
 
@@ -185,6 +229,7 @@ function selectServiceQuality(candidate) {
185
229
 
186
230
  function buildIiifImageUrlFromNormalizedService(service, preferredSize = 800) {
187
231
  if (!service || !isIiifImageService(service)) return '';
232
+ if (isLevel0Profile(service.profile)) return '';
188
233
  const baseId = normalizeServiceBaseId(service.id);
189
234
  if (!baseId) return '';
190
235
  const size = preferredSize && preferredSize > 0 ? preferredSize : 800;
@@ -202,6 +247,7 @@ function buildIiifImageUrlFromService(service, preferredSize = 800) {
202
247
  function buildIiifImageUrlForDimensions(service, width = 1200, height = 630) {
203
248
  const normalized = normalizeImageServiceCandidate(service);
204
249
  if (!normalized || !isIiifImageService(normalized)) return '';
250
+ if (isLevel0Profile(normalized.profile)) return '';
205
251
  const baseId = normalizeServiceBaseId(normalized.id);
206
252
  if (!baseId) return '';
207
253
  const safeWidth = Math.max(1, Math.floor(Number(width) || 0));
@@ -214,6 +260,7 @@ function buildIiifImageUrlForDimensions(service, width = 1200, height = 630) {
214
260
  function buildIiifImageSrcset(service, steps = [360, 640, 960, 1280, 1600]) {
215
261
  const normalized = normalizeImageServiceCandidate(service);
216
262
  if (!normalized || !isIiifImageService(normalized)) return '';
263
+ if (isLevel0Profile(normalized.profile)) return '';
217
264
  const uniqueSteps = Array.from(
218
265
  new Set(
219
266
  (Array.isArray(steps) ? steps : [])
@@ -354,4 +401,5 @@ module.exports = {
354
401
  buildIiifImageUrlForDimensions,
355
402
  findPrimaryCanvasImage,
356
403
  buildIiifImageSrcset,
404
+ isLevel0Service,
357
405
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-iiif/app",
3
- "version": "1.5.4",
3
+ "version": "1.5.5",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",