@canopy-iiif/app 1.5.16 → 1.6.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 CHANGED
@@ -99,6 +99,20 @@ function normalizeCollectionUris(value) {
99
99
  return uris;
100
100
  }
101
101
 
102
+ function normalizeManifestConfig(cfg) {
103
+ if (!cfg || typeof cfg !== "object") return [];
104
+ const entries = [];
105
+ const push = (value) => {
106
+ if (value === undefined || value === null) return;
107
+ if (Array.isArray(value)) entries.push(...value);
108
+ else entries.push(value);
109
+ };
110
+ push(cfg.manifest);
111
+ push(cfg.manifests);
112
+ if (!entries.length) return [];
113
+ return normalizeCollectionUris(entries);
114
+ }
115
+
102
116
  function clampSlugLength(slug, limit = MAX_ENTRY_SLUG_LENGTH) {
103
117
  if (!slug) return "";
104
118
  const max = Math.max(1, limit);
@@ -1457,7 +1471,8 @@ async function buildIiifCollectionPages(CONFIG) {
1457
1471
  process.env.CANOPY_COLLECTION_URI || ""
1458
1472
  );
1459
1473
  }
1460
- if (!collectionUris.length) return {searchRecords: []};
1474
+ const manifestUris = normalizeManifestConfig(cfg);
1475
+ if (!collectionUris.length && !manifestUris.length) return {searchRecords: []};
1461
1476
 
1462
1477
  const searchIndexCfg = (cfg && cfg.search && cfg.search.index) || {};
1463
1478
  const metadataCfg = (searchIndexCfg && searchIndexCfg.metadata) || {};
@@ -1524,6 +1539,7 @@ async function buildIiifCollectionPages(CONFIG) {
1524
1539
 
1525
1540
  // Recursively traverse Collections and gather all Manifest tasks
1526
1541
  const tasks = [];
1542
+ const queuedManifestIds = new Set();
1527
1543
  const visitedCollections = new Set(); // normalized ids
1528
1544
  const norm = (x) => {
1529
1545
  try {
@@ -1567,7 +1583,11 @@ async function buildIiifCollectionPages(CONFIG) {
1567
1583
  const entryId = entry && entry.id;
1568
1584
  if (!entryId) continue;
1569
1585
  const entryType = normalizeIiifType(entry.type || entry.fallback || "");
1586
+ const dedupeKey = norm(entryId) || String(entryId || "");
1587
+ if (!dedupeKey) continue;
1570
1588
  if (entryType === "manifest") {
1589
+ if (queuedManifestIds.has(dedupeKey)) continue;
1590
+ queuedManifestIds.add(dedupeKey);
1571
1591
  tasks.push({id: entryId, parent: collectionKey});
1572
1592
  } else if (entryType === "collection") {
1573
1593
  await gatherFromCollection(entry.raw || entryId, collectionKey);
@@ -1597,6 +1617,14 @@ async function buildIiifCollectionPages(CONFIG) {
1597
1617
  } catch (_) {}
1598
1618
  await gatherFromCollection(normalizedRoot, "");
1599
1619
  }
1620
+ if (manifestUris.length) {
1621
+ for (const uri of manifestUris) {
1622
+ const dedupeKey = norm(uri) || String(uri || "");
1623
+ if (!dedupeKey || queuedManifestIds.has(dedupeKey)) continue;
1624
+ queuedManifestIds.add(dedupeKey);
1625
+ tasks.push({id: uri, parent: ""});
1626
+ }
1627
+ }
1600
1628
  if (!tasks.length) return {searchRecords: []};
1601
1629
 
1602
1630
  // Split into chunks and process with limited concurrency
@@ -2395,6 +2423,7 @@ module.exports.__TESTING__ = {
2395
2423
  formatDurationMs,
2396
2424
  resolveBoolean,
2397
2425
  normalizeCollectionUris,
2426
+ normalizeManifestConfig,
2398
2427
  clampSlugLength,
2399
2428
  isSlugTooLong,
2400
2429
  normalizeSlugBase,
package/lib/common.js CHANGED
@@ -15,7 +15,10 @@ const BASE_PATH = readBasePath();
15
15
  let cachedAppearance = null;
16
16
  let cachedAccent = null;
17
17
  let cachedSiteMetadata = null;
18
+ let cachedSearchPageMetadata = null;
18
19
  const DEFAULT_SITE_TITLE = 'Site title';
20
+ const DEFAULT_SEARCH_PAGE_TITLE = 'Search';
21
+ const DEFAULT_SEARCH_PAGE_DESCRIPTION = '';
19
22
 
20
23
  function resolveThemeAppearance() {
21
24
  if (cachedAppearance) return cachedAppearance;
@@ -87,6 +90,32 @@ function getSiteTitle() {
87
90
  return DEFAULT_SITE_TITLE;
88
91
  }
89
92
 
93
+ function readSearchPageMetadata() {
94
+ if (cachedSearchPageMetadata) return cachedSearchPageMetadata;
95
+ cachedSearchPageMetadata = {
96
+ title: DEFAULT_SEARCH_PAGE_TITLE,
97
+ description: DEFAULT_SEARCH_PAGE_DESCRIPTION,
98
+ };
99
+ try {
100
+ const cfgPath = resolveCanopyConfigPath();
101
+ if (!fs.existsSync(cfgPath)) return cachedSearchPageMetadata;
102
+ const raw = fs.readFileSync(cfgPath, 'utf8');
103
+ const data = yaml.load(raw) || {};
104
+ const searchCfg = data && data.search ? data.search : null;
105
+ const pageCfg = searchCfg && searchCfg.page ? searchCfg.page : null;
106
+ const title = pageCfg && typeof pageCfg.title === 'string' ? pageCfg.title.trim() : '';
107
+ const description =
108
+ pageCfg && typeof pageCfg.description === 'string'
109
+ ? pageCfg.description.trim()
110
+ : '';
111
+ cachedSearchPageMetadata = {
112
+ title: title || DEFAULT_SEARCH_PAGE_TITLE,
113
+ description: description || DEFAULT_SEARCH_PAGE_DESCRIPTION,
114
+ };
115
+ } catch (_) {}
116
+ return cachedSearchPageMetadata;
117
+ }
118
+
90
119
  // Determine the absolute site origin (scheme + host[:port])
91
120
  // Priority:
92
121
  // 1) CANOPY_BASE_URL env
@@ -256,4 +285,7 @@ module.exports = {
256
285
  readSiteMetadata,
257
286
  getSiteTitle,
258
287
  DEFAULT_SITE_TITLE,
288
+ readSearchPageMetadata,
289
+ DEFAULT_SEARCH_PAGE_TITLE,
290
+ DEFAULT_SEARCH_PAGE_DESCRIPTION,
259
291
  };
@@ -11,6 +11,7 @@ const {
11
11
  OUT_DIR,
12
12
  htmlShell,
13
13
  canopyBodyClassForType,
14
+ readSearchPageMetadata,
14
15
  } = require('../common');
15
16
  const { resolveCanopyConfigPath } = require('../config-path');
16
17
 
@@ -259,14 +260,25 @@ async function buildSearchPage() {
259
260
  }
260
261
  const mdx = require('../build/mdx');
261
262
  const searchHref = rootRelativeHref('search.html');
263
+ const searchPageMeta = readSearchPageMetadata() || {};
264
+ const pageTitle =
265
+ typeof searchPageMeta.title === 'string' && searchPageMeta.title.trim()
266
+ ? searchPageMeta.title.trim()
267
+ : 'Search';
268
+ const pageDescription =
269
+ typeof searchPageMeta.description === 'string'
270
+ ? searchPageMeta.description
271
+ : '';
262
272
  const pageDetails = {
263
- title: 'Search',
273
+ title: pageTitle,
274
+ description: pageDescription,
264
275
  href: searchHref,
265
276
  url: searchHref,
266
277
  type: 'search',
267
278
  canonical: searchHref,
268
279
  meta: {
269
- title: 'Search',
280
+ title: pageTitle,
281
+ description: pageDescription,
270
282
  type: 'search',
271
283
  url: searchHref,
272
284
  canonical: searchHref,
@@ -312,7 +324,7 @@ async function buildSearchPage() {
312
324
  }
313
325
  } catch (_) {}
314
326
  const bodyClass = canopyBodyClassForType('search');
315
- let html = htmlShell({ title: 'Search', body, cssHref: null, scriptHref: jsRel, headExtra, bodyClass });
327
+ let html = htmlShell({ title: pageTitle, body, cssHref: null, scriptHref: jsRel, headExtra, bodyClass });
316
328
  try { html = require('../common').applyBaseToHtml(html); } catch (_) {}
317
329
  await fsp.writeFile(outPath, html, 'utf8');
318
330
  console.log('Search: Built', path.relative(process.cwd(), outPath));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-iiif/app",
3
- "version": "1.5.16",
3
+ "version": "1.6.0",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",