@iyulab/canopy-page 0.6.0 → 0.8.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/CHANGELOG.md +48 -0
- package/README.md +7 -2
- package/dist/assets-bundle.d.ts +11 -2
- package/dist/assets-bundle.js +20 -3
- package/dist/build.js +2 -1
- package/dist/check.js +36 -5
- package/dist/settings.d.ts +40 -0
- package/dist/settings.js +35 -10
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,54 @@ Notable changes to canopy-page. The format follows
|
|
|
7
7
|
The `settings.json` contract is what consuming projects plan their upgrades around, so changes
|
|
8
8
|
to it — its fields, its validation, and what the checks reject — are what this file is about.
|
|
9
9
|
|
|
10
|
+
## [0.8.0] — 2026-08-11
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`strings` gains `indexTitle`, `backlinks`, and `searchFailed`.** The `strings` field
|
|
15
|
+
covered the reader chrome's search/theme-toggle/navigation text, but three more reader-facing
|
|
16
|
+
literals stayed hardcoded English regardless of `lang` and `strings`: the auto-generated
|
|
17
|
+
contents page's title/heading (`indexTitle`), a page's "Linked references" section heading
|
|
18
|
+
(`backlinks`), and the client search's failure message (`searchFailed`). All three follow the
|
|
19
|
+
same pattern as the existing five keys — optional override, English default when unset.
|
|
20
|
+
- **`$schema` support.** A settings file can now name
|
|
21
|
+
[`settings.schema.json`](https://iyulab.github.io/canopy-page/settings.schema.json), hosted
|
|
22
|
+
at that fixed URL, so an editor offers completion and inline validation for every field —
|
|
23
|
+
canopy-page itself already read and ignored this key, unvalidated.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **Upgraded to canopy 0.9.0**, the release `indexTitle` and `backlinks` build on.
|
|
28
|
+
|
|
29
|
+
## [0.7.0] — 2026-08-09
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- **A `strings` field** overriding the reader chrome's own built-in text — search, the theme
|
|
34
|
+
toggle, and the navigation landmarks. `lang` only ever changed what `<html lang>` declares;
|
|
35
|
+
this text is canopy's own UI, not vault content, so it stayed English regardless. No built-in
|
|
36
|
+
translation table, the same reasoning `home.label` already follows. Threaded to canopy as a
|
|
37
|
+
JSON `--strings` flag.
|
|
38
|
+
- **`check` warns about a root-absolute reference that resolves today but would only be correct
|
|
39
|
+
if the site is served from the domain root**, whenever `siteUrl` already declares a non-root
|
|
40
|
+
mount path — reusing the one signal a settings file already carries about where the site is
|
|
41
|
+
served from, rather than requiring a new field.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- **`home.url` no longer requires an absolute http(s) URL.** A relative one (a sibling of the
|
|
46
|
+
published site, at the same origin) is now resolved against each page's depth, the same as
|
|
47
|
+
every other internal link canopy writes — useful when the site is mounted at a sub-path whose
|
|
48
|
+
absolute origin differs between environments.
|
|
49
|
+
- **Upgraded to canopy 0.8.0**, the release the relative `home.url` and `strings` support build on.
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
|
|
53
|
+
- **A `siteUrl` missing its host (`"http://"` alone, for instance) no longer crashes `check`.**
|
|
54
|
+
Only the scheme was validated, so `new URL()` threw on the rest and took the whole check run
|
|
55
|
+
down with it; a `siteUrl` that fails to parse is now treated as absent for the sub-path warning,
|
|
56
|
+
leaving `sitemapXml` to report the malformed value on its own terms.
|
|
57
|
+
|
|
10
58
|
## [0.6.0] — 2026-08-09
|
|
11
59
|
|
|
12
60
|
### Added
|
package/README.md
CHANGED
|
@@ -87,6 +87,7 @@ unpublished.
|
|
|
87
87
|
|
|
88
88
|
```json
|
|
89
89
|
{
|
|
90
|
+
"$schema": "https://iyulab.github.io/canopy-page/settings.schema.json",
|
|
90
91
|
"title": "Product Help",
|
|
91
92
|
"description": "How to use it",
|
|
92
93
|
"lang": "en-GB",
|
|
@@ -105,13 +106,15 @@ unpublished.
|
|
|
105
106
|
|
|
106
107
|
| Field | Meaning |
|
|
107
108
|
|---|---|
|
|
109
|
+
| `$schema` | Optional. Points an editor (VS Code, JetBrains) at [`settings.schema.json`](https://iyulab.github.io/canopy-page/settings.schema.json) for completion and inline validation. Read and ignored by canopy-page itself |
|
|
108
110
|
| `title` | Site name. Defaults to the folder's name |
|
|
109
111
|
| `description` | Fills `<meta name="description">`, which is what link previews show |
|
|
110
112
|
| `lang` | BCP 47 tag for `<html lang>`. Worth setting for any non-English site: assistive technology reads pronunciation from it |
|
|
113
|
+
| `strings` | Overrides for the reader chrome's own text — `search`, `toggleTheme`, `siteNav`, `pageNav`, `onThisPage`, `indexTitle` (the auto-generated contents page's title/heading), `backlinks` (a page's "linked references" heading), `searchFailed` (the client search's failure message). `lang` only changes what `<html lang>` declares; this text is canopy's own UI or canopy-page's own search script, not vault content, so it stays English otherwise. No built-in translation table — the same reasoning `home.label` already follows: link text has to be written in the site's own language. Keys left out keep their English default |
|
|
111
114
|
| `icon` | Favicon, relative to the settings file. Must be a published file |
|
|
112
115
|
| `tokens` | CSS of design-token overrides, relative to the settings file. Appended *after* canopy's own tokens, so a file naming one value keeps the rest. It is configuration rather than content, so — unlike `icon` and `logo` — it is excluded from the published site automatically. Absent: canopy's default palette |
|
|
113
116
|
| `logo` | Image shown beside the site title, relative to the settings file. Must be a published file — the opposite direction from `tokens`, because this one is content. Rendered with an empty `alt`, deliberately: the site title beside it already names the site, so there is no separate text to give it. Absent: the sidebar header shows the title text alone |
|
|
114
|
-
| `home` | A link back to the site this documentation sits beside: `{ url, label }`. Both are required together — naming half of it is not a valid setting. `url`
|
|
117
|
+
| `home` | A link back to the site this documentation sits beside: `{ url, label }`. Both are required together — naming half of it is not a valid setting. `url` is absolute when the target is a different origin, relative when it is a sibling of the published site (each page resolves it against its own depth, the same as every other internal link); there is no default `label`, because link text has to be written in the site's own language. Absent: no link back to a surrounding site is rendered |
|
|
115
118
|
| `siteUrl` | Absolute URL naming where the built site will stand. Every link canopy writes is relative, which is what lets a site be served from any sub-path — and exactly why a sitemap, whose entries must be absolute, needs this separately. **Only** when it is set does `build` write `sitemap.xml` and a `robots.txt` pointing at it. Absent: neither file is written |
|
|
116
119
|
| `exclude` | Paths to leave unpublished: a directory (`_drafts` or `_drafts/**`), an extension at any depth (`*.tmp`), or one exact path. Patterns are relative to the settings file, and a shape outside that list — `images/*.md` — is refused rather than left to match nothing |
|
|
117
120
|
| `rehypePlugins` | Package names of rehype plugins to run on every page, after canopy's own sanitize step and before syntax highlighting — canopy's fixed extension point for markdown that needs more than CommonMark and GFM, a diagram fence rendered to SVG being the case this exists for. Each entry is an installed package name (`"rehype-declart"`), never a filesystem path — a relative-looking entry is refused, since the directory it would resolve against is wherever the build happens to run from, not this file |
|
|
@@ -208,7 +211,9 @@ Warnings — reported, and the build continues:
|
|
|
208
211
|
path resolves depends on what the site is served from, which is not a checker's to know — but a
|
|
209
212
|
site served from its own root is the ordinary case, and a `public/`-style folder that other
|
|
210
213
|
generators map onto the root does not exist here, so these silently 404. A warning rather than
|
|
211
|
-
an error, because mounting the site elsewhere would make it right
|
|
214
|
+
an error, because mounting the site elsewhere would make it right. When `siteUrl` already
|
|
215
|
+
declares a sub-path mount, a root-absolute reference warns even if it resolves today, since that
|
|
216
|
+
is the one case the checker can actually judge
|
|
212
217
|
- An `exclude` pattern that matched nothing, which usually means a path written from the wrong
|
|
213
218
|
place. Extension patterns are left alone: `*.tmp` in a site with no scratch files is a rule
|
|
214
219
|
about what may never ship, not a claim that something is there
|
package/dist/assets-bundle.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The single script every canopy-page site carries via canopy's `--script`.
|
|
3
|
+
*
|
|
4
|
+
* `searchFailed` overrides the message `search.js` shows when its fetch of the
|
|
5
|
+
* search index fails — the one reader-facing string in canopy-page's own
|
|
6
|
+
* assets, `settings.strings.searchFailed` in the settings surface. The other
|
|
7
|
+
* two files carry no site-specific text, so only `search.js` takes this
|
|
8
|
+
* substitution; a source literal, not a template placeholder, so the shipped
|
|
9
|
+
* asset stays valid, readable JavaScript on its own.
|
|
10
|
+
*/
|
|
11
|
+
export declare function assembleScript(searchFailed?: string): Promise<string>;
|
|
3
12
|
/**
|
|
4
13
|
* CSS canopy-page contributes on top of a site's own tokens, carried via
|
|
5
14
|
* canopy's `--tokens-css` — the same channel a site's own `settings.tokens`
|
package/dist/assets-bundle.js
CHANGED
|
@@ -21,14 +21,31 @@ const ASSETS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), "asse
|
|
|
21
21
|
async function readAsset(name) {
|
|
22
22
|
return readFile(path.join(ASSETS_DIR, name), "utf8");
|
|
23
23
|
}
|
|
24
|
-
/** The
|
|
25
|
-
|
|
24
|
+
/** The literal `search.js` falls back to when no override is given — the substitution target. */
|
|
25
|
+
const SEARCH_FAILED_DEFAULT = "Search failed to load.";
|
|
26
|
+
/**
|
|
27
|
+
* The single script every canopy-page site carries via canopy's `--script`.
|
|
28
|
+
*
|
|
29
|
+
* `searchFailed` overrides the message `search.js` shows when its fetch of the
|
|
30
|
+
* search index fails — the one reader-facing string in canopy-page's own
|
|
31
|
+
* assets, `settings.strings.searchFailed` in the settings surface. The other
|
|
32
|
+
* two files carry no site-specific text, so only `search.js` takes this
|
|
33
|
+
* substitution; a source literal, not a template placeholder, so the shipped
|
|
34
|
+
* asset stays valid, readable JavaScript on its own.
|
|
35
|
+
*/
|
|
36
|
+
export async function assembleScript(searchFailed) {
|
|
26
37
|
const [search, scrollspy, themeToggle] = await Promise.all([
|
|
27
38
|
readAsset("search.js"),
|
|
28
39
|
readAsset("scrollspy.js"),
|
|
29
40
|
readAsset("theme-toggle.js"),
|
|
30
41
|
]);
|
|
31
|
-
|
|
42
|
+
// A function replacer, not a replacement string: String.replace treats
|
|
43
|
+
// "$&"/"$'"/"$$" etc. in a replacement string as patterns, and a site
|
|
44
|
+
// author's searchFailed text is free to contain a literal "$".
|
|
45
|
+
const searchWithStrings = searchFailed === undefined
|
|
46
|
+
? search
|
|
47
|
+
: search.replace(JSON.stringify(SEARCH_FAILED_DEFAULT), () => JSON.stringify(searchFailed));
|
|
48
|
+
return `${searchWithStrings}\n${scrollspy}\n${themeToggle}`;
|
|
32
49
|
}
|
|
33
50
|
/**
|
|
34
51
|
* CSS canopy-page contributes on top of a site's own tokens, carried via
|
package/dist/build.js
CHANGED
|
@@ -34,6 +34,7 @@ export function canopyArgs(site, out, navPath, searchAssets) {
|
|
|
34
34
|
...(settings.home === undefined
|
|
35
35
|
? []
|
|
36
36
|
: ["--home-url", settings.home.url, "--home-label", settings.home.label]),
|
|
37
|
+
...(settings.strings === undefined ? [] : ["--strings", JSON.stringify(settings.strings)]),
|
|
37
38
|
...(navPath === undefined ? [] : ["--nav", navPath]),
|
|
38
39
|
// Always on, same reasoning as --tokens-css above: a search index and the
|
|
39
40
|
// script that searches it are canopy-page's own contribution, not a site
|
|
@@ -79,7 +80,7 @@ export async function buildSite({ dir, out }) {
|
|
|
79
80
|
const tokensCssPath = path.join(workDir, "tokens.css");
|
|
80
81
|
await writeFile(tokensCssPath, await assembleTokensCss(userTokensCss), "utf8");
|
|
81
82
|
const scriptPath = path.join(workDir, "script.js");
|
|
82
|
-
await writeFile(scriptPath, await assembleScript(), "utf8");
|
|
83
|
+
await writeFile(scriptPath, await assembleScript(site.settings.strings?.searchFailed), "utf8");
|
|
83
84
|
const code = await runCanopy(canopyArgs(site, path.resolve(out), navPath, { tokensCssPath, scriptPath }));
|
|
84
85
|
// Only after canopy succeeded, and only over what it actually wrote: a
|
|
85
86
|
// sitemap listing pages a failed build never produced would be a lie a
|
package/dist/check.js
CHANGED
|
@@ -28,6 +28,31 @@ import { toPageKey } from "./vault.js";
|
|
|
28
28
|
function isRootAbsolute(url) {
|
|
29
29
|
return url.startsWith("/") && !url.startsWith("//");
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* The mount path `siteUrl` declares, when it says the site stands under a
|
|
33
|
+
* sub-path rather than a domain root.
|
|
34
|
+
*
|
|
35
|
+
* `siteUrl` is the one place settings already say where the site is served
|
|
36
|
+
* from — set today only to address a sitemap, but its path component answers
|
|
37
|
+
* exactly the question a root-absolute reference otherwise leaves open.
|
|
38
|
+
*/
|
|
39
|
+
function siteBasePath(site) {
|
|
40
|
+
const { siteUrl } = site.settings;
|
|
41
|
+
if (siteUrl === undefined)
|
|
42
|
+
return undefined;
|
|
43
|
+
// settings.ts only checks the "http(s)://" prefix, which "http://" itself
|
|
44
|
+
// satisfies without naming a host — malformed enough that `new URL` throws.
|
|
45
|
+
// That is a settings mistake for `sitemapXml` to report, not a reason for
|
|
46
|
+
// this unrelated check to crash the whole run.
|
|
47
|
+
let pathname;
|
|
48
|
+
try {
|
|
49
|
+
({ pathname } = new URL(siteUrl));
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
return pathname === "/" ? undefined : pathname;
|
|
55
|
+
}
|
|
31
56
|
/**
|
|
32
57
|
* Does anything published sit at this path — a page, a copied file, or the index
|
|
33
58
|
* page a directory is entered by?
|
|
@@ -81,14 +106,20 @@ export async function referenceFindings(site) {
|
|
|
81
106
|
const url = targetPath(reference.target);
|
|
82
107
|
if (isRootAbsolute(url)) {
|
|
83
108
|
const atRoot = decodeTarget(url.replace(/^\/+/, "")) ?? url.replace(/^\/+/, "");
|
|
84
|
-
|
|
109
|
+
const resolves = atRoot === "" || existsInSite(site, atRoot);
|
|
110
|
+
const basePath = siteBasePath(site);
|
|
111
|
+
if (resolves && basePath === undefined)
|
|
85
112
|
continue;
|
|
86
113
|
findings.push({
|
|
87
114
|
level: "warning",
|
|
88
|
-
message:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
115
|
+
message: resolves
|
|
116
|
+
? `${where}: ${reference.kind} "${reference.target}" resolves only when the ` +
|
|
117
|
+
`site is served from the domain root, but settings.siteUrl declares it is ` +
|
|
118
|
+
`mounted under "${basePath}"`
|
|
119
|
+
: `${where}: ${reference.kind} "${reference.target}" — ` +
|
|
120
|
+
`nothing is published at "${atRoot}". A root-absolute path resolves ` +
|
|
121
|
+
"against wherever the site is served from, so this is right only if " +
|
|
122
|
+
"something else answers it there",
|
|
92
123
|
});
|
|
93
124
|
continue;
|
|
94
125
|
}
|
package/dist/settings.d.ts
CHANGED
|
@@ -111,7 +111,47 @@ export interface Settings {
|
|
|
111
111
|
* relative path against.
|
|
112
112
|
*/
|
|
113
113
|
rehypePlugins?: string[];
|
|
114
|
+
/**
|
|
115
|
+
* Overrides for the reader chrome's own text — search, the theme toggle,
|
|
116
|
+
* and the navigation landmarks.
|
|
117
|
+
*
|
|
118
|
+
* `lang` changes what `<html lang>` declares, but that text is canopy's own
|
|
119
|
+
* UI, not vault content, so `lang` alone leaves it English. There is no
|
|
120
|
+
* built-in translation table: like `home.label`, link text has to be
|
|
121
|
+
* written in the site's own language, and canopy cannot know what that
|
|
122
|
+
* language calls "Search". Keys left out keep their English default.
|
|
123
|
+
*/
|
|
124
|
+
strings?: {
|
|
125
|
+
search?: string;
|
|
126
|
+
toggleTheme?: string;
|
|
127
|
+
siteNav?: string;
|
|
128
|
+
pageNav?: string;
|
|
129
|
+
onThisPage?: string;
|
|
130
|
+
/** Title and heading of the auto-generated contents page at the site root. */
|
|
131
|
+
indexTitle?: string;
|
|
132
|
+
/** Heading over a page's list of pages that link to it. */
|
|
133
|
+
backlinks?: string;
|
|
134
|
+
/**
|
|
135
|
+
* Message shown in place of results when the client search index fails to
|
|
136
|
+
* load. This key rides the same JSON `--strings` flag as every other one
|
|
137
|
+
* here (canopy just never reads it), but it is consumed by canopy-page's
|
|
138
|
+
* own script assembly (`assembleScript`), not by canopy — every other key
|
|
139
|
+
* styles canopy's own shell markup, this one styles canopy-page's own
|
|
140
|
+
* search UI (see `assets/search.js`).
|
|
141
|
+
*/
|
|
142
|
+
searchFailed?: string;
|
|
143
|
+
};
|
|
114
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Exported so `settings.schema.json` (`examples/site/`) can be tested against
|
|
147
|
+
* the parser's own allowlists rather than a hand-copied duplicate — the two
|
|
148
|
+
* are otherwise free to drift silently apart as fields are added.
|
|
149
|
+
*/
|
|
150
|
+
export declare const SETTINGS_KEYS: Set<string>;
|
|
151
|
+
export declare const SECTION_KEYS: Set<string>;
|
|
152
|
+
export declare const HOME_KEYS: Set<string>;
|
|
153
|
+
export declare const STRINGS_KEYS: Set<string>;
|
|
154
|
+
export declare const NAV_ITEM_KEYS: Set<string>;
|
|
115
155
|
/**
|
|
116
156
|
* Parse and validate a settings file from JSON text.
|
|
117
157
|
*
|
package/dist/settings.js
CHANGED
|
@@ -38,7 +38,12 @@ function fail(message) {
|
|
|
38
38
|
* so it has to survive a strict key check.
|
|
39
39
|
*/
|
|
40
40
|
const IGNORED_KEYS = new Set(["$schema"]);
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Exported so `settings.schema.json` (`examples/site/`) can be tested against
|
|
43
|
+
* the parser's own allowlists rather than a hand-copied duplicate — the two
|
|
44
|
+
* are otherwise free to drift silently apart as fields are added.
|
|
45
|
+
*/
|
|
46
|
+
export const SETTINGS_KEYS = new Set([
|
|
42
47
|
"title",
|
|
43
48
|
"description",
|
|
44
49
|
"lang",
|
|
@@ -50,10 +55,21 @@ const SETTINGS_KEYS = new Set([
|
|
|
50
55
|
"home",
|
|
51
56
|
"siteUrl",
|
|
52
57
|
"rehypePlugins",
|
|
58
|
+
"strings",
|
|
53
59
|
]);
|
|
54
|
-
const SECTION_KEYS = new Set(["path", "label", "order", "items"]);
|
|
55
|
-
const HOME_KEYS = new Set(["url", "label"]);
|
|
56
|
-
const
|
|
60
|
+
export const SECTION_KEYS = new Set(["path", "label", "order", "items"]);
|
|
61
|
+
export const HOME_KEYS = new Set(["url", "label"]);
|
|
62
|
+
export const STRINGS_KEYS = new Set([
|
|
63
|
+
"search",
|
|
64
|
+
"toggleTheme",
|
|
65
|
+
"siteNav",
|
|
66
|
+
"pageNav",
|
|
67
|
+
"onThisPage",
|
|
68
|
+
"indexTitle",
|
|
69
|
+
"backlinks",
|
|
70
|
+
"searchFailed",
|
|
71
|
+
]);
|
|
72
|
+
export const NAV_ITEM_KEYS = new Set(["label", "path", "items"]);
|
|
57
73
|
/**
|
|
58
74
|
* Unknown keys are rejected rather than ignored.
|
|
59
75
|
*
|
|
@@ -227,7 +243,7 @@ export function parseSettings(json) {
|
|
|
227
243
|
}
|
|
228
244
|
const value = asObject(raw, "settings", "expected a JSON object");
|
|
229
245
|
rejectUnknownKeys(value, SETTINGS_KEYS, "settings");
|
|
230
|
-
const { title, description, lang, icon, tokens, exclude, sections, logo, home, siteUrl, rehypePlugins } = value;
|
|
246
|
+
const { title, description, lang, icon, tokens, exclude, sections, logo, home, siteUrl, rehypePlugins, strings, } = value;
|
|
231
247
|
if (title !== undefined)
|
|
232
248
|
asString(title, "settings.title");
|
|
233
249
|
if (description !== undefined)
|
|
@@ -256,12 +272,11 @@ export function parseSettings(json) {
|
|
|
256
272
|
fail('settings.home.url: needed alongside "label"');
|
|
257
273
|
if (object.label === undefined)
|
|
258
274
|
fail('settings.home.label: needed alongside "url"');
|
|
275
|
+
// Absolute when the target is a different origin, relative when it is a
|
|
276
|
+
// sibling of the published site (a product this documentation is mounted
|
|
277
|
+
// beside) — canopy resolves a relative one against each page's depth, the
|
|
278
|
+
// same way it resolves every other internal link.
|
|
259
279
|
const url = asString(object.url, "settings.home.url");
|
|
260
|
-
// The target is normally outside the published site, so a relative path has
|
|
261
|
-
// nothing here to resolve against.
|
|
262
|
-
if (!/^https?:\/\//i.test(url)) {
|
|
263
|
-
fail(`settings.home.url: "${url}" must be an absolute http(s) URL`);
|
|
264
|
-
}
|
|
265
280
|
parsedHome = { url, label: asString(object.label, "settings.home.label") };
|
|
266
281
|
}
|
|
267
282
|
if (siteUrl !== undefined) {
|
|
@@ -270,6 +285,15 @@ export function parseSettings(json) {
|
|
|
270
285
|
fail(`settings.siteUrl: "${url}" must be an absolute http(s) URL`);
|
|
271
286
|
}
|
|
272
287
|
}
|
|
288
|
+
let parsedStrings;
|
|
289
|
+
if (strings !== undefined) {
|
|
290
|
+
const object = asObject(strings, "settings.strings", "expected an object");
|
|
291
|
+
rejectUnknownKeys(object, STRINGS_KEYS, "settings.strings");
|
|
292
|
+
parsedStrings = {};
|
|
293
|
+
for (const key of Object.keys(object)) {
|
|
294
|
+
parsedStrings[key] = asString(object[key], `settings.strings.${key}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
273
297
|
return {
|
|
274
298
|
...(title === undefined ? {} : { title: title }),
|
|
275
299
|
...(description === undefined ? {} : { description: description }),
|
|
@@ -292,6 +316,7 @@ export function parseSettings(json) {
|
|
|
292
316
|
...(logo === undefined ? {} : { logo: asRelativePath(logo, "settings.logo") }),
|
|
293
317
|
...(parsedHome === undefined ? {} : { home: parsedHome }),
|
|
294
318
|
...(siteUrl === undefined ? {} : { siteUrl: siteUrl }),
|
|
319
|
+
...(parsedStrings === undefined ? {} : { strings: parsedStrings }),
|
|
295
320
|
...(rehypePlugins === undefined
|
|
296
321
|
? {}
|
|
297
322
|
: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iyulab/canopy-page",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Authoring pipeline for documentation sites: one settings file, integrity checks, and a build.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -46,11 +46,13 @@
|
|
|
46
46
|
"@biomejs/biome": "^2.5.0",
|
|
47
47
|
"@iyulab/declart": "^0.20.0",
|
|
48
48
|
"@types/node": "^25.9.3",
|
|
49
|
+
"playwright": "^1.62.1",
|
|
49
50
|
"rehype-declart": "^0.20.0",
|
|
51
|
+
"rehype-mermaid": "^3.0.0",
|
|
50
52
|
"typescript": "^6.0.3",
|
|
51
53
|
"vitest": "^4.1.9"
|
|
52
54
|
},
|
|
53
55
|
"dependencies": {
|
|
54
|
-
"@iyulab/canopy": "^0.
|
|
56
|
+
"@iyulab/canopy": "^0.9.0"
|
|
55
57
|
}
|
|
56
58
|
}
|