@canopy-iiif/app 0.12.4 → 0.12.6

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
@@ -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,
@@ -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>`);
@@ -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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-iiif/app",
3
- "version": "0.12.4",
3
+ "version": "0.12.6",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",