@canopy-iiif/app 1.5.15 → 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 +3 -0
- package/lib/common.js +34 -2
- package/lib/head.js +39 -4
- package/lib/page-context.js +1 -1
- package/package.json +1 -1
- package/ui/dist/index.mjs +10 -5
- package/ui/dist/index.mjs.map +2 -2
- package/ui/dist/server.mjs +10 -5
- package/ui/dist/server.mjs.map +2 -2
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 =
|
|
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
|
|
60
|
-
const
|
|
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
|
|
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
|
|
package/lib/page-context.js
CHANGED
|
@@ -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
package/ui/dist/index.mjs
CHANGED
|
@@ -1211,7 +1211,7 @@ function getSharedRoot() {
|
|
|
1211
1211
|
function getSafePageContext() {
|
|
1212
1212
|
const root = getSharedRoot();
|
|
1213
1213
|
if (root && root[CONTEXT_KEY]) return root[CONTEXT_KEY];
|
|
1214
|
-
const ctx = React15.createContext({ navigation: null, page: null });
|
|
1214
|
+
const ctx = React15.createContext({ navigation: null, page: null, site: null });
|
|
1215
1215
|
if (root) root[CONTEXT_KEY] = ctx;
|
|
1216
1216
|
return ctx;
|
|
1217
1217
|
}
|
|
@@ -1278,13 +1278,18 @@ function CanopyHeader(props = {}) {
|
|
|
1278
1278
|
searchHotkey = "mod+k",
|
|
1279
1279
|
searchPlaceholder = "Search\u2026",
|
|
1280
1280
|
brandHref = "/",
|
|
1281
|
-
title
|
|
1281
|
+
title: titleProp,
|
|
1282
1282
|
logo: SiteLogo
|
|
1283
1283
|
} = props;
|
|
1284
1284
|
const navLinks = ensureArray(navLinksProp);
|
|
1285
1285
|
const PageContext = getSafePageContext();
|
|
1286
1286
|
const context = React15.useContext(PageContext);
|
|
1287
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;
|
|
1288
1293
|
const sectionNavigation = contextNavigation && contextNavigation.root ? contextNavigation : null;
|
|
1289
1294
|
const navigationRoots = contextNavigation && contextNavigation.allRoots ? contextNavigation.allRoots : null;
|
|
1290
1295
|
const sectionHeading = sectionNavigation && sectionNavigation.title || (sectionNavigation && sectionNavigation.root ? sectionNavigation.root.title : "");
|
|
@@ -1313,7 +1318,7 @@ function CanopyHeader(props = {}) {
|
|
|
1313
1318
|
/* @__PURE__ */ React15.createElement("div", { className: "canopy-header__brand" }, /* @__PURE__ */ React15.createElement(
|
|
1314
1319
|
CanopyBrand,
|
|
1315
1320
|
{
|
|
1316
|
-
label:
|
|
1321
|
+
label: resolvedTitle,
|
|
1317
1322
|
href: brandHref,
|
|
1318
1323
|
className: "canopy-header__brand-link",
|
|
1319
1324
|
Logo: SiteLogo
|
|
@@ -1408,7 +1413,7 @@ function CanopyHeader(props = {}) {
|
|
|
1408
1413
|
id: "canopy-modal-nav",
|
|
1409
1414
|
variant: "nav",
|
|
1410
1415
|
labelledBy: "canopy-modal-nav-label",
|
|
1411
|
-
label:
|
|
1416
|
+
label: resolvedTitle,
|
|
1412
1417
|
logo: SiteLogo,
|
|
1413
1418
|
href: brandHref,
|
|
1414
1419
|
closeLabel: "Close navigation",
|
|
@@ -1503,7 +1508,7 @@ function CanopyHeader(props = {}) {
|
|
|
1503
1508
|
id: "canopy-modal-search",
|
|
1504
1509
|
variant: "search",
|
|
1505
1510
|
labelledBy: "canopy-modal-search-label",
|
|
1506
|
-
label:
|
|
1511
|
+
label: resolvedTitle,
|
|
1507
1512
|
logo: SiteLogo,
|
|
1508
1513
|
href: brandHref,
|
|
1509
1514
|
closeLabel: "Close search",
|