@canopy-iiif/app 0.10.24 → 0.10.27

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/mdx.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const React = require("react");
2
2
  const ReactDOMServer = require("react-dom/server");
3
- const { pathToFileURL } = require("url");
3
+ const {pathToFileURL} = require("url");
4
4
  const {
5
5
  fs,
6
6
  fsp,
@@ -22,7 +22,10 @@ try {
22
22
 
23
23
  const EXTRA_REMARK_PLUGINS = (() => {
24
24
  try {
25
- const absPath = path.resolve(process.cwd(), "packages/helpers/docs/remark-code-meta.js");
25
+ const absPath = path.resolve(
26
+ process.cwd(),
27
+ "packages/helpers/docs/remark-code-meta.js"
28
+ );
26
29
  if (fs.existsSync(absPath)) {
27
30
  const plugin = require(absPath);
28
31
  if (typeof plugin === "function") return [plugin];
@@ -53,13 +56,13 @@ function buildCompileOptions(overrides = {}) {
53
56
  base.remarkPlugins = remarkPlugins;
54
57
  }
55
58
  if (overrides && typeof overrides === "object") {
56
- const { remarkPlugins: _omit, ...rest } = overrides;
59
+ const {remarkPlugins: _omit, ...rest} = overrides;
57
60
  Object.assign(base, rest);
58
61
  }
59
62
  return base;
60
63
  }
61
64
  const yaml = require("js-yaml");
62
- const { getPageContext } = require("../page-context");
65
+ const {getPageContext} = require("../page-context");
63
66
 
64
67
  function parseFrontmatter(src) {
65
68
  let input = String(src || "");
@@ -67,7 +70,7 @@ function parseFrontmatter(src) {
67
70
  if (input.charCodeAt(0) === 0xfeff) input = input.slice(1);
68
71
  // Allow a few leading blank lines before frontmatter
69
72
  const m = input.match(/^(?:\s*\r?\n)*---\s*\r?\n([\s\S]*?)\r?\n---\s*\r?\n?/);
70
- if (!m) return { data: null, content: input };
73
+ if (!m) return {data: null, content: input};
71
74
  let data = null;
72
75
  try {
73
76
  data = yaml.load(m[1]) || null;
@@ -75,7 +78,7 @@ function parseFrontmatter(src) {
75
78
  data = null;
76
79
  }
77
80
  const content = input.slice(m[0].length);
78
- return { data, content };
81
+ return {data, content};
79
82
  }
80
83
 
81
84
  // ESM-only in v3; load dynamically from CJS
@@ -94,130 +97,201 @@ async function getMdxProvider() {
94
97
  // Lazily load UI components from the workspace package and cache them.
95
98
  // Re-import when the built UI server bundle changes on disk.
96
99
  let UI_COMPONENTS = null;
97
- let UI_COMPONENTS_PATH = '';
100
+ let UI_COMPONENTS_PATH = "";
98
101
  let UI_COMPONENTS_MTIME = 0;
99
- const DEBUG = process.env.CANOPY_DEBUG === '1' || process.env.CANOPY_DEBUG === 'true';
102
+ const DEBUG =
103
+ process.env.CANOPY_DEBUG === "1" || process.env.CANOPY_DEBUG === "true";
100
104
  async function loadUiComponents() {
101
105
  // Do not rely on a cached mapping; re-import each time to avoid transient races.
102
106
  try {
103
107
  // Prefer the workspace dist path during dev to avoid export-map resolution issues
104
108
  let resolved = null;
105
109
  try {
106
- const wsDist = path.join(process.cwd(), 'packages', 'app', 'ui', 'dist', 'server.mjs');
110
+ const wsDist = path.join(
111
+ process.cwd(),
112
+ "packages",
113
+ "app",
114
+ "ui",
115
+ "dist",
116
+ "server.mjs"
117
+ );
107
118
  if (fs.existsSync(wsDist)) resolved = wsDist;
108
119
  } catch (_) {}
109
120
  // Prefer explicit dist path to avoid export-map issues
110
121
  if (!resolved) {
111
- try { resolved = require.resolve("@canopy-iiif/app/ui/dist/server.mjs"); } catch (_) {
112
- try { resolved = require.resolve("@canopy-iiif/app/ui/server"); } catch (_) { resolved = null; }
122
+ try {
123
+ resolved = require.resolve("@canopy-iiif/app/ui/dist/server.mjs");
124
+ } catch (_) {
125
+ try {
126
+ resolved = require.resolve("@canopy-iiif/app/ui/server");
127
+ } catch (_) {
128
+ resolved = null;
129
+ }
113
130
  }
114
131
  }
115
132
  // Determine current mtime for change detection
116
- let currentPath = resolved || '';
133
+ let currentPath = resolved || "";
117
134
  let currentMtime = 0;
118
135
  if (currentPath) {
119
- try { const st = fs.statSync(currentPath); currentMtime = Math.floor(st.mtimeMs || 0); } catch (_) { currentMtime = 0; }
136
+ try {
137
+ const st = fs.statSync(currentPath);
138
+ currentMtime = Math.floor(st.mtimeMs || 0);
139
+ } catch (_) {
140
+ currentMtime = 0;
141
+ }
120
142
  }
121
143
  // If we have a cached module and the path/mtime have not changed, return cached
122
- if (UI_COMPONENTS && UI_COMPONENTS_PATH === currentPath && UI_COMPONENTS_MTIME === currentMtime) {
144
+ if (
145
+ UI_COMPONENTS &&
146
+ UI_COMPONENTS_PATH === currentPath &&
147
+ UI_COMPONENTS_MTIME === currentMtime
148
+ ) {
123
149
  if (DEBUG) {
124
- try { console.log('[canopy][mdx] UI components cache hit:', { path: UI_COMPONENTS_PATH, mtime: UI_COMPONENTS_MTIME }); } catch(_){}
150
+ try {
151
+ console.log("[canopy][mdx] UI components cache hit:", {
152
+ path: UI_COMPONENTS_PATH,
153
+ mtime: UI_COMPONENTS_MTIME,
154
+ });
155
+ } catch (_) {}
125
156
  }
126
157
  return UI_COMPONENTS;
127
158
  }
128
159
  let mod = null;
129
160
  let importErr = null;
130
161
  if (resolved) {
131
- const { pathToFileURL } = require("url");
162
+ const {pathToFileURL} = require("url");
132
163
  const fileUrl = pathToFileURL(resolved).href;
133
164
  const attempts = 5;
134
165
  for (let i = 0; i < attempts && !mod; i++) {
135
- const bustVal = currentMtime ? `${currentMtime}-${i}` : `${Date.now()}-${i}`;
166
+ const bustVal = currentMtime
167
+ ? `${currentMtime}-${i}`
168
+ : `${Date.now()}-${i}`;
136
169
  try {
137
170
  mod = await import(fileUrl + `?v=${bustVal}`);
138
171
  } catch (e) {
139
172
  importErr = e;
140
173
  if (DEBUG) {
141
- try { console.warn('[canopy][mdx] ESM import failed for', resolved, '(attempt', i + 1, 'of', attempts + ')\n', e && (e.stack || e.message || String(e))); } catch(_){}
174
+ try {
175
+ console.warn(
176
+ "[canopy][mdx] ESM import failed for",
177
+ resolved,
178
+ "(attempt",
179
+ i + 1,
180
+ "of",
181
+ attempts + ")\n",
182
+ e && (e.stack || e.message || String(e))
183
+ );
184
+ } catch (_) {}
142
185
  }
143
186
  // Small delay to avoid watch-write race
144
187
  await new Promise((r) => setTimeout(r, 60));
145
188
  }
146
189
  }
147
190
  if (DEBUG) {
148
- try { console.log('[canopy][mdx] UI components resolved', { path: resolved, mtime: currentMtime, loaded: !!mod }); } catch(_){}
191
+ try {
192
+ console.log("[canopy][mdx] UI components resolved", {
193
+ path: resolved,
194
+ mtime: currentMtime,
195
+ loaded: !!mod,
196
+ });
197
+ } catch (_) {}
149
198
  }
150
199
  }
151
200
  if (!mod) {
152
201
  // Try package subpath as a secondary resolution path to avoid export-map issues
153
202
  try {
154
- mod = await import('@canopy-iiif/app/ui/server');
203
+ mod = await import("@canopy-iiif/app/ui/server");
155
204
  } catch (e2) {
156
205
  const msgA = importErr && (importErr.stack || importErr.message);
157
206
  const msgB = e2 && (e2.stack || e2.message);
158
- throw new Error('Failed to load @canopy-iiif/app/ui/server. Ensure the UI package is built.\nPath import error: ' + (msgA||'') + '\nExport-map import error: ' + (msgB||''));
207
+ throw new Error(
208
+ "Failed to load @canopy-iiif/app/ui/server. Ensure the UI package is built.\nPath import error: " +
209
+ (msgA || "") +
210
+ "\nExport-map import error: " +
211
+ (msgB || "")
212
+ );
159
213
  }
160
214
  }
161
- let comp = (mod && typeof mod === 'object') ? mod : {};
215
+ let comp = mod && typeof mod === "object" ? mod : {};
162
216
  // Hard-require core exports; do not inject fallbacks
163
- const required = ['SearchPanel', 'SearchFormModal', 'SearchResults', 'SearchSummary', 'SearchTabs', 'Viewer', 'Slider', 'RelatedItems', 'Interstitials'];
217
+ const required = [
218
+ "SearchPanel",
219
+ "SearchFormModal",
220
+ "SearchResults",
221
+ "SearchSummary",
222
+ "SearchTabs",
223
+ "Viewer",
224
+ "Slider",
225
+ "RelatedItems",
226
+ "Interstitials",
227
+ ];
164
228
  const missing = required.filter((k) => !comp || !comp[k]);
165
229
  if (missing.length) {
166
- throw new Error('[canopy][mdx] Missing UI exports: ' + missing.join(', '));
230
+ throw new Error(
231
+ "[canopy][mdx] Missing UI exports: " + missing.join(", ")
232
+ );
167
233
  }
168
234
  if (DEBUG) {
169
- try { console.log('[canopy][mdx] UI component sources', {
170
- path: currentPath,
171
- mtime: currentMtime,
172
- hasServerExport: !!mod,
173
- hasWorkspace: typeof comp !== 'undefined',
174
- SearchFormModal: !!comp.SearchFormModal,
175
- Viewer: !!comp.Viewer,
176
- Slider: !!comp.Slider,
177
- }); } catch(_){}
235
+ try {
236
+ console.log("[canopy][mdx] UI component sources", {
237
+ path: currentPath,
238
+ mtime: currentMtime,
239
+ hasServerExport: !!mod,
240
+ hasWorkspace: typeof comp !== "undefined",
241
+ SearchFormModal: !!comp.SearchFormModal,
242
+ Viewer: !!comp.Viewer,
243
+ Slider: !!comp.Slider,
244
+ });
245
+ } catch (_) {}
178
246
  }
179
247
  UI_COMPONENTS = comp;
180
248
  UI_COMPONENTS_PATH = currentPath;
181
249
  UI_COMPONENTS_MTIME = currentMtime;
182
250
  } catch (e) {
183
- const msg = e && (e.stack || e.message || String(e)) || 'unknown error';
184
- throw new Error('[canopy][mdx] Failed to load UI components (no fallbacks): ' + msg);
251
+ const msg = (e && (e.stack || e.message || String(e))) || "unknown error";
252
+ throw new Error(
253
+ "[canopy][mdx] Failed to load UI components (no fallbacks): " + msg
254
+ );
185
255
  }
186
256
  return UI_COMPONENTS;
187
257
  }
188
258
 
189
259
  function slugifyHeading(text) {
190
260
  try {
191
- return String(text || '')
261
+ return String(text || "")
192
262
  .toLowerCase()
193
263
  .trim()
194
- .replace(/[^a-z0-9\s-]/g, '')
195
- .replace(/\s+/g, '-');
264
+ .replace(/[^a-z0-9\s-]/g, "")
265
+ .replace(/\s+/g, "-");
196
266
  } catch (_) {
197
- return '';
267
+ return "";
198
268
  }
199
269
  }
200
270
 
201
271
  function extractHeadings(mdxSource) {
202
- const { content } = parseFrontmatter(String(mdxSource || ''));
203
- const cleaned = content.replace(/```[\s\S]*?```/g, '');
272
+ const {content} = parseFrontmatter(String(mdxSource || ""));
273
+ const cleaned = content.replace(/```[\s\S]*?```/g, "");
204
274
  const headingRegex = /^ {0,3}(#{1,6})\s+(.+?)\s*$/gm;
205
275
  const seen = new Map();
206
276
  const headings = [];
207
277
  let match;
208
278
  while ((match = headingRegex.exec(cleaned))) {
209
- const hashes = match[1] || '';
279
+ const hashes = match[1] || "";
210
280
  const depth = hashes.length;
211
- let raw = match[2] || '';
281
+ let raw = match[2] || "";
212
282
  let explicitId = null;
213
283
  const idMatch = raw.match(/\s*\{#([A-Za-z0-9_-]+)\}\s*$/);
214
284
  if (idMatch) {
215
285
  explicitId = idMatch[1];
216
286
  raw = raw.slice(0, raw.length - idMatch[0].length);
217
287
  }
218
- const title = raw.replace(/\<[^>]*\>/g, '').replace(/\[([^\]]+)\]\([^)]*\)/g, '$1').trim();
288
+ const title = raw
289
+ .replace(/\<[^>]*\>/g, "")
290
+ .replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
291
+ .trim();
219
292
  if (!title) continue;
220
- const baseId = explicitId || slugifyHeading(title) || `section-${headings.length + 1}`;
293
+ const baseId =
294
+ explicitId || slugifyHeading(title) || `section-${headings.length + 1}`;
221
295
  const count = seen.get(baseId) || 0;
222
296
  seen.set(baseId, count + 1);
223
297
  const id = count === 0 ? baseId : `${baseId}-${count + 1}`;
@@ -231,39 +305,39 @@ function extractHeadings(mdxSource) {
231
305
  }
232
306
 
233
307
  function extractPlainText(mdxSource) {
234
- let { content } = parseFrontmatter(String(mdxSource || ''));
235
- if (!content) return '';
236
- content = content.replace(/```[\s\S]*?```/g, ' ');
237
- content = content.replace(/`{1,3}([^`]+)`{1,3}/g, '$1');
238
- content = content.replace(/!\[[^\]]*\]\([^)]*\)/g, ' ');
239
- content = content.replace(/\[[^\]]*\]\([^)]*\)/g, '$1');
240
- content = content.replace(/<[^>]+>/g, ' ');
241
- content = content.replace(/\{#([^}]+)\}/g, ' ');
242
- content = content.replace(/\{\/[A-Za-z0-9_.-]+\}/g, ' ');
243
- content = content.replace(/\{[^{}]*\}/g, ' ');
244
- content = content.replace(/[#>*~_\-]+/g, ' ');
245
- content = content.replace(/\n+/g, ' ');
246
- content = content.replace(/\s+/g, ' ').trim();
308
+ let {content} = parseFrontmatter(String(mdxSource || ""));
309
+ if (!content) return "";
310
+ content = content.replace(/```[\s\S]*?```/g, " ");
311
+ content = content.replace(/`{1,3}([^`]+)`{1,3}/g, "$1");
312
+ content = content.replace(/!\[[^\]]*\]\([^)]*\)/g, " ");
313
+ content = content.replace(/\[[^\]]*\]\([^)]*\)/g, "$1");
314
+ content = content.replace(/<[^>]+>/g, " ");
315
+ content = content.replace(/\{#([^}]+)\}/g, " ");
316
+ content = content.replace(/\{\/[A-Za-z0-9_.-]+\}/g, " ");
317
+ content = content.replace(/\{[^{}]*\}/g, " ");
318
+ content = content.replace(/[#>*~_\-]+/g, " ");
319
+ content = content.replace(/\n+/g, " ");
320
+ content = content.replace(/\s+/g, " ").trim();
247
321
  return content;
248
322
  }
249
323
 
250
324
  function extractMarkdownSummary(mdxSource) {
251
- let { content } = parseFrontmatter(String(mdxSource || ''));
252
- if (!content) return '';
253
- content = content.replace(/^import[^\n]+$/gm, ' ');
254
- content = content.replace(/^export[^\n]+$/gm, ' ');
255
- content = content.replace(/```[\s\S]*?```/g, ' ');
256
- content = content.replace(/<[A-Za-z][^>]*?>[\s\S]*?<\/[A-Za-z][^>]*?>/g, ' ');
257
- content = content.replace(/<[A-Za-z][^>]*?\/>/g, ' ');
258
- content = content.replace(/\{\/[A-Za-z0-9_.-]+\}/g, ' ');
259
- content = content.replace(/\{[^{}]*\}/g, ' ');
260
- content = content.replace(/^#{1,6}\s+.*$/gm, ' ');
261
- content = content.replace(/\s+/g, ' ').trim();
325
+ let {content} = parseFrontmatter(String(mdxSource || ""));
326
+ if (!content) return "";
327
+ content = content.replace(/^import[^\n]+$/gm, " ");
328
+ content = content.replace(/^export[^\n]+$/gm, " ");
329
+ content = content.replace(/```[\s\S]*?```/g, " ");
330
+ content = content.replace(/<[A-Za-z][^>]*?>[\s\S]*?<\/[A-Za-z][^>]*?>/g, " ");
331
+ content = content.replace(/<[A-Za-z][^>]*?\/>/g, " ");
332
+ content = content.replace(/\{\/[A-Za-z0-9_.-]+\}/g, " ");
333
+ content = content.replace(/\{[^{}]*\}/g, " ");
334
+ content = content.replace(/^#{1,6}\s+.*$/gm, " ");
335
+ content = content.replace(/\s+/g, " ").trim();
262
336
  return content;
263
337
  }
264
338
 
265
339
  function extractTitle(mdxSource) {
266
- const { data, content } = parseFrontmatter(String(mdxSource || ""));
340
+ const {data, content} = parseFrontmatter(String(mdxSource || ""));
267
341
  if (data && typeof data.title === "string" && data.title.trim()) {
268
342
  return data.title.trim();
269
343
  }
@@ -314,9 +388,9 @@ async function loadAppWrapper() {
314
388
  // Keep missing _app as a build-time error as specified
315
389
  throw new Error("Missing required file: content/_app.mdx");
316
390
  }
317
- const { compile } = await import("@mdx-js/mdx");
391
+ const {compile} = await import("@mdx-js/mdx");
318
392
  const raw = await fsp.readFile(appPath, "utf8");
319
- const { content: source } = parseFrontmatter(raw);
393
+ const {content: source} = parseFrontmatter(raw);
320
394
  let code = String(await compile(source, buildCompileOptions()));
321
395
  // MDX v3 default export (MDXContent) does not forward external children.
322
396
  // When present, expose the underlying layout function as __MDXLayout for wrapping.
@@ -338,10 +412,14 @@ async function loadAppWrapper() {
338
412
  // Try to render the probe inside an MDXProvider with UI server components
339
413
  const components = await loadUiComponents();
340
414
  const MDXProvider = await getMdxProvider();
341
- const probeChild = React.createElement("span", { "data-canopy-probe": "1" });
342
- const probeTree = React.createElement(App || (() => null), null, probeChild);
415
+ const probeChild = React.createElement("span", {"data-canopy-probe": "1"});
416
+ const probeTree = React.createElement(
417
+ App || (() => null),
418
+ null,
419
+ probeChild
420
+ );
343
421
  const probe = MDXProvider
344
- ? React.createElement(MDXProvider, { components }, probeTree)
422
+ ? React.createElement(MDXProvider, {components}, probeTree)
345
423
  : probeTree;
346
424
  const out = ReactDOMServer.renderToStaticMarkup(probe);
347
425
  ok = !!(out && out.indexOf("data-canopy-probe") !== -1);
@@ -353,14 +431,14 @@ async function loadAppWrapper() {
353
431
  "content/_app.mdx must render {children}. Update the layout so downstream pages receive their content."
354
432
  );
355
433
  }
356
- APP_WRAPPER = { App, Head };
434
+ APP_WRAPPER = {App, Head};
357
435
  return APP_WRAPPER;
358
436
  }
359
437
 
360
438
  async function compileMdxFile(filePath, outPath, Layout, extraProps = {}) {
361
- const { compile } = await import("@mdx-js/mdx");
439
+ const {compile} = await import("@mdx-js/mdx");
362
440
  const raw = await fsp.readFile(filePath, "utf8");
363
- const { content: source } = parseFrontmatter(raw);
441
+ const {content: source} = parseFrontmatter(raw);
364
442
  const compiled = await compile(source, buildCompileOptions());
365
443
  const code = String(compiled);
366
444
  ensureDirSync(CACHE_DIR);
@@ -392,9 +470,11 @@ async function compileMdxFile(filePath, outPath, Layout, extraProps = {}) {
392
470
  components.CodeBlock ||
393
471
  components.MarkdownCodeBlock ||
394
472
  components.MDXCodeBlock);
395
- const rawHeadings = Array.isArray(extraProps && extraProps.page && extraProps.page.headings)
473
+ const rawHeadings = Array.isArray(
474
+ extraProps && extraProps.page && extraProps.page.headings
475
+ )
396
476
  ? extraProps.page.headings
397
- .map((heading) => (heading ? { ...heading } : heading))
477
+ .map((heading) => (heading ? {...heading} : heading))
398
478
  .filter(Boolean)
399
479
  : [];
400
480
  const headingQueue = rawHeadings.slice();
@@ -407,7 +487,7 @@ async function compileMdxFile(filePath, outPath, Layout, extraProps = {}) {
407
487
  });
408
488
 
409
489
  function reserveHeadingId(base) {
410
- const fallback = base || 'section';
490
+ const fallback = base || "section";
411
491
  let candidate = fallback;
412
492
  let attempt = 1;
413
493
  while (headingIdCounts.has(candidate)) {
@@ -419,18 +499,21 @@ async function compileMdxFile(filePath, outPath, Layout, extraProps = {}) {
419
499
  }
420
500
 
421
501
  function extractTextFromChildren(children) {
422
- if (children == null) return '';
423
- if (typeof children === 'string' || typeof children === 'number') return String(children);
424
- if (Array.isArray(children)) return children.map((child) => extractTextFromChildren(child)).join('');
425
- if (React.isValidElement(children)) return extractTextFromChildren(children.props && children.props.children);
426
- return '';
502
+ if (children == null) return "";
503
+ if (typeof children === "string" || typeof children === "number")
504
+ return String(children);
505
+ if (Array.isArray(children))
506
+ return children.map((child) => extractTextFromChildren(child)).join("");
507
+ if (React.isValidElement(children))
508
+ return extractTextFromChildren(children.props && children.props.children);
509
+ return "";
427
510
  }
428
511
 
429
512
  function takeHeading(level, children) {
430
513
  if (!headingQueue.length) return null;
431
514
  const idx = headingQueue.findIndex((item) => {
432
- if (!item || typeof item !== 'object') return false;
433
- const depth = typeof item.depth === 'number' ? item.depth : item.level;
515
+ if (!item || typeof item !== "object") return false;
516
+ const depth = typeof item.depth === "number" ? item.depth : item.level;
434
517
  return depth === level;
435
518
  });
436
519
  if (idx === -1) return null;
@@ -452,7 +535,7 @@ async function compileMdxFile(filePath, outPath, Layout, extraProps = {}) {
452
535
  return function HeadingComponent(props) {
453
536
  const heading = takeHeading(level, props && props.children);
454
537
  const id = props && props.id ? props.id : heading && heading.id;
455
- const finalProps = id ? { ...props, id } : props;
538
+ const finalProps = id ? {...props, id} : props;
456
539
  return React.createElement(Base, finalProps, props && props.children);
457
540
  };
458
541
  }
@@ -461,7 +544,9 @@ async function compileMdxFile(filePath, outPath, Layout, extraProps = {}) {
461
544
  new Set(
462
545
  headingQueue
463
546
  .map((heading) => (heading ? heading.depth || heading.level : null))
464
- .filter((level) => typeof level === 'number' && level >= 1 && level <= 6)
547
+ .filter(
548
+ (level) => typeof level === "number" && level >= 1 && level <= 6
549
+ )
465
550
  )
466
551
  );
467
552
  const headingComponents = levelsPresent.length
@@ -473,27 +558,28 @@ async function compileMdxFile(filePath, outPath, Layout, extraProps = {}) {
473
558
  const MDXProvider = await getMdxProvider();
474
559
  // Base path support for anchors
475
560
  const Anchor = function A(props) {
476
- let { href = "", ...rest } = props || {};
561
+ let {href = "", ...rest} = props || {};
477
562
  href = withBase(href);
478
- return React.createElement("a", { href, ...rest }, props.children);
563
+ return React.createElement("a", {href, ...rest}, props.children);
479
564
  };
480
565
  const app = await loadAppWrapper();
481
566
  const dirLayout = await getNearestDirLayout(filePath);
482
567
  const contentNode = React.createElement(MDXContent, extraProps);
483
- const layoutProps = dirLayout ? { ...extraProps } : null;
568
+ const layoutProps = dirLayout ? {...extraProps} : null;
484
569
  const withLayout = dirLayout
485
570
  ? React.createElement(dirLayout, layoutProps, contentNode)
486
571
  : contentNode;
572
+ const withApp = React.createElement(app.App, null, withLayout);
487
573
  const PageContext = getPageContext();
488
574
  const contextValue = {
489
- navigation: extraProps && extraProps.navigation ? extraProps.navigation : null,
575
+ navigation:
576
+ extraProps && extraProps.navigation ? extraProps.navigation : null,
490
577
  page: extraProps && extraProps.page ? extraProps.page : null,
491
578
  };
492
579
  const withContext = PageContext
493
- ? React.createElement(PageContext.Provider, { value: contextValue }, withLayout)
494
- : withLayout;
495
- const withApp = React.createElement(app.App, null, withContext);
496
- const compMap = { ...components, ...headingComponents, a: Anchor };
580
+ ? React.createElement(PageContext.Provider, {value: contextValue}, withApp)
581
+ : withApp;
582
+ const compMap = {...components, ...headingComponents, a: Anchor};
497
583
  if (markdownTableComponent && !compMap.table) {
498
584
  compMap.table = markdownTableComponent;
499
585
  }
@@ -501,27 +587,31 @@ async function compileMdxFile(filePath, outPath, Layout, extraProps = {}) {
501
587
  compMap.pre = codeBlockComponent;
502
588
  }
503
589
  const page = MDXProvider
504
- ? React.createElement(MDXProvider, { components: compMap }, withApp)
505
- : withApp;
590
+ ? React.createElement(MDXProvider, {components: compMap}, withContext)
591
+ : withContext;
506
592
  const body = ReactDOMServer.renderToStaticMarkup(page);
507
- let head = '';
593
+ let head = "";
508
594
  if (app && app.Head) {
509
595
  const headElement = React.createElement(app.Head, {
510
596
  page: contextValue.page,
511
597
  navigation: contextValue.navigation,
512
598
  });
513
599
  const wrappedHead = PageContext
514
- ? React.createElement(PageContext.Provider, { value: contextValue }, headElement)
600
+ ? React.createElement(
601
+ PageContext.Provider,
602
+ {value: contextValue},
603
+ headElement
604
+ )
515
605
  : headElement;
516
606
  head = ReactDOMServer.renderToStaticMarkup(wrappedHead);
517
607
  }
518
- return { body, head };
608
+ return {body, head};
519
609
  }
520
610
 
521
611
  async function compileMdxToComponent(filePath) {
522
- const { compile } = await import("@mdx-js/mdx");
612
+ const {compile} = await import("@mdx-js/mdx");
523
613
  const raw = await fsp.readFile(filePath, "utf8");
524
- const { content: source } = parseFrontmatter(raw);
614
+ const {content: source} = parseFrontmatter(raw);
525
615
  const compiled = await compile(source, buildCompileOptions());
526
616
  const code = String(compiled);
527
617
  ensureDirSync(CACHE_DIR);
@@ -557,9 +647,12 @@ async function ensureClientRuntime() {
557
647
  esbuild = require("esbuild");
558
648
  } catch (_) {}
559
649
  }
560
- if (!esbuild) throw new Error('Viewer runtime bundling requires esbuild. Install dependencies before building.');
650
+ if (!esbuild)
651
+ throw new Error(
652
+ "Viewer runtime bundling requires esbuild. Install dependencies before building."
653
+ );
561
654
  ensureDirSync(OUT_DIR);
562
- const scriptsDir = path.join(OUT_DIR, 'scripts');
655
+ const scriptsDir = path.join(OUT_DIR, "scripts");
563
656
  ensureDirSync(scriptsDir);
564
657
  const outFile = path.join(scriptsDir, "canopy-viewer.js");
565
658
  const entry = `
@@ -646,16 +739,34 @@ async function ensureClientRuntime() {
646
739
  export const hydrateRoot = RDC.hydrateRoot;
647
740
  `;
648
741
  const plugin = {
649
- name: 'canopy-react-shims',
742
+ name: "canopy-react-shims",
650
743
  setup(build) {
651
- const ns = 'canopy-shim';
652
- build.onResolve({ filter: /^react$/ }, () => ({ path: 'react', namespace: ns }));
653
- build.onResolve({ filter: /^react-dom$/ }, () => ({ path: 'react-dom', namespace: ns }));
654
- build.onResolve({ filter: /^react-dom\/client$/ }, () => ({ path: 'react-dom-client', namespace: ns }));
655
- build.onLoad({ filter: /^react$/, namespace: ns }, () => ({ contents: reactShim, loader: 'js' }));
656
- build.onLoad({ filter: /^react-dom$/, namespace: ns }, () => ({ contents: rdomShim, loader: 'js' }));
657
- build.onLoad({ filter: /^react-dom-client$/, namespace: ns }, () => ({ contents: rdomClientShim, loader: 'js' }));
658
- }
744
+ const ns = "canopy-shim";
745
+ build.onResolve({filter: /^react$/}, () => ({
746
+ path: "react",
747
+ namespace: ns,
748
+ }));
749
+ build.onResolve({filter: /^react-dom$/}, () => ({
750
+ path: "react-dom",
751
+ namespace: ns,
752
+ }));
753
+ build.onResolve({filter: /^react-dom\/client$/}, () => ({
754
+ path: "react-dom-client",
755
+ namespace: ns,
756
+ }));
757
+ build.onLoad({filter: /^react$/, namespace: ns}, () => ({
758
+ contents: reactShim,
759
+ loader: "js",
760
+ }));
761
+ build.onLoad({filter: /^react-dom$/, namespace: ns}, () => ({
762
+ contents: rdomShim,
763
+ loader: "js",
764
+ }));
765
+ build.onLoad({filter: /^react-dom-client$/, namespace: ns}, () => ({
766
+ contents: rdomClientShim,
767
+ loader: "js",
768
+ }));
769
+ },
659
770
  };
660
771
  await esbuild.build({
661
772
  stdin: {
@@ -675,11 +786,15 @@ async function ensureClientRuntime() {
675
786
  plugins: [plugin],
676
787
  });
677
788
  try {
678
- const { logLine } = require('./log');
679
- let size = 0; try { const st = fs.statSync(outFile); size = st && st.size || 0; } catch (_) {}
680
- const kb = size ? ` (${(size/1024).toFixed(1)} KB)` : '';
681
- const rel = path.relative(process.cwd(), outFile).split(path.sep).join('/');
682
- logLine(`✓ Wrote ${rel}${kb}`, 'cyan');
789
+ const {logLine} = require("./log");
790
+ let size = 0;
791
+ try {
792
+ const st = fs.statSync(outFile);
793
+ size = (st && st.size) || 0;
794
+ } catch (_) {}
795
+ const kb = size ? ` (${(size / 1024).toFixed(1)} KB)` : "";
796
+ const rel = path.relative(process.cwd(), outFile).split(path.sep).join("/");
797
+ logLine(`✓ Wrote ${rel}${kb}`, "cyan");
683
798
  } catch (_) {}
684
799
  }
685
800
 
@@ -687,14 +802,22 @@ async function ensureClientRuntime() {
687
802
  // and renders a Slider for each.
688
803
  async function ensureFacetsRuntime() {
689
804
  let esbuild = null;
690
- try { esbuild = require("../../ui/node_modules/esbuild"); } catch (_) { try { esbuild = require("esbuild"); } catch (_) {} }
805
+ try {
806
+ esbuild = require("../../ui/node_modules/esbuild");
807
+ } catch (_) {
808
+ try {
809
+ esbuild = require("esbuild");
810
+ } catch (_) {}
811
+ }
691
812
  if (!esbuild) {
692
- throw new Error('RelatedItems runtime bundling requires esbuild. Install dependencies before building.');
813
+ throw new Error(
814
+ "RelatedItems runtime bundling requires esbuild. Install dependencies before building."
815
+ );
693
816
  }
694
817
  ensureDirSync(OUT_DIR);
695
- const scriptsDir = path.join(OUT_DIR, 'scripts');
818
+ const scriptsDir = path.join(OUT_DIR, "scripts");
696
819
  ensureDirSync(scriptsDir);
697
- const outFile = path.join(scriptsDir, 'canopy-related-items.js');
820
+ const outFile = path.join(scriptsDir, "canopy-related-items.js");
698
821
  const entry = `
699
822
  function ready(fn){ if(document.readyState==='loading') document.addEventListener('DOMContentLoaded',fn,{once:true}); else fn(); }
700
823
  function parseProps(el){ try{ const s=el.querySelector('script[type="application/json"]'); if(s) return JSON.parse(s.textContent||'{}'); }catch(_){ } return {}; }
@@ -749,6 +872,7 @@ async function ensureFacetsRuntime() {
749
872
  const pick = candidates[Math.floor(Math.random() * candidates.length)];
750
873
  const wrap = document.createElement('div');
751
874
  wrap.setAttribute('data-facet-label', entry.label);
875
+ wrap.setAttribute('class', 'canopy-slider');
752
876
  const ph = makeSliderPlaceholder({ iiifContent: rootBase() + '/api/facet/' + (allow.slug) + '/' + pick.valueSlug + '.json' + verQ });
753
877
  if (ph) wrap.appendChild(ph);
754
878
  el.appendChild(wrap);
@@ -762,6 +886,7 @@ async function ensureFacetsRuntime() {
762
886
  selected.forEach((s) => {
763
887
  const wrap = document.createElement('div');
764
888
  wrap.setAttribute('data-facet-label', s.label);
889
+ wrap.setAttribute('class', 'canopy-slider');
765
890
  const ph = makeSliderPlaceholder({ iiifContent: rootBase() + '/api/facet/' + s.labelSlug + '/' + s.valueSlug + '.json' + verQ });
766
891
  if (ph) wrap.appendChild(ph);
767
892
  el.appendChild(wrap);
@@ -770,30 +895,39 @@ async function ensureFacetsRuntime() {
770
895
  });
771
896
  });
772
897
  `;
773
- const shim = { name: 'facets-vanilla', setup(){} };
898
+ const shim = {name: "facets-vanilla", setup() {}};
774
899
  try {
775
900
  await esbuild.build({
776
- stdin: { contents: entry, resolveDir: process.cwd(), sourcefile: 'canopy-facets-entry.js', loader: 'js' },
901
+ stdin: {
902
+ contents: entry,
903
+ resolveDir: process.cwd(),
904
+ sourcefile: "canopy-facets-entry.js",
905
+ loader: "js",
906
+ },
777
907
  outfile: outFile,
778
- platform: 'browser',
779
- format: 'iife',
908
+ platform: "browser",
909
+ format: "iife",
780
910
  bundle: true,
781
911
  sourcemap: false,
782
- target: ['es2018'],
783
- logLevel: 'silent',
912
+ target: ["es2018"],
913
+ logLevel: "silent",
784
914
  minify: true,
785
- plugins: [shim]
915
+ plugins: [shim],
786
916
  });
787
917
  } catch (e) {
788
918
  const message = e && e.message ? e.message : e;
789
919
  throw new Error(`RelatedItems runtime build failed: ${message}`);
790
920
  }
791
921
  try {
792
- const { logLine } = require('./log');
793
- let size = 0; try { const st = fs.statSync(outFile); size = st && st.size || 0; } catch (_) {}
794
- const kb = size ? ` (${(size/1024).toFixed(1)} KB)` : '';
795
- const rel = path.relative(process.cwd(), outFile).split(path.sep).join('/');
796
- logLine(`✓ Wrote ${rel}${kb}`, 'cyan');
922
+ const {logLine} = require("./log");
923
+ let size = 0;
924
+ try {
925
+ const st = fs.statSync(outFile);
926
+ size = (st && st.size) || 0;
927
+ } catch (_) {}
928
+ const kb = size ? ` (${(size / 1024).toFixed(1)} KB)` : "";
929
+ const rel = path.relative(process.cwd(), outFile).split(path.sep).join("/");
930
+ logLine(`✓ Wrote ${rel}${kb}`, "cyan");
797
931
  } catch (_) {}
798
932
  }
799
933
 
@@ -803,11 +937,16 @@ async function ensureSliderRuntime() {
803
937
  try {
804
938
  esbuild = require("../ui/node_modules/esbuild");
805
939
  } catch (_) {
806
- try { esbuild = require("esbuild"); } catch (_) {}
940
+ try {
941
+ esbuild = require("esbuild");
942
+ } catch (_) {}
807
943
  }
808
- if (!esbuild) throw new Error('Slider runtime bundling requires esbuild. Install dependencies before building.');
944
+ if (!esbuild)
945
+ throw new Error(
946
+ "Slider runtime bundling requires esbuild. Install dependencies before building."
947
+ );
809
948
  ensureDirSync(OUT_DIR);
810
- const scriptsDir = path.join(OUT_DIR, 'scripts');
949
+ const scriptsDir = path.join(OUT_DIR, "scripts");
811
950
  ensureDirSync(scriptsDir);
812
951
  const outFile = path.join(scriptsDir, "canopy-slider.js");
813
952
  const entry = `
@@ -892,39 +1031,67 @@ async function ensureSliderRuntime() {
892
1031
  export const hydrateRoot = RDC.hydrateRoot;
893
1032
  `;
894
1033
  const plugin = {
895
- name: 'canopy-react-shims-slider',
1034
+ name: "canopy-react-shims-slider",
896
1035
  setup(build) {
897
- const ns = 'canopy-shim';
898
- build.onResolve({ filter: /^react$/ }, () => ({ path: 'react', namespace: ns }));
899
- build.onResolve({ filter: /^react-dom$/ }, () => ({ path: 'react-dom', namespace: ns }));
900
- build.onResolve({ filter: /^react-dom\/client$/ }, () => ({ path: 'react-dom-client', namespace: ns }));
901
- build.onLoad({ filter: /^react$/, namespace: ns }, () => ({ contents: reactShim, loader: 'js' }));
902
- build.onLoad({ filter: /^react-dom$/, namespace: ns }, () => ({ contents: "export default ((typeof window!=='undefined' && window.ReactDOM) || {});", loader: 'js' }));
903
- build.onLoad({ filter: /^react-dom-client$/, namespace: ns }, () => ({ contents: rdomClientShim, loader: 'js' }));
1036
+ const ns = "canopy-shim";
1037
+ build.onResolve({filter: /^react$/}, () => ({
1038
+ path: "react",
1039
+ namespace: ns,
1040
+ }));
1041
+ build.onResolve({filter: /^react-dom$/}, () => ({
1042
+ path: "react-dom",
1043
+ namespace: ns,
1044
+ }));
1045
+ build.onResolve({filter: /^react-dom\/client$/}, () => ({
1046
+ path: "react-dom-client",
1047
+ namespace: ns,
1048
+ }));
1049
+ build.onLoad({filter: /^react$/, namespace: ns}, () => ({
1050
+ contents: reactShim,
1051
+ loader: "js",
1052
+ }));
1053
+ build.onLoad({filter: /^react-dom$/, namespace: ns}, () => ({
1054
+ contents:
1055
+ "export default ((typeof window!=='undefined' && window.ReactDOM) || {});",
1056
+ loader: "js",
1057
+ }));
1058
+ build.onLoad({filter: /^react-dom-client$/, namespace: ns}, () => ({
1059
+ contents: rdomClientShim,
1060
+ loader: "js",
1061
+ }));
904
1062
  // Inline imported CSS into a <style> tag at runtime so we don't need a separate CSS file
905
- build.onLoad({ filter: /\.css$/ }, (args) => {
906
- const fs = require('fs');
907
- let css = '';
908
- try { css = fs.readFileSync(args.path, 'utf8'); } catch (_) { css = ''; }
1063
+ build.onLoad({filter: /\.css$/}, (args) => {
1064
+ const fs = require("fs");
1065
+ let css = "";
1066
+ try {
1067
+ css = fs.readFileSync(args.path, "utf8");
1068
+ } catch (_) {
1069
+ css = "";
1070
+ }
909
1071
  const js = [
910
1072
  `var css = ${JSON.stringify(css)};`,
911
1073
  `(function(){ try { var s = document.createElement('style'); s.setAttribute('data-canopy-slider-css',''); s.textContent = css; document.head.appendChild(s); } catch (e) {} })();`,
912
- `export default css;`
913
- ].join('\n');
914
- return { contents: js, loader: 'js' };
1074
+ `export default css;`,
1075
+ ].join("\n");
1076
+ return {contents: js, loader: "js"};
915
1077
  });
916
- }
1078
+ },
917
1079
  };
918
1080
  try {
919
1081
  await esbuild.build({
920
- stdin: { contents: entry, resolveDir: process.cwd(), sourcefile: 'canopy-slider-entry.js', loader: 'js' },
1082
+ stdin: {
1083
+ contents: entry,
1084
+ resolveDir: process.cwd(),
1085
+ sourcefile: "canopy-slider-entry.js",
1086
+ loader: "js",
1087
+ },
921
1088
  outfile: outFile,
922
- platform: 'browser',
923
- format: 'iife',
1089
+ platform: "browser",
1090
+ format: "iife",
924
1091
  bundle: true,
925
1092
  sourcemap: false,
926
- target: ['es2018'],
927
- logLevel: 'silent',
1093
+ target: ["es2018"],
1094
+ logLevel: "silent",
928
1095
  minify: true,
929
1096
  plugins: [plugin],
930
1097
  });
@@ -933,11 +1100,15 @@ async function ensureSliderRuntime() {
933
1100
  throw new Error(`Slider runtime build failed: ${message}`);
934
1101
  }
935
1102
  try {
936
- const { logLine } = require('./log');
937
- let size = 0; try { const st = fs.statSync(outFile); size = st && st.size || 0; } catch (_) {}
938
- const kb = size ? ` (${(size/1024).toFixed(1)} KB)` : '';
939
- const rel = path.relative(process.cwd(), outFile).split(path.sep).join('/');
940
- logLine(`✓ Wrote ${rel}${kb}`, 'cyan');
1103
+ const {logLine} = require("./log");
1104
+ let size = 0;
1105
+ try {
1106
+ const st = fs.statSync(outFile);
1107
+ size = (st && st.size) || 0;
1108
+ } catch (_) {}
1109
+ const kb = size ? ` (${(size / 1024).toFixed(1)} KB)` : "";
1110
+ const rel = path.relative(process.cwd(), outFile).split(path.sep).join("/");
1111
+ logLine(`✓ Wrote ${rel}${kb}`, "cyan");
941
1112
  } catch (_) {}
942
1113
  }
943
1114
 
@@ -951,8 +1122,11 @@ async function ensureReactGlobals() {
951
1122
  esbuild = require("esbuild");
952
1123
  } catch (_) {}
953
1124
  }
954
- if (!esbuild) throw new Error('React globals bundling requires esbuild. Install dependencies before building.');
955
- const { path } = require("../common");
1125
+ if (!esbuild)
1126
+ throw new Error(
1127
+ "React globals bundling requires esbuild. Install dependencies before building."
1128
+ );
1129
+ const {path} = require("../common");
956
1130
  ensureDirSync(OUT_DIR);
957
1131
  const scriptsDir = path.join(OUT_DIR, "scripts");
958
1132
  ensureDirSync(scriptsDir);
@@ -978,7 +1152,7 @@ async function ensureReactGlobals() {
978
1152
  target: ["es2018"],
979
1153
  logLevel: "silent",
980
1154
  minify: true,
981
- define: { 'process.env.NODE_ENV': '"production"' },
1155
+ define: {"process.env.NODE_ENV": '"production"'},
982
1156
  });
983
1157
  }
984
1158
 
@@ -987,35 +1161,45 @@ async function ensureHeroRuntime() {
987
1161
  try {
988
1162
  esbuild = require("../ui/node_modules/esbuild");
989
1163
  } catch (_) {
990
- try { esbuild = require("esbuild"); } catch (_) {}
1164
+ try {
1165
+ esbuild = require("esbuild");
1166
+ } catch (_) {}
991
1167
  }
992
- if (!esbuild) throw new Error('Hero slider runtime bundling requires esbuild. Install dependencies before building.');
1168
+ if (!esbuild)
1169
+ throw new Error(
1170
+ "Hero slider runtime bundling requires esbuild. Install dependencies before building."
1171
+ );
993
1172
  ensureDirSync(OUT_DIR);
994
- const scriptsDir = path.join(OUT_DIR, 'scripts');
1173
+ const scriptsDir = path.join(OUT_DIR, "scripts");
995
1174
  ensureDirSync(scriptsDir);
996
- const outFile = path.join(scriptsDir, 'canopy-hero-slider.js');
997
- const entryFile = path.join(__dirname, '..', 'components', 'hero-slider-runtime.js');
1175
+ const outFile = path.join(scriptsDir, "canopy-hero-slider.js");
1176
+ const entryFile = path.join(
1177
+ __dirname,
1178
+ "..",
1179
+ "components",
1180
+ "hero-slider-runtime.js"
1181
+ );
998
1182
  await esbuild.build({
999
1183
  entryPoints: [entryFile],
1000
1184
  outfile: outFile,
1001
- platform: 'browser',
1002
- format: 'iife',
1185
+ platform: "browser",
1186
+ format: "iife",
1003
1187
  bundle: true,
1004
1188
  sourcemap: false,
1005
- target: ['es2018'],
1006
- logLevel: 'silent',
1189
+ target: ["es2018"],
1190
+ logLevel: "silent",
1007
1191
  minify: true,
1008
1192
  });
1009
1193
  try {
1010
- const { logLine } = require('./log');
1194
+ const {logLine} = require("./log");
1011
1195
  let size = 0;
1012
1196
  try {
1013
1197
  const st = fs.statSync(outFile);
1014
1198
  size = (st && st.size) || 0;
1015
1199
  } catch (_) {}
1016
- const kb = size ? ` (${(size / 1024).toFixed(1)} KB)` : '';
1017
- const rel = path.relative(process.cwd(), outFile).split(path.sep).join('/');
1018
- logLine(`✓ Wrote ${rel}${kb}`, 'cyan');
1200
+ const kb = size ? ` (${(size / 1024).toFixed(1)} KB)` : "";
1201
+ const rel = path.relative(process.cwd(), outFile).split(path.sep).join("/");
1202
+ logLine(`✓ Wrote ${rel}${kb}`, "cyan");
1019
1203
  } catch (_) {}
1020
1204
  }
1021
1205