@astrojs/starlight 0.37.3 → 0.37.5

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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # @astrojs/starlight
2
2
 
3
+ ## 0.37.5
4
+
5
+ ### Patch Changes
6
+
7
+ - [#3675](https://github.com/withastro/starlight/pull/3675) [`0ba556d`](https://github.com/withastro/starlight/commit/0ba556d7d49dd4904f8aa8524c105bf1ceeec85c) Thanks [@controversial](https://github.com/controversial)! - Excludes the accessible labels for heading anchor links from Pagefind results
8
+
9
+ ## 0.37.4
10
+
11
+ ### Patch Changes
12
+
13
+ - [#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.
14
+
15
+ 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.
16
+
17
+ - [#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
18
+
19
+ - [#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
20
+
21
+ - [#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
22
+
23
+ - [#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.
24
+
3
25
  ## 0.37.3
4
26
 
5
27
  ### Patch Changes
@@ -48,6 +48,6 @@ const accessibleLabel = Astro.locals.t('heading.anchorLabel', {
48
48
  d="m12.11 15.39-3.88 3.88a2.52 2.52 0 0 1-3.5 0 2.47 2.47 0 0 1 0-3.5l3.88-3.88a1 1 0 0 0-1.42-1.42l-3.88 3.89a4.48 4.48 0 0 0 6.33 6.33l3.89-3.88a1 1 0 1 0-1.42-1.42Zm8.58-12.08a4.49 4.49 0 0 0-6.33 0l-3.89 3.88a1 1 0 0 0 1.42 1.42l3.88-3.88a2.52 2.52 0 0 1 3.5 0 2.47 2.47 0 0 1 0 3.5l-3.88 3.88a1 1 0 1 0 1.42 1.42l3.88-3.89a4.49 4.49 0 0 0 0-6.33ZM8.83 15.17a1 1 0 0 0 1.1.22 1 1 0 0 0 .32-.22l4.92-4.92a1 1 0 0 0-1.42-1.42l-4.92 4.92a1 1 0 0 0 0 1.42Z"
49
49
  ></path></svg
50
50
  ></span
51
- ><span class="sr-only" set:text={accessibleLabel} /></a
51
+ ><span class="sr-only" data-pagefind-ignore="" set:text={accessibleLabel} /></a
52
52
  ></div
53
53
  >
@@ -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 && 'page'}
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
- if (el instanceof HTMLHeadingElement) {
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 elements with an `id` (most likely headings) and their siblings.
76
- // Also observe direct children of `.content` to include elements before
77
- // the first heading.
78
- const toObserve = document.querySelectorAll('main [id], main [id] ~ *, main .content > *');
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, AstroIntegrationLogger } from 'astro';
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': ({ dir, logger }) => {
149
+ 'astro:build:done': async (options) => {
152
150
  if (!userConfig.pagefind) return;
153
- const loglevelFlag = getPagefindLoggingFlags(logger.options.level);
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
- }
@@ -50,7 +50,10 @@ export default function rehypeAutolinkHeadings({
50
50
  type: 'element',
51
51
  tagName: 'a',
52
52
  properties: { class: 'sl-anchor-link', href: '#' + String(node.properties.id) },
53
- children: [AnchorLinkIcon, h('span', { class: 'sr-only' }, accessibleLabel)],
53
+ children: [
54
+ AnchorLinkIcon,
55
+ h('span', { class: 'sr-only', 'data-pagefind-ignore': true }, accessibleLabel),
56
+ ],
54
57
  }
55
58
  );
56
59
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrojs/starlight",
3
- "version": "0.37.3",
3
+ "version": "0.37.5",
4
4
  "description": "Build beautiful, high-performance documentation websites with Astro",
5
5
  "keywords": [
6
6
  "docs",
@@ -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
+ }