@canopy-iiif/app 0.10.16 → 0.10.18

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.
@@ -20,6 +20,7 @@ const { ensureStyles } = require("./styles");
20
20
  const { copyAssets } = require("./assets");
21
21
  const { logLine } = require("./log");
22
22
  const navigation = require("../components/navigation");
23
+ const referenced = require("../components/referenced");
23
24
 
24
25
  // hold records between builds if skipping IIIF
25
26
  let iiifRecordsCache = [];
@@ -42,6 +43,7 @@ async function build(options = {}) {
42
43
  logLine("• Reset MDX cache", "blue", { dim: true });
43
44
  mdx?.resetMdxCaches();
44
45
  navigation?.resetNavigationCache?.();
46
+ referenced?.resetReferenceIndex?.();
45
47
  if (!skipIiif) {
46
48
  await cleanDir(OUT_DIR);
47
49
  logLine(`• Cleaned output directory`, "blue", { dim: true });
package/lib/build/iiif.js CHANGED
@@ -18,6 +18,7 @@ const mdx = require("./mdx");
18
18
  const {log, logLine, logResponse} = require("./log");
19
19
  const { getPageContext } = require("../page-context");
20
20
  const PageContext = getPageContext();
21
+ const referenced = require("../components/referenced");
21
22
  const {
22
23
  getThumbnail,
23
24
  getRepresentativeImage,
@@ -1288,6 +1289,8 @@ async function buildIiifCollectionPages(CONFIG) {
1288
1289
  throw new Error(`Failed to compile content/works/_layout.mdx: ${message}`);
1289
1290
  }
1290
1291
 
1292
+ referenced.ensureReferenceIndex();
1293
+
1291
1294
  for (let ci = 0; ci < chunks; ci++) {
1292
1295
  const chunk = tasks.slice(ci * chunkSize, (ci + 1) * chunkSize);
1293
1296
  logLine(`• Chunk ${ci + 1}/${chunks}`, "blue", {dim: true});
@@ -1412,6 +1415,8 @@ async function buildIiifCollectionPages(CONFIG) {
1412
1415
  else idxMap.byId.push(newEntry);
1413
1416
  await saveManifestIndex(idxMap);
1414
1417
  }
1418
+ const manifestId = manifest && manifest.id ? manifest.id : id;
1419
+ const references = referenced.getReferencesForManifest(manifestId);
1415
1420
  const href = path.join("works", slug + ".html");
1416
1421
  const outPath = path.join(OUT_DIR, href);
1417
1422
  ensureDirSync(path.dirname(outPath));
@@ -1456,6 +1461,8 @@ async function buildIiifCollectionPages(CONFIG) {
1456
1461
  slug,
1457
1462
  type: "work",
1458
1463
  description: pageDescription,
1464
+ manifestId,
1465
+ referencedBy: references,
1459
1466
  meta: {
1460
1467
  title,
1461
1468
  description: pageDescription,
@@ -1471,7 +1478,11 @@ async function buildIiifCollectionPages(CONFIG) {
1471
1478
  pageDetails.meta.ogImage = ogImageForPage;
1472
1479
  }
1473
1480
  const pageContextValue = { navigation: null, page: pageDetails };
1474
- const mdxContent = React.createElement(WorksLayoutComp, {manifest});
1481
+ const mdxContent = React.createElement(WorksLayoutComp, {
1482
+ manifest,
1483
+ references,
1484
+ manifestId,
1485
+ });
1475
1486
  const siteTree = mdxContent;
1476
1487
  const wrappedApp =
1477
1488
  app && app.App
package/lib/build/mdx.js CHANGED
@@ -365,6 +365,18 @@ async function compileMdxFile(filePath, outPath, Layout, extraProps = {}) {
365
365
  const mod = await import(pathToFileURL(tmpFile).href + bust);
366
366
  const MDXContent = mod.default || mod.MDXContent || mod;
367
367
  const components = await loadUiComponents();
368
+ const markdownTableComponent =
369
+ components &&
370
+ (components.MarkdownTable ||
371
+ components.DocsMarkdownTable ||
372
+ components.MarkdownTables ||
373
+ components.MDXMarkdownTable);
374
+ const codeBlockComponent =
375
+ components &&
376
+ (components.DocsCodeBlock ||
377
+ components.CodeBlock ||
378
+ components.MarkdownCodeBlock ||
379
+ components.MDXCodeBlock);
368
380
  const rawHeadings = Array.isArray(extraProps && extraProps.page && extraProps.page.headings)
369
381
  ? extraProps.page.headings
370
382
  .map((heading) => (heading ? { ...heading } : heading))
@@ -467,6 +479,12 @@ async function compileMdxFile(filePath, outPath, Layout, extraProps = {}) {
467
479
  : withLayout;
468
480
  const withApp = React.createElement(app.App, null, withContext);
469
481
  const compMap = { ...components, ...headingComponents, a: Anchor };
482
+ if (markdownTableComponent && !compMap.table) {
483
+ compMap.table = markdownTableComponent;
484
+ }
485
+ if (codeBlockComponent && !compMap.pre) {
486
+ compMap.pre = codeBlockComponent;
487
+ }
470
488
  const page = MDXProvider
471
489
  ? React.createElement(MDXProvider, { components: compMap }, withApp)
472
490
  : withApp;
@@ -11,6 +11,7 @@ const {
11
11
  const { log } = require('./log');
12
12
  const mdx = require('./mdx');
13
13
  const navigation = require('../components/navigation');
14
+ const referenced = require('../components/referenced');
14
15
 
15
16
  function normalizeWhitespace(value) {
16
17
  if (!value) return '';
@@ -89,6 +90,13 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}) {
89
90
  ? mdx.parseFrontmatter(source)
90
91
  : { data: null, content: source };
91
92
  const frontmatterData = frontmatter && isPlainObject(frontmatter.data) ? frontmatter.data : null;
93
+ const referencedManifestIdsRaw = frontmatterData ? frontmatterData.referencedManifests : null;
94
+ const referencedManifestIds = referenced.normalizeReferencedManifestList(
95
+ referencedManifestIdsRaw
96
+ );
97
+ const referencedItems = referencedManifestIds.length
98
+ ? referenced.buildReferencedItems(referencedManifestIds)
99
+ : [];
92
100
  let layoutMeta = null;
93
101
  try {
94
102
  layoutMeta = await getNearestDirLayoutMeta(filePath);
@@ -134,11 +142,23 @@ async function renderContentMdxToHtml(filePath, outPath, extraProps = {}) {
134
142
  }
135
143
  if (frontmatterMeta) Object.assign(pageMeta, frontmatterMeta);
136
144
  if (Object.keys(pageMeta).length) basePage.meta = pageMeta;
145
+ if (referencedManifestIds.length) {
146
+ basePage.referencedManifests = referencedManifestIds;
147
+ }
148
+ if (referencedItems.length) {
149
+ basePage.referencedItems = referencedItems;
150
+ }
137
151
  if (Object.keys(basePage).length) {
138
152
  mergedProps.page = mergedProps.page
139
153
  ? { ...basePage, ...mergedProps.page }
140
154
  : basePage;
141
155
  }
156
+ if (referencedManifestIds.length) {
157
+ mergedProps.referencedManifests = referencedManifestIds;
158
+ }
159
+ if (referencedItems.length) {
160
+ mergedProps.referencedItems = referencedItems;
161
+ }
142
162
  if (navData && !mergedProps.navigation) {
143
163
  mergedProps.navigation = navData;
144
164
  }
@@ -0,0 +1,212 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const {
4
+ CONTENT_DIR,
5
+ rootRelativeHref,
6
+ } = require('../common');
7
+ const mdx = require('../build/mdx.js');
8
+ const {
9
+ firstLabelString,
10
+ normalizeIiifId,
11
+ equalIiifId,
12
+ readJson,
13
+ findSlugByIdFromDiskSync,
14
+ } = require('./featured');
15
+
16
+ const IIIF_INDEX_PATH = path.resolve('.cache/iiif/index.json');
17
+ const IIIF_MANIFESTS_DIR = path.resolve('.cache/iiif/manifests');
18
+ let manifestReferenceIndex = null;
19
+ let referenceIndexBuilt = false;
20
+
21
+ function firstTextValue(value) {
22
+ if (value == null) return '';
23
+ if (typeof value === 'string') return value.trim();
24
+ if (Array.isArray(value)) {
25
+ for (const entry of value) {
26
+ const text = firstTextValue(entry);
27
+ if (text) return text;
28
+ }
29
+ return '';
30
+ }
31
+ if (typeof value === 'object') {
32
+ const keys = Object.keys(value);
33
+ for (const key of keys) {
34
+ const entry = value[key];
35
+ if (Array.isArray(entry)) {
36
+ for (const child of entry) {
37
+ const text = firstTextValue(child);
38
+ if (text) return text;
39
+ }
40
+ } else {
41
+ const text = firstTextValue(entry);
42
+ if (text) return text;
43
+ }
44
+ }
45
+ }
46
+ return '';
47
+ }
48
+
49
+ function normalizeReferencedManifestList(raw) {
50
+ const values = Array.isArray(raw) ? raw : raw ? [raw] : [];
51
+ const seen = new Set();
52
+ const out = [];
53
+ for (const entry of values) {
54
+ if (entry === undefined || entry === null) continue;
55
+ const normalized = normalizeIiifId(entry);
56
+ const key = normalized || String(entry).trim();
57
+ if (!key || seen.has(key)) continue;
58
+ seen.add(key);
59
+ out.push(key);
60
+ }
61
+ return out;
62
+ }
63
+
64
+ function readManifestBySlug(slug) {
65
+ if (!slug) return null;
66
+ const filename = `${slug}.json`;
67
+ const filePath = path.join(IIIF_MANIFESTS_DIR, filename);
68
+ return readJson(filePath);
69
+ }
70
+
71
+ function assignThumbnailFields(target, entry, manifest) {
72
+ if (!target) return target;
73
+ const heroThumb = entry && entry.heroThumbnail ? String(entry.heroThumbnail) : '';
74
+ const fallbackThumb = entry && entry.thumbnail ? String(entry.thumbnail) : '';
75
+ if (heroThumb) {
76
+ target.thumbnail = heroThumb;
77
+ if (typeof entry.heroThumbnailWidth === 'number') target.thumbnailWidth = entry.heroThumbnailWidth;
78
+ if (typeof entry.heroThumbnailHeight === 'number') target.thumbnailHeight = entry.heroThumbnailHeight;
79
+ } else if (fallbackThumb) {
80
+ target.thumbnail = fallbackThumb;
81
+ if (typeof entry.thumbnailWidth === 'number') target.thumbnailWidth = entry.thumbnailWidth;
82
+ if (typeof entry.thumbnailHeight === 'number') target.thumbnailHeight = entry.thumbnailHeight;
83
+ }
84
+ if (!target.thumbnail && manifest && manifest.thumbnail) {
85
+ const thumb = manifest.thumbnail;
86
+ if (Array.isArray(thumb) && thumb.length) {
87
+ const first = thumb[0] || {};
88
+ const id = first.id || first['@id'] || first.url || '';
89
+ if (id) target.thumbnail = String(id);
90
+ if (typeof first.width === 'number') target.thumbnailWidth = first.width;
91
+ if (typeof first.height === 'number') target.thumbnailHeight = first.height;
92
+ } else if (thumb && typeof thumb === 'object') {
93
+ const id = thumb.id || thumb['@id'] || thumb.url || '';
94
+ if (id) target.thumbnail = String(id);
95
+ if (typeof thumb.width === 'number') target.thumbnailWidth = thumb.width;
96
+ if (typeof thumb.height === 'number') target.thumbnailHeight = thumb.height;
97
+ }
98
+ }
99
+ return target;
100
+ }
101
+
102
+ function buildReferencedItems(referencedIds) {
103
+ const ids = normalizeReferencedManifestList(referencedIds);
104
+ if (!ids.length) return [];
105
+ const index = readJson(IIIF_INDEX_PATH) || {};
106
+ const byId = Array.isArray(index.byId) ? index.byId : [];
107
+ const items = [];
108
+ for (const id of ids) {
109
+ const entry = byId.find(
110
+ (candidate) => candidate && candidate.type === 'Manifest' && equalIiifId(candidate.id, id)
111
+ );
112
+ const slug = entry && entry.slug ? entry.slug : findSlugByIdFromDiskSync(id);
113
+ if (!slug) continue;
114
+ const manifest = readManifestBySlug(slug);
115
+ if (!manifest) continue;
116
+ const href = rootRelativeHref(path.join('works', `${slug}.html`).split(path.sep).join('/'));
117
+ const title = firstLabelString(manifest.label);
118
+ const summary = firstTextValue(manifest.summary);
119
+ const item = { id, slug, href, title };
120
+ if (summary) item.summary = summary;
121
+ assignThumbnailFields(item, entry, manifest);
122
+ items.push(item);
123
+ }
124
+ return items;
125
+ }
126
+
127
+ function isReservedContentFile(p) {
128
+ return mdx.isReservedFile ? mdx.isReservedFile(p) : path.basename(p).startsWith('_');
129
+ }
130
+
131
+ function resolveHrefFromContentPath(filePath) {
132
+ const rel = path.relative(CONTENT_DIR, filePath).replace(/\\/g, '/');
133
+ const htmlRel = rel.replace(/\.mdx$/i, '.html');
134
+ return rootRelativeHref(htmlRel);
135
+ }
136
+
137
+ function buildReferenceIndexSync() {
138
+ const map = new Map();
139
+ function record(filePath) {
140
+ let raw = '';
141
+ try {
142
+ raw = fs.readFileSync(filePath, 'utf8');
143
+ } catch (_) {
144
+ return;
145
+ }
146
+ const frontmatter = mdx.parseFrontmatter ? mdx.parseFrontmatter(raw) : { data: null };
147
+ const data = frontmatter && frontmatter.data && typeof frontmatter.data === 'object' ? frontmatter.data : null;
148
+ const ids = normalizeReferencedManifestList(data && data.referencedManifests);
149
+ if (!ids.length) return;
150
+ const title = (data && typeof data.title === 'string' && data.title.trim())
151
+ ? data.title.trim()
152
+ : mdx.extractTitle ? mdx.extractTitle(raw) : path.basename(filePath, path.extname(filePath));
153
+ const href = resolveHrefFromContentPath(filePath);
154
+ const entry = { title, href };
155
+ for (const id of ids) {
156
+ const normalized = normalizeIiifId(id);
157
+ if (!normalized) continue;
158
+ const list = map.get(normalized) || [];
159
+ if (!list.some((item) => item.href === entry.href)) list.push(entry);
160
+ map.set(normalized, list);
161
+ }
162
+ }
163
+ function walk(dir) {
164
+ let entries = [];
165
+ try {
166
+ entries = fs.readdirSync(dir, { withFileTypes: true });
167
+ } catch (_) {
168
+ return;
169
+ }
170
+ for (const entry of entries) {
171
+ const filePath = path.join(dir, entry.name);
172
+ if (entry.isDirectory()) {
173
+ walk(filePath);
174
+ } else if (entry.isFile() && /\.mdx$/i.test(entry.name) && !isReservedContentFile(filePath)) {
175
+ record(filePath);
176
+ }
177
+ }
178
+ }
179
+ walk(CONTENT_DIR);
180
+ for (const [key, list] of map.entries()) {
181
+ list.sort((a, b) => (a.title || '').localeCompare(b.title || ''));
182
+ }
183
+ manifestReferenceIndex = map;
184
+ referenceIndexBuilt = true;
185
+ return manifestReferenceIndex;
186
+ }
187
+
188
+ function ensureReferenceIndex() {
189
+ if (referenceIndexBuilt && manifestReferenceIndex) return manifestReferenceIndex;
190
+ return buildReferenceIndexSync();
191
+ }
192
+
193
+ function resetReferenceIndex() {
194
+ referenceIndexBuilt = false;
195
+ manifestReferenceIndex = null;
196
+ }
197
+
198
+ function getReferencesForManifest(manifestId) {
199
+ const normalized = normalizeIiifId(manifestId);
200
+ if (!normalized) return [];
201
+ const index = ensureReferenceIndex();
202
+ const list = index.get(normalized) || [];
203
+ return list.map((entry) => ({ ...entry }));
204
+ }
205
+
206
+ module.exports = {
207
+ normalizeReferencedManifestList,
208
+ buildReferencedItems,
209
+ ensureReferenceIndex,
210
+ resetReferenceIndex,
211
+ getReferencesForManifest,
212
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-iiif/app",
3
- "version": "0.10.16",
3
+ "version": "0.10.18",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",
package/ui/dist/index.mjs CHANGED
@@ -19,12 +19,14 @@ function Card({
19
19
  className,
20
20
  style,
21
21
  children,
22
+ lazy = true,
22
23
  ...rest
23
24
  }) {
24
25
  const containerRef = useRef(null);
25
- const [inView, setInView] = useState(false);
26
- const [imageLoaded, setImageLoaded] = useState(false);
26
+ const [inView, setInView] = useState(!lazy);
27
+ const [imageLoaded, setImageLoaded] = useState(!lazy);
27
28
  useEffect(() => {
29
+ if (!lazy) return;
28
30
  if (!containerRef.current) return;
29
31
  if (typeof IntersectionObserver !== "function") {
30
32
  setInView(true);
@@ -56,7 +58,7 @@ function Card({
56
58
  } catch (_) {
57
59
  }
58
60
  };
59
- }, []);
61
+ }, [lazy]);
60
62
  const w = Number(imgWidth);
61
63
  const h = Number(imgHeight);
62
64
  const ratio = Number.isFinite(Number(aspectRatio)) && Number(aspectRatio) > 0 ? Number(aspectRatio) : Number.isFinite(w) && w > 0 && Number.isFinite(h) && h > 0 ? w / h : void 0;