@canopy-iiif/app 1.2.8 → 1.3.0

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
@@ -440,6 +440,35 @@ async function normalizeToV3(resource) {
440
440
  return resource;
441
441
  }
442
442
 
443
+ let upgradeModulePromise = null;
444
+ async function loadUpgradeModule() {
445
+ if (!upgradeModulePromise) {
446
+ upgradeModulePromise = import("@iiif/parser/upgrader").catch(() => null);
447
+ }
448
+ return upgradeModulePromise;
449
+ }
450
+
451
+ async function upgradeIiifResource(resource) {
452
+ if (!resource) return resource;
453
+ try {
454
+ const mod = await loadUpgradeModule();
455
+ const upgrader = mod && (mod.upgrade || mod.default);
456
+ if (typeof upgrader === "function") {
457
+ let upgraded = upgrader(resource);
458
+ if (upgraded && typeof upgraded.then === "function") {
459
+ upgraded = await upgraded;
460
+ }
461
+ if (upgraded) return upgraded;
462
+ }
463
+ } catch (_) {}
464
+ return normalizeToV3(resource);
465
+ }
466
+
467
+ async function ensurePresentation3Manifest(manifest) {
468
+ const upgraded = await upgradeIiifResource(manifest);
469
+ return {manifest: upgraded, changed: upgraded !== manifest};
470
+ }
471
+
443
472
  async function readJson(p) {
444
473
  const raw = await fsp.readFile(p, "utf8");
445
474
  return JSON.parse(raw);
@@ -461,6 +490,62 @@ function normalizeIiifId(raw) {
461
490
  }
462
491
  }
463
492
 
493
+ function normalizeIiifType(value) {
494
+ try {
495
+ const raw = String(value || "").trim();
496
+ if (!raw) return "";
497
+ const lower = raw.toLowerCase();
498
+ const idx = lower.lastIndexOf(":");
499
+ if (idx >= 0 && idx < lower.length - 1) return lower.slice(idx + 1);
500
+ return lower;
501
+ } catch (_) {
502
+ return "";
503
+ }
504
+ }
505
+
506
+ function extractCollectionEntries(collection) {
507
+ if (!collection || typeof collection !== "object") return [];
508
+ const entries = [];
509
+ const seen = new Set();
510
+ const pushEntry = (raw, fallbackType) => {
511
+ if (!raw) return;
512
+ let id = "";
513
+ let type = "";
514
+ if (typeof raw === "string") {
515
+ id = raw;
516
+ type = fallbackType || "";
517
+ } else if (typeof raw === "object") {
518
+ id = raw.id || raw["@id"] || fallbackType || "";
519
+ type = raw.type || raw["@type"] || fallbackType || "";
520
+ }
521
+ const normalizedId = String(id || "").trim();
522
+ if (!normalizedId) return;
523
+ const normalizedType = normalizeIiifType(type || fallbackType || "");
524
+ const fallback = normalizeIiifType(fallbackType || "");
525
+ const key = `${normalizedType}::${normalizedId}`;
526
+ if (seen.has(key)) return;
527
+ seen.add(key);
528
+ entries.push({
529
+ id: normalizedId,
530
+ type: normalizedType,
531
+ fallback,
532
+ raw,
533
+ });
534
+ };
535
+
536
+ const sources = [
537
+ {list: collection.items, fallback: ""},
538
+ {list: collection.manifests, fallback: "manifest"},
539
+ {list: collection.collections, fallback: "collection"},
540
+ {list: collection.members, fallback: ""},
541
+ ];
542
+ for (const source of sources) {
543
+ const arr = Array.isArray(source.list) ? source.list : [];
544
+ for (const entry of arr) pushEntry(entry, source.fallback);
545
+ }
546
+ return entries;
547
+ }
548
+
464
549
  async function readJsonFromUri(uri, options = {log: false}) {
465
550
  try {
466
551
  if (/^https?:\/\//i.test(uri)) {
@@ -720,23 +805,31 @@ async function loadCachedManifestById(id) {
720
805
  if (!slug) return null;
721
806
  const p = path.join(IIIF_CACHE_MANIFESTS_DIR, slug + ".json");
722
807
  if (!fs.existsSync(p)) return null;
723
- return await readJson(p);
808
+ const raw = await readJson(p);
809
+ const {manifest: normalized, changed} = await ensurePresentation3Manifest(raw);
810
+ if (changed) {
811
+ try {
812
+ await fsp.writeFile(p, JSON.stringify(normalized, null, 2), "utf8");
813
+ } catch (_) {}
814
+ }
815
+ return normalized;
724
816
  } catch (_) {
725
817
  return null;
726
818
  }
727
819
  }
728
820
 
729
821
  async function saveCachedManifest(manifest, id, parentId) {
822
+ const {manifest: normalizedManifest} = await ensurePresentation3Manifest(manifest);
730
823
  try {
731
824
  const index = await loadManifestIndex();
732
- const title = firstLabelString(manifest && manifest.label);
825
+ const title = firstLabelString(normalizedManifest && normalizedManifest.label);
733
826
  const baseSlug =
734
827
  slugify(title || "untitled", {lower: true, strict: true, trim: true}) ||
735
828
  "untitled";
736
829
  const slug = computeUniqueSlug(index, baseSlug, id, "Manifest");
737
830
  ensureDirSync(IIIF_CACHE_MANIFESTS_DIR);
738
831
  const dest = path.join(IIIF_CACHE_MANIFESTS_DIR, slug + ".json");
739
- await fsp.writeFile(dest, JSON.stringify(manifest, null, 2), "utf8");
832
+ await fsp.writeFile(dest, JSON.stringify(normalizedManifest, null, 2), "utf8");
740
833
  index.byId = Array.isArray(index.byId) ? index.byId : [];
741
834
  const nid = normalizeIiifId(id);
742
835
  const existingEntryIdx = index.byId.findIndex(
@@ -751,7 +844,10 @@ async function saveCachedManifest(manifest, id, parentId) {
751
844
  if (existingEntryIdx >= 0) index.byId[existingEntryIdx] = entry;
752
845
  else index.byId.push(entry);
753
846
  await saveManifestIndex(index);
754
- } catch (_) {}
847
+ return normalizedManifest;
848
+ } catch (_) {
849
+ return normalizedManifest;
850
+ }
755
851
  }
756
852
 
757
853
  // Ensure any configured featured manifests are present in the local cache
@@ -772,10 +868,10 @@ async function ensureFeaturedInCache(cfg) {
772
868
  if (!manifest) {
773
869
  const m = await readJsonFromUri(id).catch(() => null);
774
870
  if (!m) continue;
775
- const v3 = await normalizeToV3(m);
776
- if (!v3 || !v3.id) continue;
777
- await saveCachedManifest(v3, id, "");
778
- manifest = v3;
871
+ const upgraded = await upgradeIiifResource(m);
872
+ if (!upgraded || !upgraded.id) continue;
873
+ manifest = (await saveCachedManifest(upgraded, id, "")) || upgraded;
874
+ manifest = (await loadCachedManifestById(id)) || manifest;
779
875
  }
780
876
  // Ensure thumbnail fields exist in index for this manifest (if computable)
781
877
  try {
@@ -958,9 +1054,10 @@ async function loadCachedCollectionById(id) {
958
1054
 
959
1055
  async function saveCachedCollection(collection, id, parentId) {
960
1056
  try {
1057
+ const normalizedCollection = await upgradeIiifResource(collection);
961
1058
  ensureDirSync(IIIF_CACHE_COLLECTIONS_DIR);
962
1059
  const index = await loadManifestIndex();
963
- const title = firstLabelString(collection && collection.label);
1060
+ const title = firstLabelString(normalizedCollection && normalizedCollection.label);
964
1061
  const baseSlug =
965
1062
  slugify(title || "collection", {
966
1063
  lower: true,
@@ -969,7 +1066,7 @@ async function saveCachedCollection(collection, id, parentId) {
969
1066
  }) || "collection";
970
1067
  const slug = computeUniqueSlug(index, baseSlug, id, "Collection");
971
1068
  const dest = path.join(IIIF_CACHE_COLLECTIONS_DIR, slug + ".json");
972
- await fsp.writeFile(dest, JSON.stringify(collection, null, 2), "utf8");
1069
+ await fsp.writeFile(dest, JSON.stringify(normalizedCollection, null, 2), "utf8");
973
1070
  try {
974
1071
  if (process.env.CANOPY_IIIF_DEBUG === "1") {
975
1072
  const {logLine} = require("./log");
@@ -1222,7 +1319,7 @@ async function buildIiifCollectionPages(CONFIG) {
1222
1319
  ? colLike
1223
1320
  : await readJsonFromUri(uri, {log: true});
1224
1321
  if (!col) return;
1225
- const ncol = await normalizeToV3(col);
1322
+ const ncol = await upgradeIiifResource(col);
1226
1323
  const reportedId = String(
1227
1324
  (ncol && (ncol.id || ncol["@id"])) ||
1228
1325
  (typeof colLike === "object" &&
@@ -1237,15 +1334,15 @@ async function buildIiifCollectionPages(CONFIG) {
1237
1334
  try {
1238
1335
  await saveCachedCollection(ncol, collectionKey, parentId || "");
1239
1336
  } catch (_) {}
1240
- const itemsArr = Array.isArray(ncol.items) ? ncol.items : [];
1241
- for (const entry of itemsArr) {
1242
- if (!entry) continue;
1243
- const t = String(entry.type || entry["@type"] || "").toLowerCase();
1244
- const entryId = entry.id || entry["@id"] || "";
1245
- if (t === "manifest") {
1337
+ const childEntries = extractCollectionEntries(ncol);
1338
+ for (const entry of childEntries) {
1339
+ const entryId = entry && entry.id;
1340
+ if (!entryId) continue;
1341
+ const entryType = normalizeIiifType(entry.type || entry.fallback || "");
1342
+ if (entryType === "manifest") {
1246
1343
  tasks.push({id: entryId, parent: collectionKey});
1247
- } else if (t === "collection") {
1248
- await gatherFromCollection(entryId, collectionKey);
1344
+ } else if (entryType === "collection") {
1345
+ await gatherFromCollection(entry.raw || entryId, collectionKey);
1249
1346
  }
1250
1347
  }
1251
1348
  // Traverse strictly by parent/child hierarchy (Presentation 3): items → Manifest or Collection
@@ -1266,7 +1363,7 @@ async function buildIiifCollectionPages(CONFIG) {
1266
1363
  } catch (_) {}
1267
1364
  continue;
1268
1365
  }
1269
- const normalizedRoot = await normalizeToV3(root);
1366
+ const normalizedRoot = await upgradeIiifResource(root);
1270
1367
  try {
1271
1368
  await saveCachedCollection(normalizedRoot, normalizedRoot.id || uri, "");
1272
1369
  } catch (_) {}
@@ -1352,13 +1449,15 @@ async function buildIiifCollectionPages(CONFIG) {
1352
1449
  if (res && res.ok) {
1353
1450
  lns.push([`↓ ${String(id)} → ${res.status}`, "yellow"]);
1354
1451
  const remote = await res.json();
1355
- const norm = await normalizeToV3(remote);
1356
- manifest = norm;
1357
- await saveCachedManifest(
1452
+ manifest = await upgradeIiifResource(remote);
1453
+ const saved = await saveCachedManifest(
1358
1454
  manifest,
1359
1455
  String(id),
1360
1456
  String(it.parent || "")
1361
1457
  );
1458
+ manifest = saved || manifest;
1459
+ const cached = await loadCachedManifestById(String(id));
1460
+ if (cached) manifest = cached;
1362
1461
  } else {
1363
1462
  lns.push([
1364
1463
  `⊘ ${String(id)} → ${res ? res.status : "ERR"}`,
@@ -1377,13 +1476,15 @@ async function buildIiifCollectionPages(CONFIG) {
1377
1476
  lns.push([`⊘ ${String(id)} → ERR`, "red"]);
1378
1477
  continue;
1379
1478
  }
1380
- const norm = await normalizeToV3(local);
1381
- manifest = norm;
1382
- await saveCachedManifest(
1479
+ manifest = await upgradeIiifResource(local);
1480
+ const saved = await saveCachedManifest(
1383
1481
  manifest,
1384
1482
  String(id),
1385
1483
  String(it.parent || "")
1386
1484
  );
1485
+ manifest = saved || manifest;
1486
+ const cached = await loadCachedManifestById(String(id));
1487
+ if (cached) manifest = cached;
1387
1488
  lns.push([`↓ ${String(id)} → Cached`, "yellow"]);
1388
1489
  } catch (_) {
1389
1490
  lns.push([`⊘ ${String(id)} → ERR`, "red"]);
@@ -1394,7 +1495,8 @@ async function buildIiifCollectionPages(CONFIG) {
1394
1495
  continue;
1395
1496
  }
1396
1497
  if (!manifest) continue;
1397
- manifest = await normalizeToV3(manifest);
1498
+ const ensured = await ensurePresentation3Manifest(manifest);
1499
+ manifest = ensured.manifest;
1398
1500
  const title = firstLabelString(manifest.label);
1399
1501
  let summaryRaw = '';
1400
1502
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-iiif/app",
3
- "version": "1.2.8",
3
+ "version": "1.3.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",
package/ui/theme.js CHANGED
@@ -219,9 +219,6 @@ function toTailwindScale(name, options = {}) {
219
219
  if (!value) return null;
220
220
  scale[lvl] = value;
221
221
  }
222
- if (scale["50"] && scale["100"]) {
223
- scale["50"] = mixHexColors(scale["50"], scale["100"], 0.6);
224
- }
225
222
  const saturate700 = options.saturate700 !== false;
226
223
  if (scale["700"]) {
227
224
  let adjusted =