@canopy-iiif/app 0.9.14 → 0.10.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/AGENTS.md +1 -0
- package/lib/build/build.js +1 -0
- package/lib/build/dev.js +17 -4
- package/lib/build/iiif.js +234 -12
- package/lib/build/mdx.js +2 -0
- package/lib/build/pages.js +17 -1
- package/lib/build/styles.js +4 -8
- package/lib/components/featured.js +6 -0
- package/lib/components/hero-slider-runtime.js +9 -2
- package/lib/iiif/thumbnail.js +262 -4
- package/package.json +14 -1
- package/ui/dist/index.mjs +191 -99
- package/ui/dist/index.mjs.map +4 -4
- package/ui/dist/server.mjs +295 -187
- package/ui/dist/server.mjs.map +4 -4
- package/ui/styles/base/_common.scss +33 -33
- package/ui/styles/base/_heading.scss +26 -19
- package/ui/styles/base/index.scss +0 -1
- package/ui/styles/components/_buttons.scss +53 -46
- package/ui/styles/components/_interstitial-hero.scss +13 -18
- package/ui/styles/components/_sub-navigation.scss +1 -0
- package/ui/styles/components/header/_header.scss +10 -3
- package/ui/styles/components/header/_logo.scss +32 -33
- package/ui/styles/components/search/_form.scss +4 -9
- package/ui/styles/index.css +161 -171
- package/ui/tailwind-canopy-iiif-preset.js +5 -15
- package/ui/tailwind-config.d.ts +21 -0
- package/ui/tailwind-config.js +134 -0
- package/ui/tailwind-default.config.mjs +3 -0
- package/ui/theme.js +4 -4
- package/ui/styles/_variables.scss +0 -1
package/lib/AGENTS.md
CHANGED
|
@@ -64,6 +64,7 @@ Logbook
|
|
|
64
64
|
- 2025-09-26 / chatgpt: Replaced the legacy command runtime stub with an esbuild-bundled runtime (`search/search-form-runtime.js`); `prepareSearchFormRuntime()` now builds `site/scripts/canopy-search-form.js` and fails if esbuild is missing.
|
|
65
65
|
- 2025-09-27 / chatgpt: Documented Tailwind token flow in `app/styles/tailwind.config.mts`, compiled UI Sass variables during config load, and exposed `stylesheetHref`/`Stylesheet` helpers via `@canopy-iiif/app/head` so `_app.mdx` can reference the generated CSS directly.
|
|
66
66
|
- 2025-09-27 / chatgpt: Expanded search indexing to harvest MDX pages (respecting frontmatter/layout types), injected BASE_PATH hydration data into search.html, and reworked `mdx.extractTitle()` so generated records surface real headings instead of `Untitled`.
|
|
67
|
+
- 2025-10-19 / chatgpt: Embedded the Tailwind preset/plugin in a packaged config so dev/build fall back automatically; removed `app/styles/tailwind.config.*` from the default app and switched the public stylesheet to Tailwind’s CSS-first (`@import 'tailwindcss'; @theme { ... }`) workflow.
|
|
67
68
|
|
|
68
69
|
Verification Commands
|
|
69
70
|
---------------------
|
package/lib/build/build.js
CHANGED
|
@@ -76,6 +76,7 @@ async function build(options = {}) {
|
|
|
76
76
|
// so SSR interstitials can resolve items even if they are not part of
|
|
77
77
|
// the traversed collection or when IIIF build is skipped during incremental rebuilds.
|
|
78
78
|
try { await iiif.ensureFeaturedInCache(CONFIG); } catch (_) {}
|
|
79
|
+
try { await iiif.rebuildManifestIndexFromCache(); } catch (_) {}
|
|
79
80
|
|
|
80
81
|
/**
|
|
81
82
|
* Build contextual MDX content from the content directory.
|
package/lib/build/dev.js
CHANGED
|
@@ -816,17 +816,30 @@ async function dev() {
|
|
|
816
816
|
"tailwind.config.mts",
|
|
817
817
|
"tailwind.config.ts",
|
|
818
818
|
].map((n) => path.join(appStylesDir, n));
|
|
819
|
-
|
|
819
|
+
let configPath = [...twConfigsApp, ...twConfigsRoot].find((p) => {
|
|
820
820
|
try {
|
|
821
821
|
return fs.existsSync(p);
|
|
822
822
|
} catch (_) {
|
|
823
823
|
return false;
|
|
824
824
|
}
|
|
825
825
|
});
|
|
826
|
+
const fallbackConfig = (() => {
|
|
827
|
+
try {
|
|
828
|
+
return require.resolve("@canopy-iiif/app/ui/tailwind-default-config");
|
|
829
|
+
} catch (_) {
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
})();
|
|
826
833
|
if (!configPath) {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
834
|
+
configPath = fallbackConfig;
|
|
835
|
+
if (configPath) {
|
|
836
|
+
console.log(
|
|
837
|
+
"[tailwind] no local config found — using the built-in Canopy config"
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
if (!configPath) {
|
|
842
|
+
throw new Error("[tailwind] Unable to resolve a Tailwind config file.");
|
|
830
843
|
}
|
|
831
844
|
const inputCandidates = [
|
|
832
845
|
path.join(appStylesDir, "index.css"),
|
package/lib/build/iiif.js
CHANGED
|
@@ -15,6 +15,13 @@ const {
|
|
|
15
15
|
} = require("../common");
|
|
16
16
|
const mdx = require("./mdx");
|
|
17
17
|
const {log, logLine, logResponse} = require("./log");
|
|
18
|
+
const {
|
|
19
|
+
getThumbnail,
|
|
20
|
+
getRepresentativeImage,
|
|
21
|
+
buildIiifImageUrlFromService,
|
|
22
|
+
findPrimaryCanvasImage,
|
|
23
|
+
buildIiifImageSrcset,
|
|
24
|
+
} = require("../iiif/thumbnail");
|
|
18
25
|
|
|
19
26
|
const IIIF_CACHE_DIR = path.resolve(".cache/iiif");
|
|
20
27
|
const IIIF_CACHE_MANIFESTS_DIR = path.join(IIIF_CACHE_DIR, "manifests");
|
|
@@ -35,6 +42,8 @@ const IIIF_CACHE_INDEX_MANIFESTS = path.join(
|
|
|
35
42
|
const DEFAULT_THUMBNAIL_SIZE = 400;
|
|
36
43
|
const DEFAULT_CHUNK_SIZE = 20;
|
|
37
44
|
const DEFAULT_FETCH_CONCURRENCY = 5;
|
|
45
|
+
const HERO_THUMBNAIL_SIZE = 800;
|
|
46
|
+
const HERO_IMAGE_SIZES_ATTR = "(min-width: 1024px) 1280px, 100vw";
|
|
38
47
|
|
|
39
48
|
function resolvePositiveInteger(value, fallback) {
|
|
40
49
|
const num = Number(value);
|
|
@@ -98,6 +107,20 @@ function normalizeMetadataLabel(label) {
|
|
|
98
107
|
return trimmed.toLowerCase();
|
|
99
108
|
}
|
|
100
109
|
|
|
110
|
+
function resolveParentFromPartOf(resource) {
|
|
111
|
+
try {
|
|
112
|
+
const partOf = resource && resource.partOf;
|
|
113
|
+
if (!partOf) return "";
|
|
114
|
+
const arr = Array.isArray(partOf) ? partOf : [partOf];
|
|
115
|
+
for (const entry of arr) {
|
|
116
|
+
if (!entry) continue;
|
|
117
|
+
const id = entry.id || entry["@id"];
|
|
118
|
+
if (id) return String(id);
|
|
119
|
+
}
|
|
120
|
+
} catch (_) {}
|
|
121
|
+
return "";
|
|
122
|
+
}
|
|
123
|
+
|
|
101
124
|
function extractSummaryValues(manifest) {
|
|
102
125
|
const values = [];
|
|
103
126
|
try {
|
|
@@ -586,10 +609,8 @@ async function ensureFeaturedInCache(cfg) {
|
|
|
586
609
|
? CONFIG.featured
|
|
587
610
|
: [];
|
|
588
611
|
if (!featured.length) return;
|
|
589
|
-
const {getThumbnail, getRepresentativeImage} = require("../iiif/thumbnail");
|
|
590
612
|
const {size: thumbSize, unsafe: unsafeThumbs} =
|
|
591
613
|
resolveThumbnailPreferences();
|
|
592
|
-
const HERO_THUMBNAIL_SIZE = 1200;
|
|
593
614
|
for (const rawId of featured) {
|
|
594
615
|
const id = normalizeIiifId(String(rawId || ""));
|
|
595
616
|
if (!id) continue;
|
|
@@ -651,22 +672,69 @@ async function ensureFeaturedInCache(cfg) {
|
|
|
651
672
|
HERO_THUMBNAIL_SIZE,
|
|
652
673
|
true
|
|
653
674
|
);
|
|
654
|
-
|
|
655
|
-
|
|
675
|
+
const canvasImage = findPrimaryCanvasImage(manifest);
|
|
676
|
+
const heroService =
|
|
677
|
+
(canvasImage && canvasImage.service) ||
|
|
678
|
+
(heroRep && heroRep.service);
|
|
679
|
+
const preferredHeroThumbnail = buildIiifImageUrlFromService(
|
|
680
|
+
heroService,
|
|
681
|
+
HERO_THUMBNAIL_SIZE
|
|
682
|
+
);
|
|
683
|
+
const heroSrcset = buildIiifImageSrcset(heroService);
|
|
684
|
+
const heroFallbackId = (() => {
|
|
685
|
+
if (canvasImage && canvasImage.id) return String(canvasImage.id);
|
|
686
|
+
if (heroRep && heroRep.id) return String(heroRep.id);
|
|
687
|
+
return "";
|
|
688
|
+
})();
|
|
689
|
+
const heroWidth = (() => {
|
|
690
|
+
if (canvasImage && typeof canvasImage.width === "number") {
|
|
691
|
+
return canvasImage.width;
|
|
692
|
+
}
|
|
693
|
+
if (heroRep && typeof heroRep.width === "number") {
|
|
694
|
+
return heroRep.width;
|
|
695
|
+
}
|
|
696
|
+
return undefined;
|
|
697
|
+
})();
|
|
698
|
+
const heroHeight = (() => {
|
|
699
|
+
if (canvasImage && typeof canvasImage.height === "number") {
|
|
700
|
+
return canvasImage.height;
|
|
701
|
+
}
|
|
702
|
+
if (heroRep && typeof heroRep.height === "number") {
|
|
703
|
+
return heroRep.height;
|
|
704
|
+
}
|
|
705
|
+
return undefined;
|
|
706
|
+
})();
|
|
707
|
+
if (preferredHeroThumbnail || heroFallbackId) {
|
|
708
|
+
const nextHero = preferredHeroThumbnail || heroFallbackId;
|
|
656
709
|
if (entry.heroThumbnail !== nextHero) {
|
|
657
710
|
entry.heroThumbnail = nextHero;
|
|
658
711
|
touched = true;
|
|
659
712
|
}
|
|
660
|
-
if (
|
|
661
|
-
if (entry.
|
|
662
|
-
entry.
|
|
713
|
+
if (heroSrcset) {
|
|
714
|
+
if (entry.heroThumbnailSrcset !== heroSrcset) touched = true;
|
|
715
|
+
entry.heroThumbnailSrcset = heroSrcset;
|
|
716
|
+
if (entry.heroThumbnailSizes !== HERO_IMAGE_SIZES_ATTR) touched = true;
|
|
717
|
+
entry.heroThumbnailSizes = HERO_IMAGE_SIZES_ATTR;
|
|
718
|
+
} else {
|
|
719
|
+
if (entry.heroThumbnailSrcset !== undefined) {
|
|
720
|
+
delete entry.heroThumbnailSrcset;
|
|
721
|
+
touched = true;
|
|
722
|
+
}
|
|
723
|
+
if (entry.heroThumbnailSizes !== undefined) {
|
|
724
|
+
delete entry.heroThumbnailSizes;
|
|
725
|
+
touched = true;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
if (typeof heroWidth === "number") {
|
|
729
|
+
if (entry.heroThumbnailWidth !== heroWidth) touched = true;
|
|
730
|
+
entry.heroThumbnailWidth = heroWidth;
|
|
663
731
|
} else if (entry.heroThumbnailWidth !== undefined) {
|
|
664
732
|
delete entry.heroThumbnailWidth;
|
|
665
733
|
touched = true;
|
|
666
734
|
}
|
|
667
|
-
if (typeof
|
|
668
|
-
if (entry.heroThumbnailHeight !==
|
|
669
|
-
entry.heroThumbnailHeight =
|
|
735
|
+
if (typeof heroHeight === "number") {
|
|
736
|
+
if (entry.heroThumbnailHeight !== heroHeight) touched = true;
|
|
737
|
+
entry.heroThumbnailHeight = heroHeight;
|
|
670
738
|
} else if (entry.heroThumbnailHeight !== undefined) {
|
|
671
739
|
delete entry.heroThumbnailHeight;
|
|
672
740
|
touched = true;
|
|
@@ -810,6 +878,158 @@ async function saveCachedCollection(collection, id, parentId) {
|
|
|
810
878
|
} catch (_) {}
|
|
811
879
|
}
|
|
812
880
|
|
|
881
|
+
async function rebuildManifestIndexFromCache() {
|
|
882
|
+
try {
|
|
883
|
+
const previous = await loadManifestIndex();
|
|
884
|
+
const previousEntries = Array.isArray(previous.byId) ? previous.byId : [];
|
|
885
|
+
const priorMap = new Map();
|
|
886
|
+
for (const entry of previousEntries) {
|
|
887
|
+
if (!entry || !entry.id) continue;
|
|
888
|
+
const type = entry.type || "Manifest";
|
|
889
|
+
const key = `${type}:${normalizeIiifId(entry.id)}`;
|
|
890
|
+
priorMap.set(key, entry);
|
|
891
|
+
}
|
|
892
|
+
const nextIndex = {
|
|
893
|
+
byId: [],
|
|
894
|
+
collection: previous.collection || null,
|
|
895
|
+
};
|
|
896
|
+
const collectionFiles = fs.existsSync(IIIF_CACHE_COLLECTIONS_DIR)
|
|
897
|
+
? (await fsp.readdir(IIIF_CACHE_COLLECTIONS_DIR))
|
|
898
|
+
.filter((name) => name && name.toLowerCase().endsWith(".json"))
|
|
899
|
+
.sort()
|
|
900
|
+
: [];
|
|
901
|
+
const manifestFiles = fs.existsSync(IIIF_CACHE_MANIFESTS_DIR)
|
|
902
|
+
? (await fsp.readdir(IIIF_CACHE_MANIFESTS_DIR))
|
|
903
|
+
.filter((name) => name && name.toLowerCase().endsWith(".json"))
|
|
904
|
+
.sort()
|
|
905
|
+
: [];
|
|
906
|
+
const {size: thumbSize, unsafe: unsafeThumbs} =
|
|
907
|
+
resolveThumbnailPreferences();
|
|
908
|
+
|
|
909
|
+
for (const name of collectionFiles) {
|
|
910
|
+
const slug = name.replace(/\.json$/i, "");
|
|
911
|
+
const fp = path.join(IIIF_CACHE_COLLECTIONS_DIR, name);
|
|
912
|
+
let data = null;
|
|
913
|
+
try {
|
|
914
|
+
data = await readJson(fp);
|
|
915
|
+
} catch (_) {
|
|
916
|
+
data = null;
|
|
917
|
+
}
|
|
918
|
+
if (!data) continue;
|
|
919
|
+
const id = data.id || data["@id"];
|
|
920
|
+
if (!id) continue;
|
|
921
|
+
const nid = normalizeIiifId(String(id));
|
|
922
|
+
const key = `Collection:${nid}`;
|
|
923
|
+
const fallback = priorMap.get(key) || {};
|
|
924
|
+
const parent = resolveParentFromPartOf(data) || fallback.parent || "";
|
|
925
|
+
nextIndex.byId.push({
|
|
926
|
+
id: String(nid),
|
|
927
|
+
type: "Collection",
|
|
928
|
+
slug,
|
|
929
|
+
parent,
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
for (const name of manifestFiles) {
|
|
934
|
+
const slug = name.replace(/\.json$/i, "");
|
|
935
|
+
const fp = path.join(IIIF_CACHE_MANIFESTS_DIR, name);
|
|
936
|
+
let manifest = null;
|
|
937
|
+
try {
|
|
938
|
+
manifest = await readJson(fp);
|
|
939
|
+
} catch (_) {
|
|
940
|
+
manifest = null;
|
|
941
|
+
}
|
|
942
|
+
if (!manifest) continue;
|
|
943
|
+
const id = manifest.id || manifest["@id"];
|
|
944
|
+
if (!id) continue;
|
|
945
|
+
const nid = normalizeIiifId(String(id));
|
|
946
|
+
MEMO_ID_TO_SLUG.set(String(id), slug);
|
|
947
|
+
const key = `Manifest:${nid}`;
|
|
948
|
+
const fallback = priorMap.get(key) || {};
|
|
949
|
+
const parent = resolveParentFromPartOf(manifest) || fallback.parent || "";
|
|
950
|
+
const entry = {
|
|
951
|
+
id: String(nid),
|
|
952
|
+
type: "Manifest",
|
|
953
|
+
slug,
|
|
954
|
+
parent,
|
|
955
|
+
};
|
|
956
|
+
try {
|
|
957
|
+
const thumb = await getThumbnail(manifest, thumbSize, unsafeThumbs);
|
|
958
|
+
if (thumb && thumb.url) {
|
|
959
|
+
entry.thumbnail = String(thumb.url);
|
|
960
|
+
if (typeof thumb.width === "number") entry.thumbnailWidth = thumb.width;
|
|
961
|
+
if (typeof thumb.height === "number") entry.thumbnailHeight = thumb.height;
|
|
962
|
+
}
|
|
963
|
+
} catch (_) {}
|
|
964
|
+
try {
|
|
965
|
+
const heroSource = (() => {
|
|
966
|
+
if (manifest && manifest.thumbnail) {
|
|
967
|
+
const clone = {...manifest};
|
|
968
|
+
try {
|
|
969
|
+
delete clone.thumbnail;
|
|
970
|
+
} catch (_) {
|
|
971
|
+
clone.thumbnail = undefined;
|
|
972
|
+
}
|
|
973
|
+
return clone;
|
|
974
|
+
}
|
|
975
|
+
return manifest;
|
|
976
|
+
})();
|
|
977
|
+
const heroRep = await getRepresentativeImage(
|
|
978
|
+
heroSource || manifest,
|
|
979
|
+
HERO_THUMBNAIL_SIZE,
|
|
980
|
+
true
|
|
981
|
+
);
|
|
982
|
+
const canvasImage = findPrimaryCanvasImage(manifest);
|
|
983
|
+
const heroService =
|
|
984
|
+
(canvasImage && canvasImage.service) ||
|
|
985
|
+
(heroRep && heroRep.service);
|
|
986
|
+
const preferredHero = buildIiifImageUrlFromService(
|
|
987
|
+
heroService,
|
|
988
|
+
HERO_THUMBNAIL_SIZE
|
|
989
|
+
);
|
|
990
|
+
const heroFallbackId = (() => {
|
|
991
|
+
if (canvasImage && canvasImage.id) return String(canvasImage.id);
|
|
992
|
+
if (heroRep && heroRep.id) return String(heroRep.id);
|
|
993
|
+
return "";
|
|
994
|
+
})();
|
|
995
|
+
const heroWidth = (() => {
|
|
996
|
+
if (canvasImage && typeof canvasImage.width === "number")
|
|
997
|
+
return canvasImage.width;
|
|
998
|
+
if (heroRep && typeof heroRep.width === "number") return heroRep.width;
|
|
999
|
+
return undefined;
|
|
1000
|
+
})();
|
|
1001
|
+
const heroHeight = (() => {
|
|
1002
|
+
if (canvasImage && typeof canvasImage.height === "number")
|
|
1003
|
+
return canvasImage.height;
|
|
1004
|
+
if (heroRep && typeof heroRep.height === "number")
|
|
1005
|
+
return heroRep.height;
|
|
1006
|
+
return undefined;
|
|
1007
|
+
})();
|
|
1008
|
+
if (preferredHero || heroFallbackId) {
|
|
1009
|
+
entry.heroThumbnail = preferredHero || heroFallbackId;
|
|
1010
|
+
if (typeof heroWidth === "number") entry.heroThumbnailWidth = heroWidth;
|
|
1011
|
+
if (typeof heroHeight === "number") entry.heroThumbnailHeight = heroHeight;
|
|
1012
|
+
const heroSrcset = buildIiifImageSrcset(heroService);
|
|
1013
|
+
if (heroSrcset) {
|
|
1014
|
+
entry.heroThumbnailSrcset = heroSrcset;
|
|
1015
|
+
entry.heroThumbnailSizes = HERO_IMAGE_SIZES_ATTR;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
} catch (_) {}
|
|
1019
|
+
nextIndex.byId.push(entry);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
await saveManifestIndex(nextIndex);
|
|
1023
|
+
try {
|
|
1024
|
+
logLine("✓ Rebuilt IIIF cache index", "cyan");
|
|
1025
|
+
} catch (_) {}
|
|
1026
|
+
} catch (err) {
|
|
1027
|
+
try {
|
|
1028
|
+
logLine("! Skipped IIIF index rebuild", "yellow");
|
|
1029
|
+
} catch (_) {}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
|
|
813
1033
|
async function loadConfig() {
|
|
814
1034
|
const cfgPath = path.resolve("canopy.yml");
|
|
815
1035
|
if (!fs.existsSync(cfgPath)) return {};
|
|
@@ -1155,7 +1375,9 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1155
1375
|
)
|
|
1156
1376
|
: "";
|
|
1157
1377
|
const needsHydrateViewer =
|
|
1158
|
-
body.includes("data-canopy-viewer") ||
|
|
1378
|
+
body.includes("data-canopy-viewer") ||
|
|
1379
|
+
body.includes("data-canopy-scroll") ||
|
|
1380
|
+
body.includes("data-canopy-image");
|
|
1159
1381
|
const needsRelated = body.includes("data-canopy-related-items");
|
|
1160
1382
|
const needsHeroSlider = body.includes("data-canopy-hero-slider");
|
|
1161
1383
|
const needsSearchForm = body.includes("data-canopy-search-form");
|
|
@@ -1281,7 +1503,6 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1281
1503
|
let thumbWidth = undefined;
|
|
1282
1504
|
let thumbHeight = undefined;
|
|
1283
1505
|
try {
|
|
1284
|
-
const {getThumbnail} = require("../iiif/thumbnail");
|
|
1285
1506
|
const t = await getThumbnail(manifest, thumbSize, unsafeThumbs);
|
|
1286
1507
|
if (t && t.url) {
|
|
1287
1508
|
thumbUrl = String(t.url);
|
|
@@ -1382,6 +1603,7 @@ module.exports = {
|
|
|
1382
1603
|
loadCachedManifestById,
|
|
1383
1604
|
saveCachedManifest,
|
|
1384
1605
|
ensureFeaturedInCache,
|
|
1606
|
+
rebuildManifestIndexFromCache,
|
|
1385
1607
|
};
|
|
1386
1608
|
|
|
1387
1609
|
// Debug: list collections cache after traversal
|
package/lib/build/mdx.js
CHANGED
|
@@ -525,6 +525,7 @@ async function ensureClientRuntime() {
|
|
|
525
525
|
const entry = `
|
|
526
526
|
import CloverViewer from '@samvera/clover-iiif/viewer';
|
|
527
527
|
import CloverScroll from '@samvera/clover-iiif/scroll';
|
|
528
|
+
import CloverImage from '@samvera/clover-iiif/image';
|
|
528
529
|
|
|
529
530
|
function ready(fn) {
|
|
530
531
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fn, { once: true });
|
|
@@ -563,6 +564,7 @@ async function ensureClientRuntime() {
|
|
|
563
564
|
ready(function() {
|
|
564
565
|
mountAll('[data-canopy-viewer]', CloverViewer);
|
|
565
566
|
mountAll('[data-canopy-scroll]', CloverScroll);
|
|
567
|
+
mountAll('[data-canopy-image]', CloverImage);
|
|
566
568
|
});
|
|
567
569
|
`;
|
|
568
570
|
const reactShim = `
|
package/lib/build/pages.js
CHANGED
|
@@ -66,7 +66,9 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}) {
|
|
|
66
66
|
}
|
|
67
67
|
const { body, head } = await mdx.compileMdxFile(filePath, outPath, null, mergedProps);
|
|
68
68
|
const needsHydrateViewer =
|
|
69
|
-
body.includes('data-canopy-viewer') ||
|
|
69
|
+
body.includes('data-canopy-viewer') ||
|
|
70
|
+
body.includes('data-canopy-scroll') ||
|
|
71
|
+
body.includes('data-canopy-image');
|
|
70
72
|
const needsHydrateSlider = body.includes('data-canopy-slider');
|
|
71
73
|
const needsHeroSlider = body.includes('data-canopy-hero-slider');
|
|
72
74
|
const needsSearchForm = true; // search form runtime is global
|
|
@@ -80,6 +82,9 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}) {
|
|
|
80
82
|
const heroRel = needsHeroSlider
|
|
81
83
|
? path.relative(path.dirname(outPath), path.join(OUT_DIR, 'scripts', 'canopy-hero-slider.js')).split(path.sep).join('/')
|
|
82
84
|
: null;
|
|
85
|
+
const heroCssRel = needsHeroSlider
|
|
86
|
+
? path.relative(path.dirname(outPath), path.join(OUT_DIR, 'scripts', 'canopy-hero-slider.css')).split(path.sep).join('/')
|
|
87
|
+
: null;
|
|
83
88
|
const facetsRel = needsFacets
|
|
84
89
|
? path.relative(path.dirname(outPath), path.join(OUT_DIR, 'scripts', 'canopy-related-items.js')).split(path.sep).join('/')
|
|
85
90
|
: null;
|
|
@@ -118,6 +123,17 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}) {
|
|
|
118
123
|
if (viewerRel && jsRel !== viewerRel) extraScripts.push(`<script defer src="${viewerRel}"></script>`);
|
|
119
124
|
if (sliderRel && jsRel !== sliderRel) extraScripts.push(`<script defer src="${sliderRel}"></script>`);
|
|
120
125
|
if (searchFormRel && jsRel !== searchFormRel) extraScripts.push(`<script defer src="${searchFormRel}"></script>`);
|
|
126
|
+
const extraStyles = [];
|
|
127
|
+
if (heroCssRel) {
|
|
128
|
+
let rel = heroCssRel;
|
|
129
|
+
try {
|
|
130
|
+
const heroCssAbs = path.join(OUT_DIR, 'scripts', 'canopy-hero-slider.css');
|
|
131
|
+
const st = fs.statSync(heroCssAbs);
|
|
132
|
+
rel += `?v=${Math.floor(st.mtimeMs || Date.now())}`;
|
|
133
|
+
} catch (_) {}
|
|
134
|
+
extraStyles.push(`<link rel="stylesheet" href="${rel}">`);
|
|
135
|
+
}
|
|
136
|
+
if (extraStyles.length) headExtra = extraStyles.join('') + headExtra;
|
|
121
137
|
if (extraScripts.length) headExtra = extraScripts.join('') + headExtra;
|
|
122
138
|
const html = htmlShell({ title, body, cssHref: null, scriptHref: jsRel, headExtra: vendorTag + headExtra });
|
|
123
139
|
const { applyBaseToHtml } = require('../common');
|
package/lib/build/styles.js
CHANGED
|
@@ -39,13 +39,9 @@ async function ensureStyles() {
|
|
|
39
39
|
});
|
|
40
40
|
if (!configPath) {
|
|
41
41
|
try {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const genCfg = path.join(genDir, "tailwind.config.js");
|
|
46
|
-
const cfg = `module.exports = {\n presets: [require('@canopy-iiif/app/ui/canopy-iiif-preset')],\n content: [\n './content/**/*.{mdx,html}',\n './site/**/*.html',\n './site/**/*.js',\n './packages/app/ui/**/*.{js,jsx,ts,tsx}',\n './packages/app/lib/iiif/components/**/*.{js,jsx}',\n ],\n theme: { extend: {} },\n plugins: [require('@canopy-iiif/app/ui/canopy-iiif-plugin')],\n};\n`;
|
|
47
|
-
fs.writeFileSync(genCfg, cfg, "utf8");
|
|
48
|
-
configPath = genCfg;
|
|
42
|
+
configPath = require.resolve(
|
|
43
|
+
"@canopy-iiif/app/ui/tailwind-default-config"
|
|
44
|
+
);
|
|
49
45
|
} catch (_) {
|
|
50
46
|
configPath = null;
|
|
51
47
|
}
|
|
@@ -64,7 +60,7 @@ async function ensureStyles() {
|
|
|
64
60
|
const genDir = path.join(CACHE_DIR, "tailwind");
|
|
65
61
|
ensureDirSync(genDir);
|
|
66
62
|
generatedInput = path.join(genDir, "index.css");
|
|
67
|
-
const css = `@
|
|
63
|
+
const css = `@import 'tailwindcss';\n`;
|
|
68
64
|
fs.writeFileSync(generatedInput, css, "utf8");
|
|
69
65
|
} catch (_) {
|
|
70
66
|
generatedInput = null;
|
|
@@ -117,6 +117,12 @@ function readFeaturedFromCacheSync() {
|
|
|
117
117
|
} else if (typeof entry.thumbnailHeight === 'number') {
|
|
118
118
|
rec.thumbnailHeight = entry.thumbnailHeight;
|
|
119
119
|
}
|
|
120
|
+
if (entry && entry.heroThumbnailSrcset) {
|
|
121
|
+
rec.srcset = String(entry.heroThumbnailSrcset);
|
|
122
|
+
}
|
|
123
|
+
if (entry && entry.heroThumbnailSizes) {
|
|
124
|
+
rec.sizes = String(entry.heroThumbnailSizes);
|
|
125
|
+
}
|
|
120
126
|
} else {
|
|
121
127
|
if (entry && entry.thumbnail) rec.thumbnail = String(entry.thumbnail);
|
|
122
128
|
if (entry && typeof entry.thumbnailWidth === 'number') rec.thumbnailWidth = entry.thumbnailWidth;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import Swiper from 'swiper';
|
|
2
|
-
import { Navigation, Pagination, Autoplay } from 'swiper/modules';
|
|
2
|
+
import { Navigation, Pagination, Autoplay, EffectFade } from 'swiper/modules';
|
|
3
3
|
import 'swiper/css';
|
|
4
4
|
import 'swiper/css/navigation';
|
|
5
5
|
import 'swiper/css/pagination';
|
|
6
|
+
import 'swiper/css/effect-fade';
|
|
6
7
|
|
|
7
8
|
function ready(fn) {
|
|
8
9
|
if (typeof document === 'undefined') return;
|
|
@@ -20,12 +21,18 @@ function initSlider(host) {
|
|
|
20
21
|
const prev = host.querySelector('.canopy-interstitial__nav-btn--prev');
|
|
21
22
|
const next = host.querySelector('.canopy-interstitial__nav-btn--next');
|
|
22
23
|
const pagination = host.querySelector('.canopy-interstitial__pagination');
|
|
24
|
+
const transitionAttr = (host.getAttribute && host.getAttribute('data-transition')) || 'fade';
|
|
25
|
+
const transition = transitionAttr && transitionAttr.toLowerCase() === 'slide' ? 'slide' : 'fade';
|
|
23
26
|
|
|
24
27
|
try {
|
|
28
|
+
const baseModules = [Navigation, Pagination, Autoplay];
|
|
29
|
+
if (transition === 'fade') baseModules.push(EffectFade);
|
|
25
30
|
const swiperInstance = new Swiper(slider, {
|
|
26
|
-
modules:
|
|
31
|
+
modules: baseModules,
|
|
27
32
|
loop: true,
|
|
28
33
|
slidesPerView: 1,
|
|
34
|
+
effect: transition,
|
|
35
|
+
fadeEffect: transition === 'fade' ? { crossFade: true } : undefined,
|
|
29
36
|
navigation: {
|
|
30
37
|
prevEl: prev || undefined,
|
|
31
38
|
nextEl: next || undefined,
|