@canopy-iiif/app 1.0.1 → 1.1.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 +28 -19
- package/lib/build/mdx.js +130 -227
- package/lib/build/pages.js +27 -13
- package/lib/build/runtimes.js +0 -1
- package/lib/components/slider-runtime-entry.js +18 -3
- package/lib/components/viewer-runtime-entry.js +74 -0
- package/package.json +1 -1
- package/ui/dist/server.mjs +9 -9
- package/ui/dist/server.mjs.map +1 -1
- package/ui/styles/components/_sub-navigation.scss +11 -9
- package/ui/styles/components/_timeline.scss +3 -6
- package/ui/styles/index.css +10 -14
package/lib/build/iiif.js
CHANGED
|
@@ -1589,11 +1589,22 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1589
1589
|
.join("/")
|
|
1590
1590
|
: null;
|
|
1591
1591
|
|
|
1592
|
+
const moduleScriptRels = [];
|
|
1593
|
+
if (viewerRel) moduleScriptRels.push(viewerRel);
|
|
1594
|
+
if (sliderRel) moduleScriptRels.push(sliderRel);
|
|
1595
|
+
const primaryClassicScripts = [];
|
|
1596
|
+
if (heroRel) primaryClassicScripts.push(heroRel);
|
|
1597
|
+
if (relatedRel) primaryClassicScripts.push(relatedRel);
|
|
1598
|
+
if (timelineRel) primaryClassicScripts.push(timelineRel);
|
|
1599
|
+
const secondaryClassicScripts = [];
|
|
1600
|
+
if (searchFormRel) secondaryClassicScripts.push(searchFormRel);
|
|
1592
1601
|
let jsRel = null;
|
|
1593
|
-
if (
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1602
|
+
if (primaryClassicScripts.length) {
|
|
1603
|
+
jsRel = primaryClassicScripts.shift();
|
|
1604
|
+
}
|
|
1605
|
+
const classicScriptRels = primaryClassicScripts.concat(
|
|
1606
|
+
secondaryClassicScripts
|
|
1607
|
+
);
|
|
1597
1608
|
|
|
1598
1609
|
const headSegments = [head];
|
|
1599
1610
|
const needsReact = !!(
|
|
@@ -1621,20 +1632,16 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1621
1632
|
} catch (_) {}
|
|
1622
1633
|
}
|
|
1623
1634
|
const extraScripts = [];
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
if (searchFormRel && jsRel !== searchFormRel)
|
|
1635
|
-
extraScripts.push(`<script defer src="${searchFormRel}"></script>`);
|
|
1636
|
-
if (extraScripts.length)
|
|
1637
|
-
headSegments.push(extraScripts.join(""));
|
|
1635
|
+
const pushClassicScript = (src) => {
|
|
1636
|
+
if (!src || src === jsRel) return;
|
|
1637
|
+
extraScripts.push(`<script defer src="${src}"></script>`);
|
|
1638
|
+
};
|
|
1639
|
+
const pushModuleScript = (src) => {
|
|
1640
|
+
if (!src) return;
|
|
1641
|
+
extraScripts.push(`<script type="module" src="${src}"></script>`);
|
|
1642
|
+
};
|
|
1643
|
+
classicScriptRels.forEach((src) => pushClassicScript(src));
|
|
1644
|
+
moduleScriptRels.forEach((src) => pushModuleScript(src));
|
|
1638
1645
|
try {
|
|
1639
1646
|
const {BASE_PATH} = require("../common");
|
|
1640
1647
|
if (BASE_PATH)
|
|
@@ -1644,7 +1651,9 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1644
1651
|
)}</script>` + vendorTag;
|
|
1645
1652
|
} catch (_) {}
|
|
1646
1653
|
let pageBody = body;
|
|
1647
|
-
|
|
1654
|
+
if (vendorTag) headSegments.push(vendorTag);
|
|
1655
|
+
if (extraScripts.length) headSegments.push(extraScripts.join(""));
|
|
1656
|
+
const headExtra = headSegments.join("");
|
|
1648
1657
|
const pageType = (pageDetails && pageDetails.type) || "work";
|
|
1649
1658
|
const bodyClass = canopyBodyClassForType(pageType);
|
|
1650
1659
|
let html = htmlShell({
|
package/lib/build/mdx.js
CHANGED
|
@@ -651,72 +651,19 @@ async function loadCustomLayout(defaultLayout) {
|
|
|
651
651
|
return defaultLayout;
|
|
652
652
|
}
|
|
653
653
|
|
|
654
|
-
|
|
655
|
-
// Bundle a lightweight client runtime to hydrate browser-only components
|
|
656
|
-
// like the Clover Viewer when placeholders are present in the HTML.
|
|
657
|
-
let esbuild = null;
|
|
654
|
+
function resolveEsbuild() {
|
|
658
655
|
try {
|
|
659
|
-
|
|
656
|
+
return require("../../ui/node_modules/esbuild");
|
|
660
657
|
} catch (_) {
|
|
661
658
|
try {
|
|
662
|
-
|
|
663
|
-
} catch (_) {
|
|
664
|
-
|
|
665
|
-
if (!esbuild)
|
|
666
|
-
throw new Error(
|
|
667
|
-
"Viewer runtime bundling requires esbuild. Install dependencies before building."
|
|
668
|
-
);
|
|
669
|
-
ensureDirSync(OUT_DIR);
|
|
670
|
-
const scriptsDir = path.join(OUT_DIR, "scripts");
|
|
671
|
-
ensureDirSync(scriptsDir);
|
|
672
|
-
const outFile = path.join(scriptsDir, "canopy-viewer.js");
|
|
673
|
-
const entry = `
|
|
674
|
-
import CloverViewer from '@samvera/clover-iiif/viewer';
|
|
675
|
-
import CloverScroll from '@samvera/clover-iiif/scroll';
|
|
676
|
-
import CloverImage from '@samvera/clover-iiif/image';
|
|
677
|
-
|
|
678
|
-
function ready(fn) {
|
|
679
|
-
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fn, { once: true });
|
|
680
|
-
else fn();
|
|
659
|
+
return require("esbuild");
|
|
660
|
+
} catch (_) {
|
|
661
|
+
return null;
|
|
681
662
|
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
682
665
|
|
|
683
|
-
|
|
684
|
-
try {
|
|
685
|
-
const s = el.querySelector('script[type="application/json"]');
|
|
686
|
-
if (s) return JSON.parse(s.textContent || '{}');
|
|
687
|
-
const raw = el.getAttribute('data-props') || '{}';
|
|
688
|
-
return JSON.parse(raw);
|
|
689
|
-
} catch (_) { return {}; }
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
function mountAll(selector, Component) {
|
|
693
|
-
try {
|
|
694
|
-
const nodes = document.querySelectorAll(selector);
|
|
695
|
-
if (!nodes || !nodes.length || !Component) return;
|
|
696
|
-
const React = (window && window.React) || null;
|
|
697
|
-
const ReactDOMClient = (window && window.ReactDOMClient) || null;
|
|
698
|
-
const createRoot = ReactDOMClient && ReactDOMClient.createRoot;
|
|
699
|
-
if (!React || !createRoot) return;
|
|
700
|
-
for (const el of nodes) {
|
|
701
|
-
try {
|
|
702
|
-
if (el.__canopyHydrated) continue;
|
|
703
|
-
const props = parseProps(el);
|
|
704
|
-
const root = createRoot(el);
|
|
705
|
-
root.render(React.createElement(Component, props));
|
|
706
|
-
el.__canopyHydrated = true;
|
|
707
|
-
} catch (_) { /* skip */ }
|
|
708
|
-
}
|
|
709
|
-
} catch (_) { /* no-op */ }
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
function seedScrollSearchInput() {}
|
|
713
|
-
|
|
714
|
-
ready(function() {
|
|
715
|
-
mountAll('[data-canopy-viewer]', CloverViewer);
|
|
716
|
-
mountAll('[data-canopy-scroll]', CloverScroll);
|
|
717
|
-
mountAll('[data-canopy-image]', CloverImage);
|
|
718
|
-
});
|
|
719
|
-
`;
|
|
666
|
+
function createReactShimPlugin() {
|
|
720
667
|
const reactShim = `
|
|
721
668
|
const React = (typeof window !== 'undefined' && window.React) || {};
|
|
722
669
|
export default React;
|
|
@@ -749,70 +696,145 @@ async function ensureClientRuntime() {
|
|
|
749
696
|
`;
|
|
750
697
|
const rdomClientShim = `
|
|
751
698
|
const RDC = (typeof window !== 'undefined' && window.ReactDOMClient) || {};
|
|
752
|
-
export default RDC;
|
|
753
699
|
export const createRoot = RDC.createRoot;
|
|
754
700
|
export const hydrateRoot = RDC.hydrateRoot;
|
|
755
701
|
`;
|
|
756
|
-
|
|
702
|
+
return {
|
|
757
703
|
name: "canopy-react-shims",
|
|
758
704
|
setup(build) {
|
|
759
705
|
const ns = "canopy-shim";
|
|
760
|
-
build.onResolve({filter: /^react$/}, () => ({
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
}));
|
|
764
|
-
build.
|
|
765
|
-
|
|
766
|
-
namespace: ns,
|
|
767
|
-
}));
|
|
768
|
-
build.onResolve({filter: /^react-dom\/client$/}, () => ({
|
|
769
|
-
path: "react-dom-client",
|
|
770
|
-
namespace: ns,
|
|
771
|
-
}));
|
|
772
|
-
build.onLoad({filter: /^react$/, namespace: ns}, () => ({
|
|
773
|
-
contents: reactShim,
|
|
774
|
-
loader: "js",
|
|
775
|
-
}));
|
|
776
|
-
build.onLoad({filter: /^react-dom$/, namespace: ns}, () => ({
|
|
777
|
-
contents: rdomShim,
|
|
778
|
-
loader: "js",
|
|
779
|
-
}));
|
|
780
|
-
build.onLoad({filter: /^react-dom-client$/, namespace: ns}, () => ({
|
|
781
|
-
contents: rdomClientShim,
|
|
782
|
-
loader: "js",
|
|
783
|
-
}));
|
|
706
|
+
build.onResolve({ filter: /^react$/ }, () => ({ path: "react", namespace: ns }));
|
|
707
|
+
build.onResolve({ filter: /^react-dom$/ }, () => ({ path: "react-dom", namespace: ns }));
|
|
708
|
+
build.onResolve({ filter: /^react-dom\/client$/ }, () => ({ path: "react-dom-client", namespace: ns }));
|
|
709
|
+
build.onLoad({ filter: /^react$/, namespace: ns }, () => ({ contents: reactShim, loader: "js" }));
|
|
710
|
+
build.onLoad({ filter: /^react-dom$/, namespace: ns }, () => ({ contents: rdomShim, loader: "js" }));
|
|
711
|
+
build.onLoad({ filter: /^react-dom-client$/, namespace: ns }, () => ({ contents: rdomClientShim, loader: "js" }));
|
|
784
712
|
},
|
|
785
713
|
};
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function createSliderCssInlinePlugin() {
|
|
717
|
+
return {
|
|
718
|
+
name: "canopy-inline-slider-css",
|
|
719
|
+
setup(build) {
|
|
720
|
+
build.onLoad({ filter: /\.css$/ }, (args) => {
|
|
721
|
+
const fs = require("fs");
|
|
722
|
+
let css = "";
|
|
723
|
+
try {
|
|
724
|
+
css = fs.readFileSync(args.path, "utf8");
|
|
725
|
+
} catch (_) {
|
|
726
|
+
css = "";
|
|
727
|
+
}
|
|
728
|
+
const js = [
|
|
729
|
+
`var css = ${JSON.stringify(css)};`,
|
|
730
|
+
`(function(){ try { var s = document.createElement('style'); s.setAttribute('data-canopy-slider-css',''); s.textContent = css; document.head.appendChild(s); } catch (e) {} })();`,
|
|
731
|
+
`export default css;`,
|
|
732
|
+
].join("\n");
|
|
733
|
+
return { contents: js, loader: "js" };
|
|
734
|
+
});
|
|
792
735
|
},
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
let cloverRuntimePromise = null;
|
|
740
|
+
|
|
741
|
+
function renameAnonymousChunks(scriptsDir) {
|
|
742
|
+
try {
|
|
743
|
+
const entries = fs
|
|
744
|
+
.readdirSync(scriptsDir)
|
|
745
|
+
.filter((name) => name.startsWith('canopy-chunk-') && name.endsWith('.js'));
|
|
746
|
+
if (!entries.length) return;
|
|
747
|
+
const replacements = [];
|
|
748
|
+
for (const oldName of entries) {
|
|
749
|
+
const newName = `canopy-shared-${oldName.slice('canopy-chunk-'.length)}`;
|
|
750
|
+
if (newName === oldName) continue;
|
|
751
|
+
const fromPath = path.join(scriptsDir, oldName);
|
|
752
|
+
const toPath = path.join(scriptsDir, newName);
|
|
753
|
+
try {
|
|
754
|
+
fs.renameSync(fromPath, toPath);
|
|
755
|
+
replacements.push({ from: oldName, to: newName });
|
|
756
|
+
} catch (_) {}
|
|
757
|
+
}
|
|
758
|
+
if (!replacements.length) return;
|
|
759
|
+
const targetFiles = fs
|
|
760
|
+
.readdirSync(scriptsDir)
|
|
761
|
+
.filter((name) => name.endsWith('.js'));
|
|
762
|
+
for (const filename of targetFiles) {
|
|
763
|
+
const filePath = path.join(scriptsDir, filename);
|
|
764
|
+
let contents = '';
|
|
765
|
+
try {
|
|
766
|
+
contents = fs.readFileSync(filePath, 'utf8');
|
|
767
|
+
} catch (_) {
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
let changed = false;
|
|
771
|
+
replacements.forEach(({ from, to }) => {
|
|
772
|
+
if (contents.includes(from)) {
|
|
773
|
+
contents = contents.split(from).join(to);
|
|
774
|
+
changed = true;
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
if (changed) {
|
|
778
|
+
try {
|
|
779
|
+
fs.writeFileSync(filePath, contents, 'utf8');
|
|
780
|
+
} catch (_) {}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
} catch (_) {}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
async function buildCloverHydrationRuntimes() {
|
|
787
|
+
const esbuild = resolveEsbuild();
|
|
788
|
+
if (!esbuild)
|
|
789
|
+
throw new Error(
|
|
790
|
+
"Clover hydration runtimes require esbuild. Install dependencies before building."
|
|
791
|
+
);
|
|
792
|
+
ensureDirSync(OUT_DIR);
|
|
793
|
+
const scriptsDir = path.join(OUT_DIR, "scripts");
|
|
794
|
+
ensureDirSync(scriptsDir);
|
|
795
|
+
const entryPoints = {
|
|
796
|
+
viewer: path.join(__dirname, "../components/viewer-runtime-entry.js"),
|
|
797
|
+
slider: path.join(__dirname, "../components/slider-runtime-entry.js"),
|
|
798
|
+
};
|
|
799
|
+
await esbuild.build({
|
|
800
|
+
entryPoints,
|
|
801
|
+
outdir: scriptsDir,
|
|
802
|
+
entryNames: "canopy-[name]",
|
|
803
|
+
chunkNames: "canopy-[name]-[hash]",
|
|
796
804
|
bundle: true,
|
|
805
|
+
platform: "browser",
|
|
806
|
+
format: "esm",
|
|
807
|
+
splitting: true,
|
|
797
808
|
sourcemap: false,
|
|
798
809
|
target: ["es2018"],
|
|
799
810
|
logLevel: "silent",
|
|
800
811
|
minify: true,
|
|
801
|
-
|
|
812
|
+
define: { "process.env.NODE_ENV": '"production"' },
|
|
813
|
+
plugins: [createReactShimPlugin(), createSliderCssInlinePlugin()],
|
|
802
814
|
});
|
|
815
|
+
renameAnonymousChunks(scriptsDir);
|
|
803
816
|
try {
|
|
804
|
-
const {logLine} = require("./log");
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
817
|
+
const { logLine } = require("./log");
|
|
818
|
+
["canopy-viewer.js", "canopy-slider.js"].forEach((file) => {
|
|
819
|
+
try {
|
|
820
|
+
const abs = path.join(scriptsDir, file);
|
|
821
|
+
const st = fs.statSync(abs);
|
|
822
|
+
const size = st && st.size ? st.size : 0;
|
|
823
|
+
const kb = size ? ` (${(size / 1024).toFixed(1)} KB)` : "";
|
|
824
|
+
const rel = path.relative(process.cwd(), abs).split(path.sep).join("/");
|
|
825
|
+
logLine(`✓ Wrote ${rel}${kb}`, "cyan");
|
|
826
|
+
} catch (_) {}
|
|
827
|
+
});
|
|
813
828
|
} catch (_) {}
|
|
814
829
|
}
|
|
815
830
|
|
|
831
|
+
async function ensureClientRuntime() {
|
|
832
|
+
if (!cloverRuntimePromise) {
|
|
833
|
+
cloverRuntimePromise = buildCloverHydrationRuntimes();
|
|
834
|
+
}
|
|
835
|
+
return cloverRuntimePromise;
|
|
836
|
+
}
|
|
837
|
+
|
|
816
838
|
// Facets runtime: fetches /api/search/facets.json, picks a value per label (random from top 3),
|
|
817
839
|
// and renders a Slider for each.
|
|
818
840
|
async function ensureFacetsRuntime() {
|
|
@@ -952,128 +974,8 @@ async function ensureFacetsRuntime() {
|
|
|
952
974
|
} catch (_) {}
|
|
953
975
|
}
|
|
954
976
|
|
|
955
|
-
// Bundle a separate client runtime for the Clover Slider to keep payloads split.
|
|
956
977
|
async function ensureSliderRuntime() {
|
|
957
|
-
|
|
958
|
-
try {
|
|
959
|
-
esbuild = require("../ui/node_modules/esbuild");
|
|
960
|
-
} catch (_) {
|
|
961
|
-
try {
|
|
962
|
-
esbuild = require("esbuild");
|
|
963
|
-
} catch (_) {}
|
|
964
|
-
}
|
|
965
|
-
if (!esbuild)
|
|
966
|
-
throw new Error(
|
|
967
|
-
"Slider runtime bundling requires esbuild. Install dependencies before building."
|
|
968
|
-
);
|
|
969
|
-
ensureDirSync(OUT_DIR);
|
|
970
|
-
const scriptsDir = path.join(OUT_DIR, "scripts");
|
|
971
|
-
ensureDirSync(scriptsDir);
|
|
972
|
-
const outFile = path.join(scriptsDir, "canopy-slider.js");
|
|
973
|
-
const entryFile = path.join(__dirname, "../components/slider-runtime-entry.js");
|
|
974
|
-
const reactShim = `
|
|
975
|
-
const React = (typeof window !== 'undefined' && window.React) || {};
|
|
976
|
-
export default React;
|
|
977
|
-
export const Children = React.Children;
|
|
978
|
-
export const Component = React.Component;
|
|
979
|
-
export const Fragment = React.Fragment;
|
|
980
|
-
export const createElement = React.createElement;
|
|
981
|
-
export const cloneElement = React.cloneElement;
|
|
982
|
-
export const createContext = React.createContext;
|
|
983
|
-
export const forwardRef = React.forwardRef;
|
|
984
|
-
export const memo = React.memo;
|
|
985
|
-
export const startTransition = React.startTransition;
|
|
986
|
-
export const isValidElement = React.isValidElement;
|
|
987
|
-
export const useEffect = React.useEffect;
|
|
988
|
-
export const useLayoutEffect = React.useLayoutEffect;
|
|
989
|
-
export const useMemo = React.useMemo;
|
|
990
|
-
export const useState = React.useState;
|
|
991
|
-
export const useRef = React.useRef;
|
|
992
|
-
export const useCallback = React.useCallback;
|
|
993
|
-
export const useContext = React.useContext;
|
|
994
|
-
export const useReducer = React.useReducer;
|
|
995
|
-
export const useId = React.useId;
|
|
996
|
-
`;
|
|
997
|
-
const rdomClientShim = `
|
|
998
|
-
const RDC = (typeof window !== 'undefined' && window.ReactDOMClient) || {};
|
|
999
|
-
export const createRoot = RDC.createRoot;
|
|
1000
|
-
export const hydrateRoot = RDC.hydrateRoot;
|
|
1001
|
-
`;
|
|
1002
|
-
const plugin = {
|
|
1003
|
-
name: "canopy-react-shims-slider",
|
|
1004
|
-
setup(build) {
|
|
1005
|
-
const ns = "canopy-shim";
|
|
1006
|
-
build.onResolve({filter: /^react$/}, () => ({
|
|
1007
|
-
path: "react",
|
|
1008
|
-
namespace: ns,
|
|
1009
|
-
}));
|
|
1010
|
-
build.onResolve({filter: /^react-dom$/}, () => ({
|
|
1011
|
-
path: "react-dom",
|
|
1012
|
-
namespace: ns,
|
|
1013
|
-
}));
|
|
1014
|
-
build.onResolve({filter: /^react-dom\/client$/}, () => ({
|
|
1015
|
-
path: "react-dom-client",
|
|
1016
|
-
namespace: ns,
|
|
1017
|
-
}));
|
|
1018
|
-
build.onLoad({filter: /^react$/, namespace: ns}, () => ({
|
|
1019
|
-
contents: reactShim,
|
|
1020
|
-
loader: "js",
|
|
1021
|
-
}));
|
|
1022
|
-
build.onLoad({filter: /^react-dom$/, namespace: ns}, () => ({
|
|
1023
|
-
contents:
|
|
1024
|
-
"export default ((typeof window!=='undefined' && window.ReactDOM) || {});",
|
|
1025
|
-
loader: "js",
|
|
1026
|
-
}));
|
|
1027
|
-
build.onLoad({filter: /^react-dom-client$/, namespace: ns}, () => ({
|
|
1028
|
-
contents: rdomClientShim,
|
|
1029
|
-
loader: "js",
|
|
1030
|
-
}));
|
|
1031
|
-
// Inline imported CSS into a <style> tag at runtime so we don't need a separate CSS file
|
|
1032
|
-
build.onLoad({filter: /\.css$/}, (args) => {
|
|
1033
|
-
const fs = require("fs");
|
|
1034
|
-
let css = "";
|
|
1035
|
-
try {
|
|
1036
|
-
css = fs.readFileSync(args.path, "utf8");
|
|
1037
|
-
} catch (_) {
|
|
1038
|
-
css = "";
|
|
1039
|
-
}
|
|
1040
|
-
const js = [
|
|
1041
|
-
`var css = ${JSON.stringify(css)};`,
|
|
1042
|
-
`(function(){ try { var s = document.createElement('style'); s.setAttribute('data-canopy-slider-css',''); s.textContent = css; document.head.appendChild(s); } catch (e) {} })();`,
|
|
1043
|
-
`export default css;`,
|
|
1044
|
-
].join("\n");
|
|
1045
|
-
return {contents: js, loader: "js"};
|
|
1046
|
-
});
|
|
1047
|
-
},
|
|
1048
|
-
};
|
|
1049
|
-
try {
|
|
1050
|
-
await esbuild.build({
|
|
1051
|
-
entryPoints: [entryFile],
|
|
1052
|
-
outfile: outFile,
|
|
1053
|
-
platform: "browser",
|
|
1054
|
-
format: "iife",
|
|
1055
|
-
bundle: true,
|
|
1056
|
-
sourcemap: false,
|
|
1057
|
-
target: ["es2018"],
|
|
1058
|
-
logLevel: "silent",
|
|
1059
|
-
minify: true,
|
|
1060
|
-
plugins: [plugin],
|
|
1061
|
-
});
|
|
1062
|
-
} catch (e) {
|
|
1063
|
-
const message = e && e.message ? e.message : e;
|
|
1064
|
-
throw new Error(`Slider runtime build failed: ${message}`);
|
|
1065
|
-
}
|
|
1066
|
-
try {
|
|
1067
|
-
const {logLine} = require("./log");
|
|
1068
|
-
let size = 0;
|
|
1069
|
-
try {
|
|
1070
|
-
const st = fs.statSync(outFile);
|
|
1071
|
-
size = (st && st.size) || 0;
|
|
1072
|
-
} catch (_) {}
|
|
1073
|
-
const kb = size ? ` (${(size / 1024).toFixed(1)} KB)` : "";
|
|
1074
|
-
const rel = path.relative(process.cwd(), outFile).split(path.sep).join("/");
|
|
1075
|
-
logLine(`✓ Wrote ${rel}${kb}`, "cyan");
|
|
1076
|
-
} catch (_) {}
|
|
978
|
+
return ensureClientRuntime();
|
|
1077
979
|
}
|
|
1078
980
|
|
|
1079
981
|
// Build a small React globals vendor for client-side React pages.
|
|
@@ -1291,5 +1193,6 @@ module.exports = {
|
|
|
1291
1193
|
} catch (_) {}
|
|
1292
1194
|
APP_WRAPPER = null;
|
|
1293
1195
|
UI_COMPONENTS = null;
|
|
1196
|
+
cloverRuntimePromise = null;
|
|
1294
1197
|
},
|
|
1295
1198
|
};
|
package/lib/build/pages.js
CHANGED
|
@@ -203,13 +203,20 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}, source
|
|
|
203
203
|
try { const st = fs.statSync(runtimeAbs); rel += `?v=${Math.floor(st.mtimeMs || Date.now())}`; } catch (_) {}
|
|
204
204
|
searchFormRel = rel;
|
|
205
205
|
}
|
|
206
|
+
const moduleScriptRels = [];
|
|
207
|
+
if (viewerRel) moduleScriptRels.push(viewerRel);
|
|
208
|
+
if (sliderRel) moduleScriptRels.push(sliderRel);
|
|
209
|
+
const primaryClassicScripts = [];
|
|
210
|
+
if (heroRel) primaryClassicScripts.push(heroRel);
|
|
211
|
+
if (timelineRel) primaryClassicScripts.push(timelineRel);
|
|
212
|
+
if (facetsRel) primaryClassicScripts.push(facetsRel);
|
|
213
|
+
const secondaryClassicScripts = [];
|
|
214
|
+
if (searchFormRel) secondaryClassicScripts.push(searchFormRel);
|
|
206
215
|
let jsRel = null;
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
else if (sliderRel) jsRel = sliderRel;
|
|
212
|
-
else if (facetsRel) jsRel = facetsRel;
|
|
216
|
+
if (primaryClassicScripts.length) {
|
|
217
|
+
jsRel = primaryClassicScripts.shift();
|
|
218
|
+
}
|
|
219
|
+
const classicScriptRels = primaryClassicScripts.concat(secondaryClassicScripts);
|
|
213
220
|
const needsReact = !!(needsHydrateViewer || needsHydrateSlider || needsFacets || needsTimeline);
|
|
214
221
|
let vendorTag = '';
|
|
215
222
|
if (needsReact) {
|
|
@@ -227,12 +234,18 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}, source
|
|
|
227
234
|
} catch (_) {}
|
|
228
235
|
const headSegments = [head];
|
|
229
236
|
const extraScripts = [];
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
237
|
+
const pushClassicScript = (src) => {
|
|
238
|
+
if (!src || src === jsRel) return;
|
|
239
|
+
extraScripts.push(`<script defer src="${src}"></script>`);
|
|
240
|
+
};
|
|
241
|
+
const pushModuleScript = (src) => {
|
|
242
|
+
if (!src) return;
|
|
243
|
+
extraScripts.push(`<script type="module" src="${src}"></script>`);
|
|
244
|
+
};
|
|
245
|
+
classicScriptRels.forEach((src) => pushClassicScript(src));
|
|
246
|
+
if (moduleScriptRels.length) {
|
|
247
|
+
moduleScriptRels.forEach((src) => pushModuleScript(src));
|
|
248
|
+
}
|
|
236
249
|
const extraStyles = [];
|
|
237
250
|
if (heroCssRel) {
|
|
238
251
|
let rel = heroCssRel;
|
|
@@ -244,8 +257,9 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}, source
|
|
|
244
257
|
extraStyles.push(`<link rel="stylesheet" href="${rel}">`);
|
|
245
258
|
}
|
|
246
259
|
if (extraStyles.length) headSegments.push(extraStyles.join(''));
|
|
260
|
+
if (vendorTag) headSegments.push(vendorTag);
|
|
247
261
|
if (extraScripts.length) headSegments.push(extraScripts.join(''));
|
|
248
|
-
const headExtra = headSegments.join('')
|
|
262
|
+
const headExtra = headSegments.join('');
|
|
249
263
|
const typeForClass = resolvedType || 'page';
|
|
250
264
|
const bodyClass = canopyBodyClassForType(typeForClass);
|
|
251
265
|
const html = htmlShell({
|
package/lib/build/runtimes.js
CHANGED
|
@@ -4,7 +4,6 @@ const { fs, path, OUT_DIR, ensureDirSync } = require('../common');
|
|
|
4
4
|
async function prepareAllRuntimes() {
|
|
5
5
|
const mdx = require('./mdx');
|
|
6
6
|
try { await mdx.ensureClientRuntime(); } catch (_) {}
|
|
7
|
-
try { if (typeof mdx.ensureSliderRuntime === 'function') await mdx.ensureSliderRuntime(); } catch (_) {}
|
|
8
7
|
try { if (typeof mdx.ensureTimelineRuntime === 'function') await mdx.ensureTimelineRuntime(); } catch (_) {}
|
|
9
8
|
try { if (typeof mdx.ensureHeroRuntime === 'function') await mdx.ensureHeroRuntime(); } catch (_) {}
|
|
10
9
|
try { if (typeof mdx.ensureFacetsRuntime === 'function') await mdx.ensureFacetsRuntime(); } catch (_) {}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
|
-
import CloverSlider from '@samvera/clover-iiif/slider';
|
|
4
3
|
import 'swiper/css';
|
|
5
4
|
import 'swiper/css/navigation';
|
|
6
5
|
import 'swiper/css/pagination';
|
|
@@ -41,13 +40,29 @@ function withDefaults(rawProps) {
|
|
|
41
40
|
};
|
|
42
41
|
}
|
|
43
42
|
|
|
43
|
+
let cloverSliderPromise = null;
|
|
44
|
+
|
|
45
|
+
function loadCloverSlider() {
|
|
46
|
+
if (!cloverSliderPromise) {
|
|
47
|
+
cloverSliderPromise = import('@samvera/clover-iiif/slider')
|
|
48
|
+
.then((mod) => (mod && (mod.default || mod.Slider || mod)) || null)
|
|
49
|
+
.catch(() => null);
|
|
50
|
+
}
|
|
51
|
+
return cloverSliderPromise;
|
|
52
|
+
}
|
|
53
|
+
|
|
44
54
|
function mount(el) {
|
|
45
55
|
try {
|
|
46
56
|
if (!el || el.getAttribute('data-canopy-slider-mounted') === '1') return;
|
|
47
57
|
const props = withDefaults(parseProps(el));
|
|
48
58
|
const root = createRoot(el);
|
|
49
|
-
|
|
50
|
-
|
|
59
|
+
loadCloverSlider().then((Component) => {
|
|
60
|
+
try {
|
|
61
|
+
if (!Component) return;
|
|
62
|
+
root.render(React.createElement(Component, props));
|
|
63
|
+
el.setAttribute('data-canopy-slider-mounted', '1');
|
|
64
|
+
} catch (_) {}
|
|
65
|
+
});
|
|
51
66
|
} catch (_) {}
|
|
52
67
|
}
|
|
53
68
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
|
|
4
|
+
function ready(fn) {
|
|
5
|
+
if (typeof document === 'undefined') return;
|
|
6
|
+
if (document.readyState === 'loading') {
|
|
7
|
+
document.addEventListener('DOMContentLoaded', fn, { once: true });
|
|
8
|
+
} else {
|
|
9
|
+
fn();
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function parseProps(el) {
|
|
14
|
+
try {
|
|
15
|
+
const script = el.querySelector('script[type="application/json"]');
|
|
16
|
+
if (script) return JSON.parse(script.textContent || '{}');
|
|
17
|
+
const raw = el.getAttribute('data-props') || '{}';
|
|
18
|
+
return JSON.parse(raw);
|
|
19
|
+
} catch (_) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const componentLoaders = {
|
|
25
|
+
viewer: () => import('@samvera/clover-iiif/viewer'),
|
|
26
|
+
scroll: () => import('@samvera/clover-iiif/scroll'),
|
|
27
|
+
image: () => import('@samvera/clover-iiif/image'),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const componentCache = new Map();
|
|
31
|
+
|
|
32
|
+
function resolveComponent(key) {
|
|
33
|
+
if (!componentLoaders[key]) return Promise.resolve(null);
|
|
34
|
+
if (!componentCache.has(key)) {
|
|
35
|
+
const loader = componentLoaders[key];
|
|
36
|
+
componentCache.set(
|
|
37
|
+
key,
|
|
38
|
+
loader()
|
|
39
|
+
.then((mod) => {
|
|
40
|
+
if (!mod) return null;
|
|
41
|
+
return mod.default || mod.Viewer || mod.Scroll || mod.Image || mod;
|
|
42
|
+
})
|
|
43
|
+
.catch(() => null)
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return componentCache.get(key);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function mountAll(selector, key) {
|
|
50
|
+
try {
|
|
51
|
+
const nodes = document.querySelectorAll(selector);
|
|
52
|
+
if (!nodes || !nodes.length) return;
|
|
53
|
+
const rootApi = typeof createRoot === 'function' ? createRoot : null;
|
|
54
|
+
if (!React || !rootApi) return;
|
|
55
|
+
resolveComponent(key).then((Component) => {
|
|
56
|
+
if (!Component) return;
|
|
57
|
+
nodes.forEach((el) => {
|
|
58
|
+
try {
|
|
59
|
+
if (el.__canopyHydrated) return;
|
|
60
|
+
const props = parseProps(el);
|
|
61
|
+
const root = rootApi(el);
|
|
62
|
+
root.render(React.createElement(Component, props));
|
|
63
|
+
el.__canopyHydrated = true;
|
|
64
|
+
} catch (_) {}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
} catch (_) {}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
ready(() => {
|
|
71
|
+
mountAll('[data-canopy-viewer]', 'viewer');
|
|
72
|
+
mountAll('[data-canopy-scroll]', 'scroll');
|
|
73
|
+
mountAll('[data-canopy-image]', 'image');
|
|
74
|
+
});
|