@canopy-iiif/app 0.12.4 → 0.12.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 +15 -1
- package/lib/build/mdx.js +101 -0
- package/lib/build/pages.js +7 -1
- package/lib/build/runtimes.js +1 -0
- package/lib/components/timeline-runtime.js +80 -0
- package/lib/search/search-form-runtime.js +1 -1
- package/package.json +1 -1
- package/ui/dist/index.mjs +701 -67
- package/ui/dist/index.mjs.map +4 -4
- package/ui/dist/server.mjs +873 -61
- package/ui/dist/server.mjs.map +4 -4
- package/ui/styles/components/_timeline.scss +272 -0
- package/ui/styles/components/index.scss +1 -0
- package/ui/styles/index.css +265 -0
package/lib/build/iiif.js
CHANGED
|
@@ -1525,6 +1525,7 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1525
1525
|
body.includes("data-canopy-image");
|
|
1526
1526
|
const needsRelated = body.includes("data-canopy-related-items");
|
|
1527
1527
|
const needsHeroSlider = body.includes("data-canopy-hero-slider");
|
|
1528
|
+
const needsTimeline = body.includes("data-canopy-timeline");
|
|
1528
1529
|
const needsSearchForm = body.includes("data-canopy-search-form");
|
|
1529
1530
|
const needsHydrate =
|
|
1530
1531
|
body.includes("data-canopy-hydrate") ||
|
|
@@ -1550,6 +1551,15 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1550
1551
|
.split(path.sep)
|
|
1551
1552
|
.join("/")
|
|
1552
1553
|
: null;
|
|
1554
|
+
const timelineRel = needsTimeline
|
|
1555
|
+
? path
|
|
1556
|
+
.relative(
|
|
1557
|
+
path.dirname(outPath),
|
|
1558
|
+
path.join(OUT_DIR, "scripts", "canopy-timeline.js")
|
|
1559
|
+
)
|
|
1560
|
+
.split(path.sep)
|
|
1561
|
+
.join("/")
|
|
1562
|
+
: null;
|
|
1553
1563
|
const heroRel = needsHeroSlider
|
|
1554
1564
|
? path
|
|
1555
1565
|
.relative(
|
|
@@ -1581,12 +1591,14 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1581
1591
|
let jsRel = null;
|
|
1582
1592
|
if (needsHeroSlider && heroRel) jsRel = heroRel;
|
|
1583
1593
|
else if (needsRelated && sliderRel) jsRel = sliderRel;
|
|
1594
|
+
else if (needsTimeline && timelineRel) jsRel = timelineRel;
|
|
1584
1595
|
else if (viewerRel) jsRel = viewerRel;
|
|
1585
1596
|
|
|
1586
1597
|
const headSegments = [head];
|
|
1587
1598
|
const needsReact = !!(
|
|
1588
1599
|
needsHydrateViewer ||
|
|
1589
|
-
needsRelated
|
|
1600
|
+
needsRelated ||
|
|
1601
|
+
needsTimeline
|
|
1590
1602
|
);
|
|
1591
1603
|
let vendorTag = "";
|
|
1592
1604
|
if (needsReact) {
|
|
@@ -1612,6 +1624,8 @@ async function buildIiifCollectionPages(CONFIG) {
|
|
|
1612
1624
|
extraScripts.push(`<script defer src="${heroRel}"></script>`);
|
|
1613
1625
|
if (relatedRel && jsRel !== relatedRel)
|
|
1614
1626
|
extraScripts.push(`<script defer src="${relatedRel}"></script>`);
|
|
1627
|
+
if (timelineRel && jsRel !== timelineRel)
|
|
1628
|
+
extraScripts.push(`<script defer src="${timelineRel}"></script>`);
|
|
1615
1629
|
if (viewerRel && jsRel !== viewerRel)
|
|
1616
1630
|
extraScripts.push(`<script defer src="${viewerRel}"></script>`);
|
|
1617
1631
|
if (sliderRel && jsRel !== sliderRel)
|
package/lib/build/mdx.js
CHANGED
|
@@ -1167,6 +1167,106 @@ async function ensureHeroRuntime() {
|
|
|
1167
1167
|
} catch (_) {}
|
|
1168
1168
|
}
|
|
1169
1169
|
|
|
1170
|
+
async function ensureTimelineRuntime() {
|
|
1171
|
+
let esbuild = null;
|
|
1172
|
+
try {
|
|
1173
|
+
esbuild = require("../ui/node_modules/esbuild");
|
|
1174
|
+
} catch (_) {
|
|
1175
|
+
try {
|
|
1176
|
+
esbuild = require("esbuild");
|
|
1177
|
+
} catch (_) {}
|
|
1178
|
+
}
|
|
1179
|
+
if (!esbuild)
|
|
1180
|
+
throw new Error(
|
|
1181
|
+
"Timeline runtime bundling requires esbuild. Install dependencies before building."
|
|
1182
|
+
);
|
|
1183
|
+
ensureDirSync(OUT_DIR);
|
|
1184
|
+
const scriptsDir = path.join(OUT_DIR, "scripts");
|
|
1185
|
+
ensureDirSync(scriptsDir);
|
|
1186
|
+
const outFile = path.join(scriptsDir, "canopy-timeline.js");
|
|
1187
|
+
const entryFile = path.join(
|
|
1188
|
+
__dirname,
|
|
1189
|
+
"..",
|
|
1190
|
+
"components",
|
|
1191
|
+
"timeline-runtime.js"
|
|
1192
|
+
);
|
|
1193
|
+
const reactShim = `
|
|
1194
|
+
const React = (typeof window !== 'undefined' && window.React) || {};
|
|
1195
|
+
export default React;
|
|
1196
|
+
export const Children = React.Children;
|
|
1197
|
+
export const Component = React.Component;
|
|
1198
|
+
export const Fragment = React.Fragment;
|
|
1199
|
+
export const createElement = React.createElement;
|
|
1200
|
+
export const cloneElement = React.cloneElement;
|
|
1201
|
+
export const createContext = React.createContext;
|
|
1202
|
+
export const forwardRef = React.forwardRef;
|
|
1203
|
+
export const memo = React.memo;
|
|
1204
|
+
export const startTransition = React.startTransition;
|
|
1205
|
+
export const isValidElement = React.isValidElement;
|
|
1206
|
+
export const useEffect = React.useEffect;
|
|
1207
|
+
export const useLayoutEffect = React.useLayoutEffect;
|
|
1208
|
+
export const useMemo = React.useMemo;
|
|
1209
|
+
export const useState = React.useState;
|
|
1210
|
+
export const useRef = React.useRef;
|
|
1211
|
+
export const useCallback = React.useCallback;
|
|
1212
|
+
export const useContext = React.useContext;
|
|
1213
|
+
export const useReducer = React.useReducer;
|
|
1214
|
+
export const useId = React.useId;
|
|
1215
|
+
`;
|
|
1216
|
+
const rdomClientShim = `
|
|
1217
|
+
const RDC = (typeof window !== 'undefined' && window.ReactDOMClient) || {};
|
|
1218
|
+
export const createRoot = RDC.createRoot;
|
|
1219
|
+
export const hydrateRoot = RDC.hydrateRoot;
|
|
1220
|
+
`;
|
|
1221
|
+
const plugin = {
|
|
1222
|
+
name: "canopy-react-shims-timeline",
|
|
1223
|
+
setup(build) {
|
|
1224
|
+
const ns = "canopy-timeline-shim";
|
|
1225
|
+
build.onResolve({filter: /^react$/}, () => ({path: "react", namespace: ns}));
|
|
1226
|
+
build.onResolve({filter: /^react-dom$/}, () => ({path: "react-dom", namespace: ns}));
|
|
1227
|
+
build.onResolve({filter: /^react-dom\/client$/}, () => ({
|
|
1228
|
+
path: "react-dom-client",
|
|
1229
|
+
namespace: ns,
|
|
1230
|
+
}));
|
|
1231
|
+
build.onLoad({filter: /^react$/, namespace: ns}, () => ({
|
|
1232
|
+
contents: reactShim,
|
|
1233
|
+
loader: "js",
|
|
1234
|
+
}));
|
|
1235
|
+
build.onLoad({filter: /^react-dom$/, namespace: ns}, () => ({
|
|
1236
|
+
contents: "export default (typeof window !== 'undefined' && window.ReactDOM) || {};",
|
|
1237
|
+
loader: "js",
|
|
1238
|
+
}));
|
|
1239
|
+
build.onLoad({filter: /^react-dom-client$/, namespace: ns}, () => ({
|
|
1240
|
+
contents: rdomClientShim,
|
|
1241
|
+
loader: "js",
|
|
1242
|
+
}));
|
|
1243
|
+
},
|
|
1244
|
+
};
|
|
1245
|
+
await esbuild.build({
|
|
1246
|
+
entryPoints: [entryFile],
|
|
1247
|
+
outfile: outFile,
|
|
1248
|
+
platform: "browser",
|
|
1249
|
+
format: "iife",
|
|
1250
|
+
bundle: true,
|
|
1251
|
+
sourcemap: false,
|
|
1252
|
+
target: ["es2018"],
|
|
1253
|
+
logLevel: "silent",
|
|
1254
|
+
minify: true,
|
|
1255
|
+
plugins: [plugin],
|
|
1256
|
+
});
|
|
1257
|
+
try {
|
|
1258
|
+
const {logLine} = require("./log");
|
|
1259
|
+
let size = 0;
|
|
1260
|
+
try {
|
|
1261
|
+
const st = fs.statSync(outFile);
|
|
1262
|
+
size = (st && st.size) || 0;
|
|
1263
|
+
} catch (_) {}
|
|
1264
|
+
const kb = size ? ` (${(size / 1024).toFixed(1)} KB)` : "";
|
|
1265
|
+
const rel = path.relative(process.cwd(), outFile).split(path.sep).join("/");
|
|
1266
|
+
logLine(`✓ Wrote ${rel}${kb}`, "cyan");
|
|
1267
|
+
} catch (_) {}
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1170
1270
|
module.exports = {
|
|
1171
1271
|
extractTitle,
|
|
1172
1272
|
extractHeadings,
|
|
@@ -1181,6 +1281,7 @@ module.exports = {
|
|
|
1181
1281
|
loadAppWrapper,
|
|
1182
1282
|
ensureClientRuntime,
|
|
1183
1283
|
ensureSliderRuntime,
|
|
1284
|
+
ensureTimelineRuntime,
|
|
1184
1285
|
ensureHeroRuntime,
|
|
1185
1286
|
ensureFacetsRuntime,
|
|
1186
1287
|
ensureReactGlobals,
|
package/lib/build/pages.js
CHANGED
|
@@ -175,6 +175,7 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}, source
|
|
|
175
175
|
body.includes('data-canopy-image');
|
|
176
176
|
const needsHydrateSlider = body.includes('data-canopy-slider');
|
|
177
177
|
const needsHeroSlider = body.includes('data-canopy-hero-slider');
|
|
178
|
+
const needsTimeline = body.includes('data-canopy-timeline');
|
|
178
179
|
const needsSearchForm = true; // search form runtime is global
|
|
179
180
|
const needsFacets = body.includes('data-canopy-related-items');
|
|
180
181
|
const viewerRel = needsHydrateViewer
|
|
@@ -189,6 +190,9 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}, source
|
|
|
189
190
|
const heroCssRel = needsHeroSlider
|
|
190
191
|
? path.relative(path.dirname(outPath), path.join(OUT_DIR, 'scripts', 'canopy-hero-slider.css')).split(path.sep).join('/')
|
|
191
192
|
: null;
|
|
193
|
+
const timelineRel = needsTimeline
|
|
194
|
+
? path.relative(path.dirname(outPath), path.join(OUT_DIR, 'scripts', 'canopy-timeline.js')).split(path.sep).join('/')
|
|
195
|
+
: null;
|
|
192
196
|
const facetsRel = needsFacets
|
|
193
197
|
? path.relative(path.dirname(outPath), path.join(OUT_DIR, 'scripts', 'canopy-related-items.js')).split(path.sep).join('/')
|
|
194
198
|
: null;
|
|
@@ -202,10 +206,11 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}, source
|
|
|
202
206
|
let jsRel = null;
|
|
203
207
|
if (needsHeroSlider && heroRel) jsRel = heroRel;
|
|
204
208
|
else if (needsFacets && sliderRel) jsRel = sliderRel;
|
|
209
|
+
else if (needsTimeline && timelineRel) jsRel = timelineRel;
|
|
205
210
|
else if (viewerRel) jsRel = viewerRel;
|
|
206
211
|
else if (sliderRel) jsRel = sliderRel;
|
|
207
212
|
else if (facetsRel) jsRel = facetsRel;
|
|
208
|
-
const needsReact = !!(needsHydrateViewer || needsHydrateSlider || needsFacets);
|
|
213
|
+
const needsReact = !!(needsHydrateViewer || needsHydrateSlider || needsFacets || needsTimeline);
|
|
209
214
|
let vendorTag = '';
|
|
210
215
|
if (needsReact) {
|
|
211
216
|
try {
|
|
@@ -223,6 +228,7 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}, source
|
|
|
223
228
|
const headSegments = [head];
|
|
224
229
|
const extraScripts = [];
|
|
225
230
|
if (heroRel && jsRel !== heroRel) extraScripts.push(`<script defer src="${heroRel}"></script>`);
|
|
231
|
+
if (timelineRel && jsRel !== timelineRel) extraScripts.push(`<script defer src="${timelineRel}"></script>`);
|
|
226
232
|
if (facetsRel && jsRel !== facetsRel) extraScripts.push(`<script defer src="${facetsRel}"></script>`);
|
|
227
233
|
if (viewerRel && jsRel !== viewerRel) extraScripts.push(`<script defer src="${viewerRel}"></script>`);
|
|
228
234
|
if (sliderRel && jsRel !== sliderRel) extraScripts.push(`<script defer src="${sliderRel}"></script>`);
|
package/lib/build/runtimes.js
CHANGED
|
@@ -5,6 +5,7 @@ async function prepareAllRuntimes() {
|
|
|
5
5
|
const mdx = require('./mdx');
|
|
6
6
|
try { await mdx.ensureClientRuntime(); } catch (_) {}
|
|
7
7
|
try { if (typeof mdx.ensureSliderRuntime === 'function') await mdx.ensureSliderRuntime(); } catch (_) {}
|
|
8
|
+
try { if (typeof mdx.ensureTimelineRuntime === 'function') await mdx.ensureTimelineRuntime(); } catch (_) {}
|
|
8
9
|
try { if (typeof mdx.ensureHeroRuntime === 'function') await mdx.ensureHeroRuntime(); } catch (_) {}
|
|
9
10
|
try { if (typeof mdx.ensureFacetsRuntime === 'function') await mdx.ensureFacetsRuntime(); } catch (_) {}
|
|
10
11
|
try { if (typeof mdx.ensureReactGlobals === 'function') await mdx.ensureReactGlobals(); } catch (_) {}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createRoot, hydrateRoot } from 'react-dom/client';
|
|
3
|
+
import Timeline from '../../ui/src/content/timeline/Timeline.jsx';
|
|
4
|
+
|
|
5
|
+
function ready(fn) {
|
|
6
|
+
if (typeof document === 'undefined') return;
|
|
7
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', fn, { once: true });
|
|
8
|
+
else fn();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function parseProps(el) {
|
|
12
|
+
try {
|
|
13
|
+
const script = el.querySelector('script[type="application/json"]');
|
|
14
|
+
if (script) return JSON.parse(script.textContent || '{}');
|
|
15
|
+
const raw = el.getAttribute('data-props') || '{}';
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
} catch (_) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function mount(el) {
|
|
23
|
+
try {
|
|
24
|
+
if (!el || el.getAttribute('data-canopy-timeline-mounted') === '1') return;
|
|
25
|
+
const props = parseProps(el);
|
|
26
|
+
const dataNode = el.querySelector('script[type="application/json"]');
|
|
27
|
+
if (dataNode && dataNode.parentNode === el) {
|
|
28
|
+
dataNode.parentNode.removeChild(dataNode);
|
|
29
|
+
}
|
|
30
|
+
if (el.childElementCount > 0) {
|
|
31
|
+
hydrateRoot(el, React.createElement(Timeline, props));
|
|
32
|
+
} else {
|
|
33
|
+
const root = createRoot(el);
|
|
34
|
+
root.render(React.createElement(Timeline, props));
|
|
35
|
+
}
|
|
36
|
+
el.setAttribute('data-canopy-timeline-mounted', '1');
|
|
37
|
+
} catch (error) {
|
|
38
|
+
try {
|
|
39
|
+
console.warn('[canopy][timeline] failed to mount timeline', error);
|
|
40
|
+
} catch (_) {}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function scan() {
|
|
45
|
+
try {
|
|
46
|
+
document
|
|
47
|
+
.querySelectorAll('[data-canopy-timeline]:not([data-canopy-timeline-mounted="1"])')
|
|
48
|
+
.forEach(mount);
|
|
49
|
+
} catch (_) {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function observe() {
|
|
53
|
+
try {
|
|
54
|
+
const obs = new MutationObserver((mutations) => {
|
|
55
|
+
const toMount = [];
|
|
56
|
+
mutations.forEach((mutation) => {
|
|
57
|
+
mutation.addedNodes &&
|
|
58
|
+
mutation.addedNodes.forEach((node) => {
|
|
59
|
+
if (!(node instanceof Element)) return;
|
|
60
|
+
if (node.matches && node.matches('[data-canopy-timeline]')) toMount.push(node);
|
|
61
|
+
const inner = node.querySelectorAll
|
|
62
|
+
? node.querySelectorAll('[data-canopy-timeline]')
|
|
63
|
+
: [];
|
|
64
|
+
inner && inner.forEach && inner.forEach((el) => toMount.push(el));
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
if (toMount.length) Promise.resolve().then(() => toMount.forEach(mount));
|
|
68
|
+
});
|
|
69
|
+
obs.observe(document.documentElement || document.body, {
|
|
70
|
+
childList: true,
|
|
71
|
+
subtree: true,
|
|
72
|
+
});
|
|
73
|
+
} catch (_) {}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
ready(() => {
|
|
77
|
+
if (typeof document === 'undefined') return;
|
|
78
|
+
scan();
|
|
79
|
+
observe();
|
|
80
|
+
});
|
|
@@ -234,7 +234,7 @@ function renderList(list, records, groupOrder) {
|
|
|
234
234
|
item.setAttribute('data-canopy-item', '');
|
|
235
235
|
item.href = href;
|
|
236
236
|
item.tabIndex = 0;
|
|
237
|
-
item.className = 'canopy-card canopy-card--teaser canopy-search-teaser__item';
|
|
237
|
+
item.className = 'canopy-card canopy-card--teaser canopy-search-teaser__item canopy-teaser-card';
|
|
238
238
|
|
|
239
239
|
const showThumb = String(record && record.type || '') === 'work' && record && record.thumbnail;
|
|
240
240
|
if (showThumb) {
|