@docpensieve/core 0.4.0-beta.2 → 0.5.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/client/search.js +22 -4
- package/package.json +2 -2
- package/src/authors.js +15 -10
- package/src/config.js +178 -0
- package/src/generator.js +401 -262
- package/src/search-index.js +19 -5
- package/templates/header-menu.hbs +20 -2
- package/templates/layout.hbs +30 -18
- package/types/authors.d.ts +4 -2
- package/types/config.d.ts +30 -0
- package/types/search-index.d.ts +3 -1
package/src/search-index.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* @module @docpensieve/core/search-index
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { UI_STRINGS, pageCount } from '@docpensieve/shared';
|
|
13
|
+
|
|
12
14
|
/** Path of the search page within a version. */
|
|
13
15
|
export const SEARCH_SLUG = 'search';
|
|
14
16
|
|
|
@@ -56,21 +58,33 @@ function escapeHtml(value) {
|
|
|
56
58
|
* @param {{ title: string, url: string, description: string }[]} entries
|
|
57
59
|
* Pages of the version, in reading order.
|
|
58
60
|
* @param {string} indexUrl URL of the version's index.
|
|
61
|
+
* @param {import('@docpensieve/shared').UiStrings} [strings] Wording of the page.
|
|
62
|
+
* @param {string} [lang] Language, which decides the plural of a count.
|
|
59
63
|
* @returns {string}
|
|
60
64
|
*/
|
|
61
|
-
export function searchPageContent(entries, indexUrl) {
|
|
65
|
+
export function searchPageContent(entries, indexUrl, strings = UI_STRINGS.en, lang = 'en') {
|
|
62
66
|
const items = entries.map((entry) => {
|
|
63
67
|
const description = entry.description ? `<p>${escapeHtml(entry.description)}</p>` : '';
|
|
64
68
|
return `<li data-url="${escapeHtml(entry.url)}"><a href="${escapeHtml(entry.url)}">${escapeHtml(entry.title)}</a>${description}</li>`;
|
|
65
69
|
});
|
|
66
70
|
|
|
67
71
|
return [
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
`<h1 id="search">${escapeHtml(strings.searchTitle)}</h1>`,
|
|
73
|
+
// The script is copied as it is, so its wording travels with the page
|
|
74
|
+
// rather than inside the file: one script, every language.
|
|
75
|
+
`<form class="dp-search-page" role="search" data-search-page data-index="${escapeHtml(indexUrl)}" data-strings="${escapeHtml(
|
|
76
|
+
JSON.stringify({
|
|
77
|
+
lang,
|
|
78
|
+
failed: strings.searchIndexFailed,
|
|
79
|
+
pages: strings.pages,
|
|
80
|
+
noResultFor: strings.noResultFor,
|
|
81
|
+
resultsFor: strings.resultsFor,
|
|
82
|
+
}),
|
|
83
|
+
)}">`,
|
|
84
|
+
`<label for="dp-search-query">${escapeHtml(strings.searchTheDocumentation)}</label>`,
|
|
71
85
|
'<input id="dp-search-query" type="search" name="q" autocomplete="off" />',
|
|
72
86
|
'</form>',
|
|
73
|
-
`<p class="dp-search-status" data-search-status aria-live="polite">${entries.length}
|
|
87
|
+
`<p class="dp-search-status" data-search-status aria-live="polite">${escapeHtml(pageCount(entries.length, strings, lang))}.</p>`,
|
|
74
88
|
`<ol class="dp-search-results" data-search-results>${items.join('')}</ol>`,
|
|
75
89
|
].join('');
|
|
76
90
|
}
|
|
@@ -5,7 +5,7 @@ a row on a wide screen, behind a menu button on a narrow one. --}}
|
|
|
5
5
|
<details class="{{{cls.versions}}}">
|
|
6
6
|
{{!-- The accessible name contains the visible label: when dictated by voice,
|
|
7
7
|
what is read on screen must find this button. --}}
|
|
8
|
-
<summary aria-label="
|
|
8
|
+
<summary aria-label="{{ui.versionSwitch}} {{versionName}}, {{ui.switchVersion}}">{{versionName}}</summary>
|
|
9
9
|
<ul class="{{{cls.versionsList}}}">
|
|
10
10
|
{{#each versions}}
|
|
11
11
|
<li><a href="{{url}}"{{#if current}} aria-current="true"{{/if}}>{{name}}</a></li>
|
|
@@ -13,6 +13,24 @@ a row on a wide screen, behind a menu button on a narrow one. --}}
|
|
|
13
13
|
</ul>
|
|
14
14
|
</details>
|
|
15
15
|
{{/if}}
|
|
16
|
+
{{#if languages.length}}
|
|
17
|
+
<details class="{{{cls.languages}}}">
|
|
18
|
+
<summary aria-label="{{ui.language}}">{{ui.language}}</summary>
|
|
19
|
+
<ul class="{{{cls.languagesList}}}">
|
|
20
|
+
{{#each languages}}
|
|
21
|
+
<li>
|
|
22
|
+
{{#if url}}
|
|
23
|
+
<a href="{{url}}"{{#if current}} aria-current="true"{{/if}}>{{label}}</a>
|
|
24
|
+
{{else}}
|
|
25
|
+
{{!-- Not translated: shown so that the reader knows the language exists,
|
|
26
|
+
inert so that they are not sent to a page that does not. --}}
|
|
27
|
+
<span aria-disabled="true">{{label}}</span>
|
|
28
|
+
{{/if}}
|
|
29
|
+
</li>
|
|
30
|
+
{{/each}}
|
|
31
|
+
</ul>
|
|
32
|
+
</details>
|
|
33
|
+
{{/if}}
|
|
16
34
|
{{#if headerLinks.length}}
|
|
17
35
|
<ul class="{{{cls.headerLinks}}}">
|
|
18
36
|
{{#each headerLinks}}
|
|
@@ -43,6 +61,6 @@ a row on a wide screen, behind a menu button on a narrow one. --}}
|
|
|
43
61
|
{{!-- A plain form: it leads to the search page, and needs no script. --}}
|
|
44
62
|
{{#if searchUrl}}
|
|
45
63
|
<form class="{{{cls.search}}}" role="search" action="{{searchUrl}}">
|
|
46
|
-
<input type="search" name="q" placeholder="
|
|
64
|
+
<input type="search" name="q" placeholder="{{ui.search}}" aria-label="{{ui.searchTheDocumentation}}" />
|
|
47
65
|
</form>
|
|
48
66
|
{{/if}}
|
package/templates/layout.hbs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html lang="{{lang}}"{{#if darkModeClass}} class="{{darkModeClass}}"{{/if}}>
|
|
2
|
+
<html lang="{{lang}}" dir="{{dir}}"{{#if darkModeClass}} class="{{darkModeClass}}"{{/if}}>
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
@@ -45,10 +45,22 @@
|
|
|
45
45
|
{{#each scripts}}
|
|
46
46
|
<script type="module" src="{{this}}"></script>
|
|
47
47
|
{{/each}}
|
|
48
|
+
{{!-- The other languages of this page, for search engines: a reader
|
|
49
|
+
looking for the French page must not be sent to the English one. --}}
|
|
50
|
+
{{#each languages}}
|
|
51
|
+
{{#if url}}
|
|
52
|
+
<link rel="alternate" hreflang="{{lang}}" href="{{url}}" />
|
|
53
|
+
{{#if default}}
|
|
54
|
+
{{!-- x-default names the address to serve a reader whose language the site
|
|
55
|
+
does not have: the language it is written in. --}}
|
|
56
|
+
<link rel="alternate" hreflang="x-default" href="{{url}}" />
|
|
57
|
+
{{/if}}
|
|
58
|
+
{{/if}}
|
|
59
|
+
{{/each}}
|
|
48
60
|
{{{jsonld}}}
|
|
49
61
|
</head>
|
|
50
62
|
<body>
|
|
51
|
-
<a class="{{{cls.skip}}}" href="#content">
|
|
63
|
+
<a class="{{{cls.skip}}}" href="#content">{{ui.skipToContent}}</a>
|
|
52
64
|
|
|
53
65
|
{{!-- Target of the back-to-top link: it brings the focus back, not only the
|
|
54
66
|
view. It stays out of the header on purpose — the header is sticky, so it
|
|
@@ -56,28 +68,28 @@
|
|
|
56
68
|
into view scrolled nowhere. --}}
|
|
57
69
|
<div id="top" tabindex="-1"></div>
|
|
58
70
|
|
|
59
|
-
<header class="{{{cls.header}}}">
|
|
71
|
+
<header class="{{#if stickyHeader}}{{{cls.header}}}{{else}}{{{cls.headerStatic}}}{{/if}}">
|
|
60
72
|
{{!-- The logo's alt stays empty: the name that follows already says it. --}}
|
|
61
73
|
<a class="{{{cls.brand}}}" href="{{homeUrl}}">{{#if logoUrl}}<img class="{{{cls.brandLogo}}}" src="{{logoUrl}}" alt="" />{{/if}}{{projectName}}</a>
|
|
62
74
|
{{!-- Shown twice, one at a time: in a row on a wide screen, behind a button
|
|
63
75
|
on a narrow one. The button is a details element, so that the menu opens
|
|
64
76
|
without a script — as the version switcher does. --}}
|
|
65
77
|
{{#if headerMenu}}
|
|
66
|
-
<nav class="{{{cls.headerNav}}}" aria-label="
|
|
78
|
+
<nav class="{{{cls.headerNav}}}" aria-label="{{ui.site}}">
|
|
67
79
|
{{> headerMenu}}
|
|
68
80
|
</nav>
|
|
69
81
|
<details class="{{{cls.menu}}}">
|
|
70
|
-
<summary aria-label="
|
|
82
|
+
<summary aria-label="{{ui.menu}}">
|
|
71
83
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 7h16M4 12h16M4 17h16" /></svg>
|
|
72
84
|
</summary>
|
|
73
|
-
<nav class="{{{cls.menuPanel}}}" aria-label="
|
|
85
|
+
<nav class="{{{cls.menuPanel}}}" aria-label="{{ui.site}}">
|
|
74
86
|
{{> headerMenu}}
|
|
75
87
|
</nav>
|
|
76
88
|
</details>
|
|
77
89
|
{{/if}}
|
|
78
90
|
{{!-- Hidden until its script runs: without JavaScript, it would do nothing. --}}
|
|
79
91
|
{{#if schemeToggle}}
|
|
80
|
-
<button class="{{{cls.schemeToggle}}}" type="button" data-scheme-toggle hidden aria-label="
|
|
92
|
+
<button class="{{{cls.schemeToggle}}}" type="button" data-scheme-toggle hidden aria-label="{{ui.switchScheme}}">
|
|
81
93
|
<svg class="dp-scheme-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" /></svg>
|
|
82
94
|
<svg class="dp-scheme-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="4" /><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" /></svg>
|
|
83
95
|
</button>
|
|
@@ -89,12 +101,12 @@
|
|
|
89
101
|
menu above the content on a narrow one, where a full menu would eat the
|
|
90
102
|
first screen. Only one of the two is displayed at a time. --}}
|
|
91
103
|
{{#if sidebar.length}}
|
|
92
|
-
<nav class="{{{cls.sidebar}}}" aria-label="
|
|
104
|
+
<nav class="{{{cls.sidebar}}}" aria-label="{{ui.documentationNavigation}}">
|
|
93
105
|
{{> navItems sidebar}}
|
|
94
106
|
</nav>
|
|
95
107
|
<details class="{{{cls.sidebarMenu}}}">
|
|
96
|
-
<summary>
|
|
97
|
-
<nav aria-label="
|
|
108
|
+
<summary>{{ui.documentationMenu}}</summary>
|
|
109
|
+
<nav aria-label="{{ui.documentationNavigation}}">
|
|
98
110
|
{{> navItems sidebar}}
|
|
99
111
|
</nav>
|
|
100
112
|
</details>
|
|
@@ -104,11 +116,11 @@
|
|
|
104
116
|
{{#if notice}}
|
|
105
117
|
<aside class="{{{cls.notice}}}" role="note">
|
|
106
118
|
{{#if notice.prerelease}}
|
|
107
|
-
|
|
119
|
+
{{ui.prereleaseNotice}}
|
|
108
120
|
{{else}}
|
|
109
|
-
|
|
121
|
+
{{ui.archivedNotice}}
|
|
110
122
|
{{/if}}
|
|
111
|
-
|
|
123
|
+
{{ui.currentVersionIs}} <a href="{{notice.url}}">{{notice.name}}</a>.
|
|
112
124
|
</aside>
|
|
113
125
|
{{/if}}
|
|
114
126
|
{{!-- Who wrote the page, and when. Absent when the page says neither,
|
|
@@ -139,26 +151,26 @@
|
|
|
139
151
|
text, they say what it was about. Labels, not links — there is no page
|
|
140
152
|
per tag. None on a home page. --}}
|
|
141
153
|
{{#if tags.length}}
|
|
142
|
-
<ul class="{{{cls.tags}}}" aria-label="
|
|
154
|
+
<ul class="{{{cls.tags}}}" aria-label="{{ui.tags}}">
|
|
143
155
|
{{#each tags}}<li class="{{{@root.cls.tag}}}">{{this}}</li>{{/each}}
|
|
144
156
|
</ul>
|
|
145
157
|
{{/if}}
|
|
146
158
|
</main>
|
|
147
159
|
|
|
148
160
|
{{#if toc.length}}
|
|
149
|
-
<nav class="{{{cls.toc}}}" aria-label="
|
|
150
|
-
<p class="{{{cls.tocTitle}}}">
|
|
161
|
+
<nav class="{{{cls.toc}}}" aria-label="{{ui.onThisPage}}">
|
|
162
|
+
<p class="{{{cls.tocTitle}}}">{{ui.onThisPage}}</p>
|
|
151
163
|
{{> tocItems toc}}
|
|
152
164
|
</nav>
|
|
153
165
|
{{/if}}
|
|
154
166
|
</div>
|
|
155
167
|
|
|
156
168
|
<footer class="{{{cls.footer}}}">
|
|
157
|
-
<p>{{projectName}} —
|
|
169
|
+
<p>{{projectName}} — {{ui.versionLabel}} {{versionName}}</p>
|
|
158
170
|
</footer>
|
|
159
171
|
|
|
160
172
|
{{#if scrollToTop}}
|
|
161
|
-
<a class="{{{cls.scrollTop}}}" href="#top" aria-label="
|
|
173
|
+
<a class="{{{cls.scrollTop}}}" href="#top" aria-label="{{ui.backToTop}}">
|
|
162
174
|
<svg
|
|
163
175
|
class="{{{cls.scrollTopIcon}}}"
|
|
164
176
|
viewBox="0 0 24 24"
|
package/types/authors.d.ts
CHANGED
|
@@ -55,10 +55,11 @@ export type Byline = {
|
|
|
55
55
|
* @param {unknown} value
|
|
56
56
|
* @param {string} field Name of the field, for the error message.
|
|
57
57
|
* @param {string} where Page the date comes from.
|
|
58
|
+
* @param {string} [locale] Locale the label is written in. Default: `en-GB`.
|
|
58
59
|
* @returns {{ iso: string, label: string } | null} `null` when absent.
|
|
59
60
|
* @throws {ConfigError} When the value is not a date.
|
|
60
61
|
*/
|
|
61
|
-
export declare function readDate(value: unknown, field: string, where: string): {
|
|
62
|
+
export declare function readDate(value: unknown, field: string, where: string, locale?: string): {
|
|
62
63
|
iso: string;
|
|
63
64
|
label: string;
|
|
64
65
|
} | null;
|
|
@@ -91,7 +92,8 @@ export declare function resolvePageAuthors(value: unknown, table?: Map<string, A
|
|
|
91
92
|
* @param {Record<string, any>} frontmatter
|
|
92
93
|
* @param {Map<string, Author>} [table]
|
|
93
94
|
* @param {string} [where] Page named in a date error.
|
|
95
|
+
* @param {string} [locale] Locale the dates are written in.
|
|
94
96
|
* @returns {Byline | null}
|
|
95
97
|
* @throws {ConfigError} When a date cannot be read.
|
|
96
98
|
*/
|
|
97
|
-
export declare function buildByline(frontmatter: Record<string, any>, table?: Map<string, Author>, where?: string): Byline | null;
|
|
99
|
+
export declare function buildByline(frontmatter: Record<string, any>, table?: Map<string, Author>, where?: string, locale?: string): Byline | null;
|
package/types/config.d.ts
CHANGED
|
@@ -37,6 +37,12 @@ export type Version = {
|
|
|
37
37
|
* Favicon of this version, instead of the project's.
|
|
38
38
|
*/
|
|
39
39
|
favicon?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Folder of each translation,
|
|
42
|
+
* by language code. The pages of the site language stay at the root of the
|
|
43
|
+
* version; a translation is served under its code.
|
|
44
|
+
*/
|
|
45
|
+
translations?: Record<string, string>;
|
|
40
46
|
};
|
|
41
47
|
export type DocPensieveConfig = {
|
|
42
48
|
/**
|
|
@@ -97,11 +103,26 @@ export type DocPensieveConfig = {
|
|
|
97
103
|
* the branch of the page being read.
|
|
98
104
|
*/
|
|
99
105
|
foldedSidebar?: boolean;
|
|
106
|
+
ui?: Record<string, Record<string, string>>;
|
|
107
|
+
/**
|
|
108
|
+
* Kinds of admonition the project adds to the ones shipped. `icon` names an
|
|
109
|
+
* SVG of the version folder, inlined in place of the tone's drawing.
|
|
110
|
+
*/
|
|
111
|
+
admonitions?: Record<string, {
|
|
112
|
+
label: string;
|
|
113
|
+
tone: string;
|
|
114
|
+
icon?: string;
|
|
115
|
+
}>;
|
|
100
116
|
globalComponents: boolean;
|
|
101
117
|
/**
|
|
102
118
|
* Back-to-top button on every page.
|
|
103
119
|
*/
|
|
104
120
|
scrollToTop: boolean;
|
|
121
|
+
/**
|
|
122
|
+
* Header held at the top of the screen.
|
|
123
|
+
* `false` lets it scroll away with the page.
|
|
124
|
+
*/
|
|
125
|
+
stickyHeader?: boolean;
|
|
105
126
|
jsonld: {
|
|
106
127
|
enabled: boolean;
|
|
107
128
|
};
|
|
@@ -153,6 +174,9 @@ export type DocPensieveConfig = {
|
|
|
153
174
|
* reference one. Its pages carry a notice and are not indexed.
|
|
154
175
|
* @property {string} [logo] Logo of this version, instead of the project's.
|
|
155
176
|
* @property {string} [favicon] Favicon of this version, instead of the project's.
|
|
177
|
+
* @property {Record<string, string>} [translations] Folder of each translation,
|
|
178
|
+
* by language code. The pages of the site language stay at the root of the
|
|
179
|
+
* version; a translation is served under its code.
|
|
156
180
|
*/
|
|
157
181
|
/**
|
|
158
182
|
* @typedef {object} DocPensieveConfig
|
|
@@ -169,8 +193,14 @@ export type DocPensieveConfig = {
|
|
|
169
193
|
* `columns` opens a panel of links instead of leading anywhere itself.
|
|
170
194
|
* @property {boolean} [foldedSidebar] Categories of the menu fold, opened on
|
|
171
195
|
* the branch of the page being read.
|
|
196
|
+
* @property {Record<string, Record<string, string>>} [ui]
|
|
197
|
+
* @property {Record<string, { label: string, tone: string, icon?: string }>} [admonitions]
|
|
198
|
+
* Kinds of admonition the project adds to the ones shipped. `icon` names an
|
|
199
|
+
* SVG of the version folder, inlined in place of the tone's drawing.
|
|
172
200
|
* @property {boolean} globalComponents
|
|
173
201
|
* @property {boolean} scrollToTop Back-to-top button on every page.
|
|
202
|
+
* @property {boolean} [stickyHeader] Header held at the top of the screen.
|
|
203
|
+
* `false` lets it scroll away with the page.
|
|
174
204
|
* @property {{ enabled: boolean }} jsonld
|
|
175
205
|
* @property {string} [logo] Image beside the project name, in the header.
|
|
176
206
|
* @property {string} [favicon] Icon of the browser tab: `.ico`, `.png` or `.svg`.
|
package/types/search-index.d.ts
CHANGED
|
@@ -24,10 +24,12 @@ export declare function htmlToText(html: string): string;
|
|
|
24
24
|
* @param {{ title: string, url: string, description: string }[]} entries
|
|
25
25
|
* Pages of the version, in reading order.
|
|
26
26
|
* @param {string} indexUrl URL of the version's index.
|
|
27
|
+
* @param {import('@docpensieve/shared').UiStrings} [strings] Wording of the page.
|
|
28
|
+
* @param {string} [lang] Language, which decides the plural of a count.
|
|
27
29
|
* @returns {string}
|
|
28
30
|
*/
|
|
29
31
|
export declare function searchPageContent(entries: {
|
|
30
32
|
title: string;
|
|
31
33
|
url: string;
|
|
32
34
|
description: string;
|
|
33
|
-
}[], indexUrl: string): string;
|
|
35
|
+
}[], indexUrl: string, strings?: import('@docpensieve/shared').UiStrings, lang?: string): string;
|