@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 +129 -27
- package/package.json +1 -1
- package/ui/theme.js +0 -3
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
776
|
-
if (!
|
|
777
|
-
await saveCachedManifest(
|
|
778
|
-
manifest =
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
1241
|
-
for (const entry of
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
const
|
|
1245
|
-
if (
|
|
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 (
|
|
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
|
|
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
|
-
|
|
1356
|
-
|
|
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
|
-
|
|
1381
|
-
|
|
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
|
-
|
|
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
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 =
|