@docpensieve/core 0.1.5 → 0.2.0-beta.1
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/README.md +4 -0
- package/client/search.js +205 -0
- package/package.json +4 -3
- package/src/compiler.js +76 -2
- package/src/config.js +72 -7
- package/src/discovery.js +128 -0
- package/src/generator.js +227 -35
- package/src/image-size.js +128 -0
- package/src/index.js +2 -1
- package/src/minify-css.js +70 -0
- package/src/search-index.js +76 -0
- package/src/sidebar.js +182 -1
- package/src/structured-data.js +1 -1
- package/templates/layout.hbs +13 -0
- package/types/compiler.d.ts +2 -1
- package/types/config.d.ts +25 -0
- package/types/discovery.d.ts +59 -0
- package/types/generator.d.ts +3 -1
- package/types/image-size.d.ts +22 -0
- package/types/index.d.ts +2 -1
- package/types/minify-css.d.ts +17 -0
- package/types/search-index.d.ts +33 -0
- package/types/sidebar.d.ts +37 -0
- package/types/structured-data.d.ts +10 -0
package/src/sidebar.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @module @docpensieve/core/sidebar
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { humanizeSlug } from '@docpensieve/shared';
|
|
7
|
+
import { ConfigError, humanizeSlug } from '@docpensieve/shared';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @typedef {object} SidebarNode
|
|
@@ -103,3 +103,184 @@ export function collectSectionTitles(docs) {
|
|
|
103
103
|
|
|
104
104
|
return titles;
|
|
105
105
|
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* An entry of a sidebar description: a page path, or an object — see
|
|
109
|
+
* `buildSidebarFromDescription`.
|
|
110
|
+
*
|
|
111
|
+
* @typedef {string | {
|
|
112
|
+
* page?: string, label?: string, items?: SidebarEntry[], href?: string, auto?: string,
|
|
113
|
+
* }} SidebarEntry
|
|
114
|
+
*/
|
|
115
|
+
|
|
116
|
+
/** The kinds of entry, for the hints. */
|
|
117
|
+
const ENTRY_KINDS =
|
|
118
|
+
'Entries: "guide/installation", { "page", "label" }, { "label", "items", "page" }, ' +
|
|
119
|
+
'{ "label", "href" } or { "auto": "folder" }.';
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @param {string} value
|
|
123
|
+
* @returns {string} The value without its leading and trailing slashes.
|
|
124
|
+
*/
|
|
125
|
+
function trimSlashes(value) {
|
|
126
|
+
let start = 0;
|
|
127
|
+
let end = value.length;
|
|
128
|
+
while (start < end && value[start] === '/') start += 1;
|
|
129
|
+
while (end > start && value[end - 1] === '/') end -= 1;
|
|
130
|
+
return value.slice(start, end);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Builds the navigation tree of a version from a description.
|
|
135
|
+
*
|
|
136
|
+
* The description is an array of entries, kept in the order written:
|
|
137
|
+
*
|
|
138
|
+
* - `"guide/installation"` — a page, by its path within the version, as in
|
|
139
|
+
* its URL; `"/"` is the home page. Its title becomes the label.
|
|
140
|
+
* - `{ "page": "guide/installation", "label": "Install" }` — the same, with a
|
|
141
|
+
* label of its own.
|
|
142
|
+
* - `{ "label": "Guide", "items": [ … ], "page": "guide" }` — a category,
|
|
143
|
+
* clickable when it names a page.
|
|
144
|
+
* - `{ "label": "Repository", "href": "https://…" }` — a link outside the site.
|
|
145
|
+
* - `{ "auto": "docpensieve" }` — the automatic tree of a folder: a section
|
|
146
|
+
* keeps its own menu without listing its pages one by one.
|
|
147
|
+
*
|
|
148
|
+
* A page left out stays published: it is only absent from the menu, which is
|
|
149
|
+
* how a page is kept off it.
|
|
150
|
+
*
|
|
151
|
+
* @param {unknown} description Parsed content of the description file.
|
|
152
|
+
* @param {import('./loader.js').Doc[]} docs Documents of the version.
|
|
153
|
+
* @param {(doc: import('./loader.js').Doc) => string} [toUrl] As for
|
|
154
|
+
* `buildSidebar`.
|
|
155
|
+
* @param {{ source?: string }} [options] `source` names the file in messages.
|
|
156
|
+
* @returns {SidebarNode[]}
|
|
157
|
+
* @throws {ConfigError} For a path that names no page, a page listed twice,
|
|
158
|
+
* or an entry of no known kind.
|
|
159
|
+
*/
|
|
160
|
+
export function buildSidebarFromDescription(
|
|
161
|
+
description,
|
|
162
|
+
docs,
|
|
163
|
+
toUrl = (doc) => doc.url,
|
|
164
|
+
options = {},
|
|
165
|
+
) {
|
|
166
|
+
const source = options.source ?? 'The sidebar description';
|
|
167
|
+
const bySlug = new Map(docs.map((doc) => [doc.slug, doc]));
|
|
168
|
+
/** @type {Set<string>} */
|
|
169
|
+
const listed = new Set();
|
|
170
|
+
|
|
171
|
+
/** @param {string} message @param {string} hint */
|
|
172
|
+
const fail = (message, hint) => new ConfigError(`${source}: ${message}`, { hint });
|
|
173
|
+
|
|
174
|
+
/** @param {string} slug @returns {string} Up to five known paths near it. */
|
|
175
|
+
const nearby = (slug) => {
|
|
176
|
+
const known = [...bySlug.keys()];
|
|
177
|
+
const first = slug.split('/')[0];
|
|
178
|
+
const close = known.filter((candidate) => candidate.split('/')[0] === first);
|
|
179
|
+
return (close.length > 0 ? close : known)
|
|
180
|
+
.slice(0, 5)
|
|
181
|
+
.map((candidate) => `"${candidate || '/'}"`)
|
|
182
|
+
.join(', ');
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/** @param {string} slug */
|
|
186
|
+
const claim = (slug) => {
|
|
187
|
+
// The menu marks a single entry as the current page: listed twice, a page
|
|
188
|
+
// would light up in two places, or in the wrong one.
|
|
189
|
+
if (listed.has(slug)) {
|
|
190
|
+
throw fail(`the page "${slug || '/'}" is listed twice.`, 'List each page once.');
|
|
191
|
+
}
|
|
192
|
+
listed.add(slug);
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/** @param {unknown} raw @returns {import('./loader.js').Doc} */
|
|
196
|
+
const pageOf = (raw) => {
|
|
197
|
+
const slug = trimSlashes(String(raw));
|
|
198
|
+
const doc = bySlug.get(slug);
|
|
199
|
+
if (!doc) {
|
|
200
|
+
throw fail(
|
|
201
|
+
`no page "${String(raw)}".`,
|
|
202
|
+
`A page is named by its path within the version, as in its URL. Close to it: ${nearby(slug)}.`,
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
claim(slug);
|
|
206
|
+
return doc;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/** @param {import('./loader.js').Doc} doc @returns {string} */
|
|
210
|
+
const titleOf = (doc) =>
|
|
211
|
+
String(
|
|
212
|
+
doc.frontmatter?.title ?? (doc.slug ? humanizeSlug(doc.slug.split('/').pop() ?? '') : 'Home'),
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
/** @param {unknown} entry @returns {SidebarNode[]} */
|
|
216
|
+
const expand = (entry) => {
|
|
217
|
+
if (typeof entry === 'string') {
|
|
218
|
+
const doc = pageOf(entry);
|
|
219
|
+
return [{ label: titleOf(doc), url: toUrl(doc), items: [] }];
|
|
220
|
+
}
|
|
221
|
+
if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
222
|
+
throw fail(
|
|
223
|
+
`an entry must be a page path or an object: ${JSON.stringify(entry)}.`,
|
|
224
|
+
ENTRY_KINDS,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const item = /** @type {Record<string, unknown>} */ (entry);
|
|
229
|
+
const label = typeof item.label === 'string' && item.label ? item.label : undefined;
|
|
230
|
+
|
|
231
|
+
if (item.href !== undefined) {
|
|
232
|
+
if (!label || typeof item.href !== 'string') {
|
|
233
|
+
throw fail(`a link needs a label and an href: ${JSON.stringify(entry)}.`, ENTRY_KINDS);
|
|
234
|
+
}
|
|
235
|
+
return [{ label, url: item.href, items: [] }];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (item.auto !== undefined) {
|
|
239
|
+
const folder = trimSlashes(String(item.auto));
|
|
240
|
+
const inside = docs.filter(
|
|
241
|
+
(doc) => !folder || doc.slug === folder || doc.slug.startsWith(`${folder}/`),
|
|
242
|
+
);
|
|
243
|
+
if (inside.length === 0) {
|
|
244
|
+
throw fail(
|
|
245
|
+
`the folder "${String(item.auto)}" holds no page.`,
|
|
246
|
+
`Name a folder of the version, as in its URLs. Close to it: ${nearby(folder)}.`,
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
for (const doc of inside) claim(doc.slug);
|
|
250
|
+
|
|
251
|
+
const tree = buildSidebar(inside, toUrl);
|
|
252
|
+
if (!folder) return tree;
|
|
253
|
+
// The tree starts at the version's root, one node per level down to the
|
|
254
|
+
// folder, since every page shares its path.
|
|
255
|
+
let [node] = tree;
|
|
256
|
+
for (let depth = 1; depth < folder.split('/').length; depth += 1) [node] = node.items;
|
|
257
|
+
return [{ ...node, label: label ?? node.label }];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (item.items !== undefined) {
|
|
261
|
+
if (!label || !Array.isArray(item.items)) {
|
|
262
|
+
throw fail(
|
|
263
|
+
`a category needs a label and a list of items: ${JSON.stringify(entry)}.`,
|
|
264
|
+
ENTRY_KINDS,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
const url = item.page !== undefined ? toUrl(pageOf(item.page)) : null;
|
|
268
|
+
return [{ label, url, items: item.items.flatMap(expand) }];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (item.page !== undefined) {
|
|
272
|
+
const doc = pageOf(item.page);
|
|
273
|
+
return [{ label: label ?? titleOf(doc), url: toUrl(doc), items: [] }];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
throw fail(
|
|
277
|
+
`${JSON.stringify(entry)} is neither a page, a category, a link nor an automatic folder.`,
|
|
278
|
+
ENTRY_KINDS,
|
|
279
|
+
);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
if (!Array.isArray(description)) {
|
|
283
|
+
throw fail('it must hold an array of entries.', ENTRY_KINDS);
|
|
284
|
+
}
|
|
285
|
+
return description.flatMap(expand);
|
|
286
|
+
}
|
package/src/structured-data.js
CHANGED
|
@@ -24,7 +24,7 @@ const ABSOLUTE_URL = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
|
|
|
24
24
|
* @param {unknown} value
|
|
25
25
|
* @returns {string | undefined} `'2026-01-15'`, or `undefined` when unusable.
|
|
26
26
|
*/
|
|
27
|
-
function toISODate(value) {
|
|
27
|
+
export function toISODate(value) {
|
|
28
28
|
if (value === undefined || value === null || value === '') return undefined;
|
|
29
29
|
|
|
30
30
|
/*
|
package/templates/layout.hbs
CHANGED
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
{{#if canonical}}
|
|
14
14
|
<link rel="canonical" href="{{canonical}}" />
|
|
15
15
|
{{/if}}
|
|
16
|
+
{{#if feedUrl}}
|
|
17
|
+
<link rel="alternate" type="application/rss+xml" title="{{projectName}}" href="{{feedUrl}}" />
|
|
18
|
+
{{/if}}
|
|
16
19
|
{{#if favicon}}
|
|
17
20
|
<link rel="icon" href="{{favicon.href}}" type="{{favicon.type}}" />
|
|
18
21
|
{{/if}}
|
|
@@ -33,6 +36,10 @@
|
|
|
33
36
|
<link rel="preload" as="{{as}}" href="{{href}}" />
|
|
34
37
|
{{/each}}
|
|
35
38
|
<link rel="stylesheet" href="{{cssHref}}" />
|
|
39
|
+
{{!-- Only the search page carries a script: content pages load none. --}}
|
|
40
|
+
{{#each scripts}}
|
|
41
|
+
<script type="module" src="{{this}}"></script>
|
|
42
|
+
{{/each}}
|
|
36
43
|
{{{jsonld}}}
|
|
37
44
|
</head>
|
|
38
45
|
<body>
|
|
@@ -54,6 +61,12 @@
|
|
|
54
61
|
</ul>
|
|
55
62
|
</details>
|
|
56
63
|
{{/if}}
|
|
64
|
+
{{!-- A plain form: it leads to the search page, and needs no script. --}}
|
|
65
|
+
{{#if searchUrl}}
|
|
66
|
+
<form class="{{{cls.search}}}" role="search" action="{{searchUrl}}">
|
|
67
|
+
<input type="search" name="q" placeholder="Search" aria-label="Search the documentation" />
|
|
68
|
+
</form>
|
|
69
|
+
{{/if}}
|
|
57
70
|
</header>
|
|
58
71
|
|
|
59
72
|
<div class="{{#if wide}}{{{cls.shellWide}}}{{else}}{{{cls.shell}}}{{/if}}">
|
package/types/compiler.d.ts
CHANGED
|
@@ -93,7 +93,7 @@ export declare class Compiler {
|
|
|
93
93
|
* Compiles a source into an HTML fragment and a table of contents.
|
|
94
94
|
*
|
|
95
95
|
* @param {string} source Markdown/MDX content, frontmatter already removed.
|
|
96
|
-
* @param {{ filepath?: string, url?: string, dirUrl?: string, basePath?: string }} [context]
|
|
96
|
+
* @param {{ filepath?: string, url?: string, dirUrl?: string, basePath?: string, sourceDir?: string }} [context]
|
|
97
97
|
* `filepath` locates errors, `dirUrl` is the base of relative targets and
|
|
98
98
|
* `basePath` prefixes absolute targets (the version root).
|
|
99
99
|
* @returns {Promise<CompileResult>}
|
|
@@ -104,5 +104,6 @@ export declare class Compiler {
|
|
|
104
104
|
url?: string;
|
|
105
105
|
dirUrl?: string;
|
|
106
106
|
basePath?: string;
|
|
107
|
+
sourceDir?: string;
|
|
107
108
|
}): Promise<CompileResult>;
|
|
108
109
|
}
|
package/types/config.d.ts
CHANGED
|
@@ -29,6 +29,14 @@ export type Version = {
|
|
|
29
29
|
* reference one. Its pages carry a notice and are not indexed.
|
|
30
30
|
*/
|
|
31
31
|
prerelease?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Logo of this version, instead of the project's.
|
|
34
|
+
*/
|
|
35
|
+
logo?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Favicon of this version, instead of the project's.
|
|
38
|
+
*/
|
|
39
|
+
favicon?: string;
|
|
32
40
|
};
|
|
33
41
|
export type DocPensieveConfig = {
|
|
34
42
|
/**
|
|
@@ -82,6 +90,18 @@ export type DocPensieveConfig = {
|
|
|
82
90
|
* Preview of a shared page. Needs `siteUrl`.
|
|
83
91
|
*/
|
|
84
92
|
socialImage?: string;
|
|
93
|
+
/**
|
|
94
|
+
* `sitemap.xml` of the published versions.
|
|
95
|
+
*/
|
|
96
|
+
sitemap?: boolean;
|
|
97
|
+
/**
|
|
98
|
+
* RSS feed of the dated pages of the current version.
|
|
99
|
+
*/
|
|
100
|
+
feed?: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Search field, index and search page of each version.
|
|
103
|
+
*/
|
|
104
|
+
search?: boolean;
|
|
85
105
|
/**
|
|
86
106
|
* Project root, set by `loadConfig`.
|
|
87
107
|
*/
|
|
@@ -104,6 +124,8 @@ export type DocPensieveConfig = {
|
|
|
104
124
|
* @property {boolean} [archived] Version kept but no longer maintained.
|
|
105
125
|
* @property {boolean} [prerelease] Version in preparation, not yet the
|
|
106
126
|
* reference one. Its pages carry a notice and are not indexed.
|
|
127
|
+
* @property {string} [logo] Logo of this version, instead of the project's.
|
|
128
|
+
* @property {string} [favicon] Favicon of this version, instead of the project's.
|
|
107
129
|
*/
|
|
108
130
|
/**
|
|
109
131
|
* @typedef {object} DocPensieveConfig
|
|
@@ -120,6 +142,9 @@ export type DocPensieveConfig = {
|
|
|
120
142
|
* @property {string} [logo] Image beside the project name, in the header.
|
|
121
143
|
* @property {string} [favicon] Icon of the browser tab: `.ico`, `.png` or `.svg`.
|
|
122
144
|
* @property {string} [socialImage] Preview of a shared page. Needs `siteUrl`.
|
|
145
|
+
* @property {boolean} [sitemap] `sitemap.xml` of the published versions.
|
|
146
|
+
* @property {boolean} [feed] RSS feed of the dated pages of the current version.
|
|
147
|
+
* @property {boolean} [search] Search field, index and search page of each version.
|
|
123
148
|
* @property {string} [rootDir] Project root, set by `loadConfig`.
|
|
124
149
|
* @property {string} [configFile] Path of the configuration file, set by `loadConfig`.
|
|
125
150
|
* @property {string} [lang] Document language, `'en'` by default.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a site offers the programs that read it: the sitemap for search
|
|
3
|
+
* engines, `robots.txt` that points to it, and the RSS feed of dated pages.
|
|
4
|
+
*
|
|
5
|
+
* Pure functions: the generator gathers the published pages and writes the
|
|
6
|
+
* files.
|
|
7
|
+
*
|
|
8
|
+
* @module @docpensieve/core/discovery
|
|
9
|
+
*/
|
|
10
|
+
export type PublishedPage = {
|
|
11
|
+
/**
|
|
12
|
+
* Page URL, deployment prefix included
|
|
13
|
+
* (`/docs/versions/v1.0/guide/`).
|
|
14
|
+
*/
|
|
15
|
+
url: string;
|
|
16
|
+
/**
|
|
17
|
+
* Frontmatter of its source.
|
|
18
|
+
*/
|
|
19
|
+
frontmatter: Record<string, any>;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Builds `sitemap.xml`.
|
|
23
|
+
*
|
|
24
|
+
* `lastmod` is the page's `modified` date, or failing that its `date`; a page
|
|
25
|
+
* that carries neither is listed without one rather than with a made-up date.
|
|
26
|
+
*
|
|
27
|
+
* @param {PublishedPage[]} pages Pages of the versions to list.
|
|
28
|
+
* @param {string} siteUrl Public address of the site: the sitemap only holds
|
|
29
|
+
* absolute addresses.
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
export declare function buildSitemap(pages: PublishedPage[], siteUrl: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Builds `robots.txt`, which lets every crawler in and names the sitemap.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} sitemapUrl Absolute address of the sitemap.
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildRobots(sitemapUrl: string): string;
|
|
40
|
+
/**
|
|
41
|
+
* Builds the RSS feed of the dated pages, newest first.
|
|
42
|
+
*
|
|
43
|
+
* Only a page with a `date` enters it: a documentation page without one is
|
|
44
|
+
* reference material, not news, and dating it at build time would announce
|
|
45
|
+
* every page again at every build.
|
|
46
|
+
*
|
|
47
|
+
* @param {PublishedPage[]} pages Pages of the current version.
|
|
48
|
+
* @param {{
|
|
49
|
+
* projectName: string, siteUrl: string, homeUrl: string, feedUrl: string, lang?: string,
|
|
50
|
+
* }} site `homeUrl` and `feedUrl` are absolute.
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
export declare function buildFeed(pages: PublishedPage[], site: {
|
|
54
|
+
projectName: string;
|
|
55
|
+
siteUrl: string;
|
|
56
|
+
homeUrl: string;
|
|
57
|
+
feedUrl: string;
|
|
58
|
+
lang?: string;
|
|
59
|
+
}): string;
|
package/types/generator.d.ts
CHANGED
|
@@ -61,12 +61,14 @@ export declare class SiteGenerator {
|
|
|
61
61
|
*
|
|
62
62
|
* @param {string} versionSlug Slug of the version to generate.
|
|
63
63
|
* @param {string} outDir Output folder of that version.
|
|
64
|
-
* @returns {Promise<{ pages: number, outDir: string }>}
|
|
64
|
+
* @returns {Promise<{ pages: number, outDir: string, published: import('./discovery.js').PublishedPage[] }>}
|
|
65
|
+
* `published` lists the pages as the site serves them, for the sitemap and the feed.
|
|
65
66
|
* @throws {GeneratorError} Write failure.
|
|
66
67
|
*/
|
|
67
68
|
buildVersion(versionSlug: string, outDir: string): Promise<{
|
|
68
69
|
pages: number;
|
|
69
70
|
outDir: string;
|
|
71
|
+
published: import('./discovery.js').PublishedPage[];
|
|
70
72
|
}>;
|
|
71
73
|
/**
|
|
72
74
|
* Generates every declared version, plus `versions.json` and a root that
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dimensions of an image, read from the first bytes of its file.
|
|
3
|
+
*
|
|
4
|
+
* The build writes them on every image of a page: the browser then keeps the
|
|
5
|
+
* room before the image arrives, instead of shifting the text when it does.
|
|
6
|
+
* Only the header of each format is read — no decoding, no dependency.
|
|
7
|
+
*
|
|
8
|
+
* @module @docpensieve/core/image-size
|
|
9
|
+
*/
|
|
10
|
+
export type ImageSize = {
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Reads the dimensions of an image from its content.
|
|
16
|
+
*
|
|
17
|
+
* @param {Buffer} bytes Content of the file — its first kilobytes are enough.
|
|
18
|
+
* @param {string} extension Extension of the file, dot included.
|
|
19
|
+
* @returns {ImageSize | null} `null` for an unknown or damaged format: the
|
|
20
|
+
* image is then written without dimensions, as before.
|
|
21
|
+
*/
|
|
22
|
+
export declare function imageSize(bytes: Buffer, extension: string): ImageSize | null;
|
package/types/index.d.ts
CHANGED
|
@@ -31,4 +31,5 @@ export { DocLoader } from './loader.js';
|
|
|
31
31
|
export { Compiler } from './compiler.js';
|
|
32
32
|
export { StructuredDataBuilder } from './structured-data.js';
|
|
33
33
|
export { SiteGenerator } from './generator.js';
|
|
34
|
-
export { buildSidebar, collectSectionTitles } from './sidebar.js';
|
|
34
|
+
export { buildSidebar, buildSidebarFromDescription, collectSectionTitles } from './sidebar.js';
|
|
35
|
+
export { buildFeed, buildRobots, buildSitemap } from './discovery.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minification of the produced stylesheet.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately cautious: comments go, and so does the whitespace that nothing
|
|
5
|
+
* reads — indentation, line breaks, the space around `{`, `}` and `;`. Every
|
|
6
|
+
* other space stays one space, since some carry meaning (`.a :hover` is not
|
|
7
|
+
* `.a:hover`), and the content of a string is never touched. That keeps most
|
|
8
|
+
* of the gain of a real minifier without its risk of changing what a rule
|
|
9
|
+
* means.
|
|
10
|
+
*
|
|
11
|
+
* @module @docpensieve/core/minify-css
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} css
|
|
15
|
+
* @returns {string} The same rules, lighter.
|
|
16
|
+
*/
|
|
17
|
+
export declare function minifyCss(css: string): string;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the build prepares for search: the index of a version, and its search
|
|
3
|
+
* page.
|
|
4
|
+
*
|
|
5
|
+
* Search is built here, not in the reader's browser: the index holds the
|
|
6
|
+
* plain text of every page, and the search page already lists every page,
|
|
7
|
+
* so that it is useful before any script runs — and without one.
|
|
8
|
+
*
|
|
9
|
+
* @module @docpensieve/core/search-index
|
|
10
|
+
*/
|
|
11
|
+
/** Path of the search page within a version. */
|
|
12
|
+
export declare const SEARCH_SLUG = "search";
|
|
13
|
+
/**
|
|
14
|
+
* Turns rendered HTML into the plain text a reader sees.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} html
|
|
17
|
+
* @returns {string}
|
|
18
|
+
*/
|
|
19
|
+
export declare function htmlToText(html: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Renders the content of the search page: a field, and the list of every
|
|
22
|
+
* page of the version, which the script filters.
|
|
23
|
+
*
|
|
24
|
+
* @param {{ title: string, url: string, description: string }[]} entries
|
|
25
|
+
* Pages of the version, in reading order.
|
|
26
|
+
* @param {string} indexUrl URL of the version's index.
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
export declare function searchPageContent(entries: {
|
|
30
|
+
title: string;
|
|
31
|
+
url: string;
|
|
32
|
+
description: string;
|
|
33
|
+
}[], indexUrl: string): string;
|
package/types/sidebar.d.ts
CHANGED
|
@@ -58,3 +58,40 @@ export declare function buildSidebar(docs: import('./loader.js').Doc[], toUrl?:
|
|
|
58
58
|
* @returns {Record<string, string>} Full folder slug to title.
|
|
59
59
|
*/
|
|
60
60
|
export declare function collectSectionTitles(docs: import('./loader.js').Doc[]): Record<string, string>;
|
|
61
|
+
export type SidebarEntry = string | {
|
|
62
|
+
page?: string;
|
|
63
|
+
label?: string;
|
|
64
|
+
items?: SidebarEntry[];
|
|
65
|
+
href?: string;
|
|
66
|
+
auto?: string;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Builds the navigation tree of a version from a description.
|
|
70
|
+
*
|
|
71
|
+
* The description is an array of entries, kept in the order written:
|
|
72
|
+
*
|
|
73
|
+
* - `"guide/installation"` — a page, by its path within the version, as in
|
|
74
|
+
* its URL; `"/"` is the home page. Its title becomes the label.
|
|
75
|
+
* - `{ "page": "guide/installation", "label": "Install" }` — the same, with a
|
|
76
|
+
* label of its own.
|
|
77
|
+
* - `{ "label": "Guide", "items": [ … ], "page": "guide" }` — a category,
|
|
78
|
+
* clickable when it names a page.
|
|
79
|
+
* - `{ "label": "Repository", "href": "https://…" }` — a link outside the site.
|
|
80
|
+
* - `{ "auto": "docpensieve" }` — the automatic tree of a folder: a section
|
|
81
|
+
* keeps its own menu without listing its pages one by one.
|
|
82
|
+
*
|
|
83
|
+
* A page left out stays published: it is only absent from the menu, which is
|
|
84
|
+
* how a page is kept off it.
|
|
85
|
+
*
|
|
86
|
+
* @param {unknown} description Parsed content of the description file.
|
|
87
|
+
* @param {import('./loader.js').Doc[]} docs Documents of the version.
|
|
88
|
+
* @param {(doc: import('./loader.js').Doc) => string} [toUrl] As for
|
|
89
|
+
* `buildSidebar`.
|
|
90
|
+
* @param {{ source?: string }} [options] `source` names the file in messages.
|
|
91
|
+
* @returns {SidebarNode[]}
|
|
92
|
+
* @throws {ConfigError} For a path that names no page, a page listed twice,
|
|
93
|
+
* or an entry of no known kind.
|
|
94
|
+
*/
|
|
95
|
+
export declare function buildSidebarFromDescription(description: unknown, docs: import('./loader.js').Doc[], toUrl?: (doc: import('./loader.js').Doc) => string, options?: {
|
|
96
|
+
source?: string;
|
|
97
|
+
}): SidebarNode[];
|
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module @docpensieve/core/structured-data
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* Normalises a frontmatter date into a short ISO date.
|
|
8
|
+
*
|
|
9
|
+
* YAML turns `date: 2026-01-15` into a `Date` object, but a quoted date stays
|
|
10
|
+
* a string: both forms must come out the same.
|
|
11
|
+
*
|
|
12
|
+
* @param {unknown} value
|
|
13
|
+
* @returns {string | undefined} `'2026-01-15'`, or `undefined` when unusable.
|
|
14
|
+
*/
|
|
15
|
+
export declare function toISODate(value: unknown): string | undefined;
|
|
6
16
|
/** Assembles a schema.org graph for a page. */
|
|
7
17
|
export declare class StructuredDataBuilder {
|
|
8
18
|
#private;
|