@canopy-iiif/app 1.5.14 → 1.5.16

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
@@ -11,6 +11,7 @@ const {
11
11
  CACHE_DIR,
12
12
  ensureDirSync,
13
13
  withBase,
14
+ readSiteMetadata,
14
15
  } = require("../common");
15
16
  let remarkGfm = null;
16
17
  try {
@@ -1043,10 +1044,12 @@ async function compileMdxFile(filePath, outPath, Layout, extraProps = {}) {
1043
1044
  : contentNode;
1044
1045
  const withApp = React.createElement(app.App, null, withLayout);
1045
1046
  const PageContext = getPageContext();
1047
+ const siteMeta = readSiteMetadata();
1046
1048
  const contextValue = {
1047
1049
  navigation:
1048
1050
  extraProps && extraProps.navigation ? extraProps.navigation : null,
1049
1051
  page: extraProps && extraProps.page ? extraProps.page : null,
1052
+ site: siteMeta ? {...siteMeta} : null,
1050
1053
  };
1051
1054
  const withContext = PageContext
1052
1055
  ? React.createElement(PageContext.Provider, {value: contextValue}, withApp)
package/lib/common.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const fsp = fs.promises;
3
3
  const path = require('path');
4
+ const yaml = require('js-yaml');
4
5
  const { resolveCanopyConfigPath } = require('./config-path');
5
6
 
6
7
  const CONTENT_DIR = path.resolve('content');
@@ -13,6 +14,8 @@ const { readBasePath, withBasePath } = require('./base-path');
13
14
  const BASE_PATH = readBasePath();
14
15
  let cachedAppearance = null;
15
16
  let cachedAccent = null;
17
+ let cachedSiteMetadata = null;
18
+ const DEFAULT_SITE_TITLE = 'Site title';
16
19
 
17
20
  function resolveThemeAppearance() {
18
21
  if (cachedAppearance) return cachedAppearance;
@@ -47,17 +50,43 @@ function resolveThemeAccent() {
47
50
 
48
51
  function readYamlConfigBaseUrl() {
49
52
  try {
50
- const y = require('js-yaml');
51
53
  const p = resolveCanopyConfigPath();
52
54
  if (!fs.existsSync(p)) return '';
53
55
  const raw = fs.readFileSync(p, 'utf8');
54
- const data = y.load(raw) || {};
56
+ const data = yaml.load(raw) || {};
55
57
  const site = data && data.site;
56
58
  const url = site && site.baseUrl ? String(site.baseUrl) : '';
57
59
  return url;
58
60
  } catch (_) { return ''; }
59
61
  }
60
62
 
63
+ function readSiteMetadata() {
64
+ if (cachedSiteMetadata) return cachedSiteMetadata;
65
+ cachedSiteMetadata = { title: DEFAULT_SITE_TITLE };
66
+ try {
67
+ const cfgPath = resolveCanopyConfigPath();
68
+ if (!fs.existsSync(cfgPath)) return cachedSiteMetadata;
69
+ const raw = fs.readFileSync(cfgPath, 'utf8');
70
+ const data = yaml.load(raw) || {};
71
+ const directTitle = data && typeof data.title === 'string' ? data.title.trim() : '';
72
+ const nestedTitle =
73
+ data && data.site && typeof data.site.title === 'string'
74
+ ? data.site.title.trim()
75
+ : '';
76
+ const resolved = directTitle || nestedTitle || DEFAULT_SITE_TITLE;
77
+ cachedSiteMetadata = { title: resolved };
78
+ } catch (_) {}
79
+ return cachedSiteMetadata;
80
+ }
81
+
82
+ function getSiteTitle() {
83
+ const site = readSiteMetadata();
84
+ if (site && typeof site.title === 'string' && site.title.trim()) {
85
+ return site.title.trim();
86
+ }
87
+ return DEFAULT_SITE_TITLE;
88
+ }
89
+
61
90
  // Determine the absolute site origin (scheme + host[:port])
62
91
  // Priority:
63
92
  // 1) CANOPY_BASE_URL env
@@ -224,4 +253,7 @@ module.exports = {
224
253
  applyBaseToHtml,
225
254
  rootRelativeHref,
226
255
  canopyBodyClassForType,
256
+ readSiteMetadata,
257
+ getSiteTitle,
258
+ DEFAULT_SITE_TITLE,
227
259
  };
package/lib/head.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const React = require('react');
2
- const { withBase, rootRelativeHref, absoluteUrl } = require('./common');
2
+ const { withBase, rootRelativeHref, absoluteUrl, getSiteTitle } = require('./common');
3
3
  const { getPageContext } = require('./page-context');
4
4
 
5
5
  const DEFAULT_STYLESHEET_PATH = '/styles/styles.css';
@@ -56,10 +56,16 @@ function Meta(props = {}) {
56
56
  page.title ||
57
57
  fallbackTitle;
58
58
  const pageTitle = normalizeText(rawTitle);
59
- const siteTitle = normalizeText(props.siteTitle) || '';
60
- const defaultTitle = siteTitle || 'Canopy IIIF';
59
+ const fallbackSiteTitle = normalizeText(getSiteTitle()) || '';
60
+ const explicitSiteTitle = normalizeText(props.siteTitle) || '';
61
+ const siteTitle = explicitSiteTitle || fallbackSiteTitle;
62
+ const defaultTitle = siteTitle || fallbackSiteTitle || 'Site title';
61
63
  const title = pageTitle ? pageTitle : defaultTitle;
62
- const fullTitle = siteTitle ? (pageTitle ? `${pageTitle} | ${siteTitle}` : siteTitle) : title;
64
+ const fullTitle = siteTitle
65
+ ? pageTitle
66
+ ? `${pageTitle} | ${siteTitle}`
67
+ : siteTitle
68
+ : title;
63
69
  const rawDescription =
64
70
  props.description ||
65
71
  (metaFromPage && metaFromPage.description) ||
@@ -108,6 +114,35 @@ function Meta(props = {}) {
108
114
  if (description) nodes.push(React.createElement('meta', { key: 'twitter-description', name: 'twitter:description', content: description }));
109
115
  if (twitterImage) nodes.push(React.createElement('meta', { key: 'twitter-image', name: 'twitter:image', content: twitterImage }));
110
116
 
117
+ const slug = typeof page.slug === 'string' ? page.slug : '';
118
+ const relPath = typeof page.relativePath === 'string' ? page.relativePath : '';
119
+ const hrefRaw = page && typeof page.href === 'string' ? page.href : '';
120
+ const urlRaw = page && typeof page.url === 'string' ? page.url : '';
121
+ const normalizedHref = hrefRaw ? rootRelativeHref(hrefRaw) : '';
122
+ const normalizedUrl = urlRaw ? rootRelativeHref(urlRaw) : '';
123
+ const isHomepage = !!(
124
+ (slug === '' && page && page.isIndex) ||
125
+ relPath === 'index.mdx' ||
126
+ normalizedHref === '/' ||
127
+ normalizedUrl === '/'
128
+ );
129
+ const siteTitleForJsonLd = siteTitle || fallbackSiteTitle || 'Site title';
130
+ if (isHomepage && siteTitleForJsonLd) {
131
+ const ldPayload = {
132
+ '@context': 'https://schema.org',
133
+ '@type': 'WebSite',
134
+ name: siteTitleForJsonLd,
135
+ url: absoluteUrl('/'),
136
+ };
137
+ nodes.push(
138
+ React.createElement('script', {
139
+ key: 'canopy-website-json-ld',
140
+ type: 'application/ld+json',
141
+ dangerouslySetInnerHTML: { __html: JSON.stringify(ldPayload) },
142
+ })
143
+ );
144
+ }
145
+
111
146
  return React.createElement(React.Fragment, null, nodes);
112
147
  }
113
148
 
@@ -13,7 +13,7 @@ function getGlobalRoot() {
13
13
  function getPageContext() {
14
14
  const root = getGlobalRoot();
15
15
  if (root[CONTEXT_KEY]) return root[CONTEXT_KEY];
16
- const ctx = React.createContext({ navigation: null, page: null });
16
+ const ctx = React.createContext({ navigation: null, page: null, site: null });
17
17
  root[CONTEXT_KEY] = ctx;
18
18
  return ctx;
19
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canopy-iiif/app",
3
- "version": "1.5.14",
3
+ "version": "1.5.16",
4
4
  "private": false,
5
5
  "license": "MIT",
6
6
  "author": "Mat Jordan <mat@northwestern.edu>",
package/ui/dist/index.mjs CHANGED
@@ -809,7 +809,7 @@ function NavigationTreeItem({ node, depth, nodeKey }) {
809
809
  "data-expanded": allowToggle ? defaultExpanded ? "true" : "false" : void 0,
810
810
  "data-default-expanded": allowToggle && defaultExpanded ? "true" : void 0
811
811
  },
812
- /* @__PURE__ */ React14.createElement("div", { className: "canopy-nav-tree__row" }, /* @__PURE__ */ React14.createElement(
812
+ /* @__PURE__ */ React14.createElement("div", { className: "canopy-nav-tree__row" }, /* @__PURE__ */ React14.createElement("div", { className: "canopy-nav-tree__link-wrapper" }, /* @__PURE__ */ React14.createElement(
813
813
  Tag,
814
814
  {
815
815
  className: classes.join(" "),
@@ -819,7 +819,7 @@ function NavigationTreeItem({ node, depth, nodeKey }) {
819
819
  },
820
820
  node.title || node.slug,
821
821
  isRoadmap ? /* @__PURE__ */ React14.createElement("span", { className: "canopy-nav-tree__badge" }, "Roadmap") : null
822
- ), allowToggle ? /* @__PURE__ */ React14.createElement(
822
+ )), allowToggle ? /* @__PURE__ */ React14.createElement(
823
823
  "button",
824
824
  {
825
825
  type: "button",
@@ -839,7 +839,14 @@ function NavigationTreeItem({ node, depth, nodeKey }) {
839
839
  strokeWidth: "1.5",
840
840
  className: "canopy-nav-tree__toggle-icon"
841
841
  },
842
- /* @__PURE__ */ React14.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 9l7 7 7-7" })
842
+ /* @__PURE__ */ React14.createElement(
843
+ "path",
844
+ {
845
+ strokeLinecap: "round",
846
+ strokeLinejoin: "round",
847
+ d: "M5 9l7 7 7-7"
848
+ }
849
+ )
843
850
  ),
844
851
  /* @__PURE__ */ React14.createElement("span", { className: "sr-only" }, toggleLabel)
845
852
  ) : null),
@@ -884,7 +891,14 @@ function NavigationTree({
884
891
  ...rest
885
892
  },
886
893
  heading ? /* @__PURE__ */ React14.createElement("div", { className: headingClassName }, heading) : null,
887
- /* @__PURE__ */ React14.createElement(NavigationTreeList, { nodes, depth: includeRoot ? -1 : 0, parentKey })
894
+ /* @__PURE__ */ React14.createElement(
895
+ NavigationTreeList,
896
+ {
897
+ nodes,
898
+ depth: includeRoot ? -1 : 0,
899
+ parentKey
900
+ }
901
+ )
888
902
  );
889
903
  }
890
904
 
@@ -1197,7 +1211,7 @@ function getSharedRoot() {
1197
1211
  function getSafePageContext() {
1198
1212
  const root = getSharedRoot();
1199
1213
  if (root && root[CONTEXT_KEY]) return root[CONTEXT_KEY];
1200
- const ctx = React15.createContext({ navigation: null, page: null });
1214
+ const ctx = React15.createContext({ navigation: null, page: null, site: null });
1201
1215
  if (root) root[CONTEXT_KEY] = ctx;
1202
1216
  return ctx;
1203
1217
  }
@@ -1264,13 +1278,18 @@ function CanopyHeader(props = {}) {
1264
1278
  searchHotkey = "mod+k",
1265
1279
  searchPlaceholder = "Search\u2026",
1266
1280
  brandHref = "/",
1267
- title = "Canopy IIIF",
1281
+ title: titleProp,
1268
1282
  logo: SiteLogo
1269
1283
  } = props;
1270
1284
  const navLinks = ensureArray(navLinksProp);
1271
1285
  const PageContext = getSafePageContext();
1272
1286
  const context = React15.useContext(PageContext);
1273
1287
  const contextNavigation = context && context.navigation ? context.navigation : null;
1288
+ const contextSite = context && context.site ? context.site : null;
1289
+ const contextSiteTitle = contextSite && typeof contextSite.title === "string" ? contextSite.title.trim() : "";
1290
+ const defaultHeaderTitle = contextSiteTitle || "Site title";
1291
+ const normalizedTitleProp = typeof titleProp === "string" ? titleProp.trim() : "";
1292
+ const resolvedTitle = normalizedTitleProp || defaultHeaderTitle;
1274
1293
  const sectionNavigation = contextNavigation && contextNavigation.root ? contextNavigation : null;
1275
1294
  const navigationRoots = contextNavigation && contextNavigation.allRoots ? contextNavigation.allRoots : null;
1276
1295
  const sectionHeading = sectionNavigation && sectionNavigation.title || (sectionNavigation && sectionNavigation.root ? sectionNavigation.root.title : "");
@@ -1299,7 +1318,7 @@ function CanopyHeader(props = {}) {
1299
1318
  /* @__PURE__ */ React15.createElement("div", { className: "canopy-header__brand" }, /* @__PURE__ */ React15.createElement(
1300
1319
  CanopyBrand,
1301
1320
  {
1302
- label: title,
1321
+ label: resolvedTitle,
1303
1322
  href: brandHref,
1304
1323
  className: "canopy-header__brand-link",
1305
1324
  Logo: SiteLogo
@@ -1394,7 +1413,7 @@ function CanopyHeader(props = {}) {
1394
1413
  id: "canopy-modal-nav",
1395
1414
  variant: "nav",
1396
1415
  labelledBy: "canopy-modal-nav-label",
1397
- label: title,
1416
+ label: resolvedTitle,
1398
1417
  logo: SiteLogo,
1399
1418
  href: brandHref,
1400
1419
  closeLabel: "Close navigation",
@@ -1489,7 +1508,7 @@ function CanopyHeader(props = {}) {
1489
1508
  id: "canopy-modal-search",
1490
1509
  variant: "search",
1491
1510
  labelledBy: "canopy-modal-search-label",
1492
- label: title,
1511
+ label: resolvedTitle,
1493
1512
  logo: SiteLogo,
1494
1513
  href: brandHref,
1495
1514
  closeLabel: "Close search",