@astrojs/starlight 0.37.3 → 0.37.4
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 +16 -0
- package/components/SidebarSublist.astro +1 -1
- package/components/TableOfContents/starlight-toc.ts +31 -17
- package/index.ts +4 -32
- package/integrations/pagefind.ts +60 -0
- package/package.json +1 -1
- package/translations/index.ts +2 -0
- package/translations/th.json +30 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# @astrojs/starlight
|
|
2
2
|
|
|
3
|
+
## 0.37.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#3534](https://github.com/withastro/starlight/pull/3534) [`703fab0`](https://github.com/withastro/starlight/commit/703fab085b99303c0c01325c9bb869ea7e1418c4) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes support for running builds when `npx` is unavailable.
|
|
8
|
+
|
|
9
|
+
Previously, Starlight would spawn a process to run the Pagefind search indexing binary using `npx`. On platforms where `npx` isn’t available, this could cause issues. Starlight now runs Pagefind using its Node.js API to avoid a separate process. As a side effect, you may notice that logging during builds is now less verbose.
|
|
10
|
+
|
|
11
|
+
- [#3656](https://github.com/withastro/starlight/pull/3656) [`a0e6368`](https://github.com/withastro/starlight/commit/a0e636838092d30cb6b8f80e5535ad842e52d759) Thanks [@delucis](https://github.com/delucis)! - Fixes several edge cases in highlighting the current page heading in Starlight’s table of contents
|
|
12
|
+
|
|
13
|
+
- [#3663](https://github.com/withastro/starlight/pull/3663) [`00cbf00`](https://github.com/withastro/starlight/commit/00cbf001fee4fd59f351c7a6c0f8c353c7c41f13) Thanks [@lines-of-codes](https://github.com/lines-of-codes)! - Adds Thai language support
|
|
14
|
+
|
|
15
|
+
- [#3658](https://github.com/withastro/starlight/pull/3658) [`ac79329`](https://github.com/withastro/starlight/commit/ac793290f0dbd21f9b9a5d6f60aa315043815227) Thanks [@delucis](https://github.com/delucis)! - Avoids adding redundant `aria-current="false"` attributes to sidebar entries
|
|
16
|
+
|
|
17
|
+
- [#3382](https://github.com/withastro/starlight/pull/3382) [`db295c2`](https://github.com/withastro/starlight/commit/db295c2a3d75aad71a41702f33001195d89de5d2) Thanks [@trueberryless](https://github.com/trueberryless)! - Fixes an issue where the mobile table of contents is unable to find the first heading when a page has a tall banner.
|
|
18
|
+
|
|
3
19
|
## 0.37.3
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
|
@@ -20,7 +20,7 @@ const { sublist, nested } = Astro.props;
|
|
|
20
20
|
{entry.type === 'link' ? (
|
|
21
21
|
<a
|
|
22
22
|
href={entry.href}
|
|
23
|
-
aria-current={entry.isCurrent
|
|
23
|
+
aria-current={entry.isCurrent ? 'page' : undefined}
|
|
24
24
|
class:list={[{ large: !nested }, entry.attrs.class]}
|
|
25
25
|
{...entry.attrs}
|
|
26
26
|
>
|
|
@@ -5,6 +5,14 @@ export class StarlightTOC extends HTMLElement {
|
|
|
5
5
|
private minH = parseInt(this.dataset.minH || '2', 10);
|
|
6
6
|
private maxH = parseInt(this.dataset.maxH || '3', 10);
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* CSS selector string that matches only headings that can appear in the table of contents.
|
|
10
|
+
* Generates a selector like `h1#_top,:where(h2,h3)[id]`.
|
|
11
|
+
*/
|
|
12
|
+
private tocHeadingSelector =
|
|
13
|
+
`h1#${PAGE_TITLE_ID},` +
|
|
14
|
+
`:where(${[...Array.from({ length: 1 + this.maxH - this.minH }).map((_, index) => `h${this.minH + index}`)].join()})[id]`;
|
|
15
|
+
|
|
8
16
|
protected set current(link: HTMLAnchorElement) {
|
|
9
17
|
if (link === this._current) return;
|
|
10
18
|
if (this._current) this._current.removeAttribute('aria-current');
|
|
@@ -25,26 +33,22 @@ export class StarlightTOC extends HTMLElement {
|
|
|
25
33
|
const links = [...this.querySelectorAll('a')];
|
|
26
34
|
|
|
27
35
|
/** Test if an element is a table-of-contents heading. */
|
|
28
|
-
const isHeading = (el: Element): el is HTMLHeadingElement =>
|
|
29
|
-
|
|
30
|
-
// Special case for page title h1
|
|
31
|
-
if (el.id === PAGE_TITLE_ID) return true;
|
|
32
|
-
// Check the heading level is within the user-configured limits for the ToC
|
|
33
|
-
const level = el.tagName[1];
|
|
34
|
-
if (level) {
|
|
35
|
-
const int = parseInt(level, 10);
|
|
36
|
-
if (int >= this.minH && int <= this.maxH) return true;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
return false;
|
|
40
|
-
};
|
|
36
|
+
const isHeading = (el: Element): el is HTMLHeadingElement =>
|
|
37
|
+
el.matches(this.tocHeadingSelector);
|
|
41
38
|
|
|
42
39
|
/** Walk up the DOM to find the nearest heading. */
|
|
43
40
|
const getElementHeading = (el: Element | null): HTMLHeadingElement | null => {
|
|
44
41
|
if (!el) return null;
|
|
45
42
|
const origin = el;
|
|
46
43
|
while (el) {
|
|
44
|
+
// Short circuit if we reach the top-level content container or one of the other containers in main.
|
|
45
|
+
if (el.matches('.sl-markdown-content, main > *')) {
|
|
46
|
+
return document.getElementById(PAGE_TITLE_ID) as HTMLHeadingElement;
|
|
47
|
+
}
|
|
47
48
|
if (isHeading(el)) return el;
|
|
49
|
+
// Find the first heading that is a child of this element, and return it if there is one.
|
|
50
|
+
const childHeading = el.querySelector<HTMLHeadingElement>(this.tocHeadingSelector);
|
|
51
|
+
if (childHeading) return childHeading;
|
|
48
52
|
// Assign the previous sibling’s last, most deeply nested child to el.
|
|
49
53
|
el = el.previousElementSibling;
|
|
50
54
|
while (el?.lastElementChild) {
|
|
@@ -72,10 +76,20 @@ export class StarlightTOC extends HTMLElement {
|
|
|
72
76
|
}
|
|
73
77
|
};
|
|
74
78
|
|
|
75
|
-
// Observe
|
|
76
|
-
//
|
|
77
|
-
// the
|
|
78
|
-
|
|
79
|
+
// Observe the following elements:
|
|
80
|
+
// - headings that appear in the table of contents
|
|
81
|
+
// - siblings of those headings or of the `.sl-heading-wrapper` added by Starlight’s anchor links feature
|
|
82
|
+
// - direct children of `.sl-markdown-content` to include elements before the first subheading
|
|
83
|
+
// - direct children of `main` that don’t include headings (mainly to target Starlight’s banner)
|
|
84
|
+
// Ignore any elements that themselves contain a table-of-contents heading, as we are already observing those children.
|
|
85
|
+
const toObserve = document.querySelectorAll(
|
|
86
|
+
[
|
|
87
|
+
`main :where(${this.tocHeadingSelector})`,
|
|
88
|
+
`main :where(${this.tocHeadingSelector}, .sl-heading-wrapper) ~ *:not(:has(${this.tocHeadingSelector}))`,
|
|
89
|
+
`main .sl-markdown-content > *:not(:has(${this.tocHeadingSelector}))`,
|
|
90
|
+
`main > *:not(:has(${this.tocHeadingSelector}))`,
|
|
91
|
+
].join()
|
|
92
|
+
);
|
|
79
93
|
|
|
80
94
|
let observer: IntersectionObserver | undefined;
|
|
81
95
|
const observe = () => {
|
package/index.ts
CHANGED
|
@@ -8,11 +8,8 @@
|
|
|
8
8
|
/// <reference path="./virtual.d.ts" />
|
|
9
9
|
|
|
10
10
|
import mdx from '@astrojs/mdx';
|
|
11
|
-
import type { AstroIntegration
|
|
11
|
+
import type { AstroIntegration } from 'astro';
|
|
12
12
|
import { AstroError } from 'astro/errors';
|
|
13
|
-
import { spawn } from 'node:child_process';
|
|
14
|
-
import { dirname, relative } from 'node:path';
|
|
15
|
-
import { fileURLToPath } from 'node:url';
|
|
16
13
|
import {
|
|
17
14
|
starlightRehypePlugins,
|
|
18
15
|
starlightRemarkPlugins,
|
|
@@ -20,6 +17,7 @@ import {
|
|
|
20
17
|
} from './integrations/remark-rehype';
|
|
21
18
|
import { starlightDirectivesRestorationIntegration } from './integrations/asides';
|
|
22
19
|
import { starlightExpressiveCode } from './integrations/expressive-code/index';
|
|
20
|
+
import { starlightPagefind } from './integrations/pagefind';
|
|
23
21
|
import { starlightSitemap } from './integrations/sitemap';
|
|
24
22
|
import { vitePluginStarlightCssLayerOrder } from './integrations/vite-layer-order';
|
|
25
23
|
import { vitePluginStarlightUserConfig } from './integrations/virtual-user-config';
|
|
@@ -148,36 +146,10 @@ export default function StarlightIntegration(
|
|
|
148
146
|
injectPluginTranslationsTypes(pluginTranslations, injectTypes);
|
|
149
147
|
},
|
|
150
148
|
|
|
151
|
-
'astro:build:done': (
|
|
149
|
+
'astro:build:done': async (options) => {
|
|
152
150
|
if (!userConfig.pagefind) return;
|
|
153
|
-
|
|
154
|
-
const targetDir = fileURLToPath(dir);
|
|
155
|
-
const cwd = dirname(fileURLToPath(import.meta.url));
|
|
156
|
-
const relativeDir = relative(cwd, targetDir);
|
|
157
|
-
return new Promise<void>((resolve) => {
|
|
158
|
-
spawn('npx', ['-y', 'pagefind', ...loglevelFlag, '--site', relativeDir], {
|
|
159
|
-
stdio: 'inherit',
|
|
160
|
-
shell: true,
|
|
161
|
-
cwd,
|
|
162
|
-
}).on('close', () => resolve());
|
|
163
|
-
});
|
|
151
|
+
return starlightPagefind(options);
|
|
164
152
|
},
|
|
165
153
|
},
|
|
166
154
|
};
|
|
167
155
|
}
|
|
168
|
-
|
|
169
|
-
/** Map the logging level of Astro’s logger to one of Pagefind’s logging level flags. */
|
|
170
|
-
function getPagefindLoggingFlags(level: AstroIntegrationLogger['options']['level']) {
|
|
171
|
-
switch (level) {
|
|
172
|
-
case 'silent':
|
|
173
|
-
case 'error':
|
|
174
|
-
return ['--silent'];
|
|
175
|
-
case 'warn':
|
|
176
|
-
return ['--quiet'];
|
|
177
|
-
case 'debug':
|
|
178
|
-
return ['--verbose'];
|
|
179
|
-
case 'info':
|
|
180
|
-
default:
|
|
181
|
-
return [];
|
|
182
|
-
}
|
|
183
|
-
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { HookParameters } from 'astro';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import * as pagefind from 'pagefind';
|
|
4
|
+
|
|
5
|
+
/** Run Pagefind to generate search index files based on the build output directory. */
|
|
6
|
+
export async function starlightPagefind({
|
|
7
|
+
dir,
|
|
8
|
+
logger: starlightLogger,
|
|
9
|
+
}: PagefindIntegrationOptions) {
|
|
10
|
+
const logger = starlightLogger.fork('starlight:pagefind');
|
|
11
|
+
const options = { dir, logger };
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const now = performance.now();
|
|
15
|
+
logger.info('Building search index with Pagefind...');
|
|
16
|
+
|
|
17
|
+
const newIndexResponse = await pagefind.createIndex();
|
|
18
|
+
|
|
19
|
+
const { index } = assertPagefindResponse<pagefind.NewIndexResponse>(newIndexResponse, options);
|
|
20
|
+
|
|
21
|
+
const indexingResponse = await index.addDirectory({ path: fileURLToPath(dir) });
|
|
22
|
+
const { page_count } = assertPagefindResponse<pagefind.IndexingResponse>(
|
|
23
|
+
indexingResponse,
|
|
24
|
+
options
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
logger.info(`Found ${page_count} HTML files.`);
|
|
28
|
+
|
|
29
|
+
const writeFilesResponse = await index.writeFiles({
|
|
30
|
+
outputPath: fileURLToPath(new URL('./pagefind/', dir)),
|
|
31
|
+
});
|
|
32
|
+
assertPagefindResponse<pagefind.WriteFilesResponse>(writeFilesResponse, options);
|
|
33
|
+
|
|
34
|
+
const pagefindTime = performance.now() - now;
|
|
35
|
+
logger.info(
|
|
36
|
+
`Finished building search index in ${pagefindTime < 750 ? `${Math.round(pagefindTime)}ms` : `${(pagefindTime / 1000).toFixed(2)}s`}.`
|
|
37
|
+
);
|
|
38
|
+
} catch (cause) {
|
|
39
|
+
throw new Error('Failed to run Pagefind.', { cause });
|
|
40
|
+
} finally {
|
|
41
|
+
await pagefind.close();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function assertPagefindResponse<T extends PagefindBaseResponse>(
|
|
46
|
+
response: T,
|
|
47
|
+
{ logger }: PagefindIntegrationOptions
|
|
48
|
+
) {
|
|
49
|
+
if (response.errors.length > 0) {
|
|
50
|
+
for (const error of response.errors) logger.error(`Pagefind error: ${error}`);
|
|
51
|
+
throw new Error('Pagefind response contained errors.');
|
|
52
|
+
}
|
|
53
|
+
return response as Required<T>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface PagefindBaseResponse {
|
|
57
|
+
errors: string[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
type PagefindIntegrationOptions = Pick<HookParameters<'astro:build:done'>, 'dir' | 'logger'>;
|
package/package.json
CHANGED
package/translations/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import id from './id.json';
|
|
|
15
15
|
import it from './it.json';
|
|
16
16
|
import nl from './nl.json';
|
|
17
17
|
import da from './da.json';
|
|
18
|
+
import th from './th.json';
|
|
18
19
|
import tr from './tr.json';
|
|
19
20
|
import ar from './ar.json';
|
|
20
21
|
import nb from './nb.json';
|
|
@@ -53,6 +54,7 @@ export default Object.fromEntries(
|
|
|
53
54
|
it,
|
|
54
55
|
nl,
|
|
55
56
|
da,
|
|
57
|
+
th,
|
|
56
58
|
tr,
|
|
57
59
|
ar,
|
|
58
60
|
nb,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skipLink.label": "ข้ามไปยังเนื้อหา",
|
|
3
|
+
"search.label": "ค้นหา",
|
|
4
|
+
"search.ctrlKey": "Ctrl",
|
|
5
|
+
"search.cancelLabel": "ยกเลิก",
|
|
6
|
+
"search.devWarning": "การค้นหาสามารถใช้งานได้ในเฉพาะเวอร์ชันใช้งานจริงเท่านั้น\nโปรดลองบิลด์และดูตัวอย่างเว็บไซต์เพื่อทดสอบฟังก์ชันบนอุปกรณ์ของคุณ",
|
|
7
|
+
"themeSelect.accessibleLabel": "เลือกธีม",
|
|
8
|
+
"themeSelect.dark": "มืด",
|
|
9
|
+
"themeSelect.light": "สว่าง",
|
|
10
|
+
"themeSelect.auto": "อัตโนมัติ",
|
|
11
|
+
"languageSelect.accessibleLabel": "เลือกภาษา",
|
|
12
|
+
"menuButton.accessibleLabel": "เมนู",
|
|
13
|
+
"sidebarNav.accessibleLabel": "หลัก",
|
|
14
|
+
"tableOfContents.onThisPage": "ในหน้านี้",
|
|
15
|
+
"tableOfContents.overview": "ภาพรวม",
|
|
16
|
+
"i18n.untranslatedContent": "เนื้อหานี้ยังไม่มีในภาษาของคุณ",
|
|
17
|
+
"page.editLink": "แก้ไขหน้า",
|
|
18
|
+
"page.lastUpdated": "อัพเดทล่าสุด:",
|
|
19
|
+
"page.previousLink": "ก่อนหน้า",
|
|
20
|
+
"page.nextLink": "ถัดไป",
|
|
21
|
+
"page.draft": "เนื้อหานี้เป็นแบบร่างและจะไม่ถูกใส่ในเวอร์ชันใช้งานจริง",
|
|
22
|
+
"404.text": "ไม่พบหน้า โปรดตรวจสอบ URL หรือลองใช้ฟังก์ชันการค้นหา",
|
|
23
|
+
"aside.note": "หมายเหตุ",
|
|
24
|
+
"aside.tip": "เคล็ดลับ",
|
|
25
|
+
"aside.caution": "คำเตือน",
|
|
26
|
+
"aside.danger": "อันตราย",
|
|
27
|
+
"fileTree.directory": "โฟลเดอร์",
|
|
28
|
+
"builtWithStarlight.label": "ถูกสร้างขึ้นด้วย Starlight",
|
|
29
|
+
"heading.anchorLabel": "หัวข้อที่มีชื่อว่า “{{title}}”"
|
|
30
|
+
}
|