@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 +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 +28 -9
- package/ui/dist/index.mjs.map +2 -2
- package/ui/dist/server.mjs +425 -86
- package/ui/dist/server.mjs.map +3 -3
- package/ui/styles/components/_layout.scss +3 -3
- package/ui/styles/components/_nav-tree.scss +12 -5
- package/ui/styles/components/_sub-navigation.scss +69 -38
- package/ui/styles/index.css +78 -38
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
|
@@ -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(
|
|
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(
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
1511
|
+
label: resolvedTitle,
|
|
1493
1512
|
logo: SiteLogo,
|
|
1494
1513
|
href: brandHref,
|
|
1495
1514
|
closeLabel: "Close search",
|