@@ -9,96 +9,99 @@ import type { HookParameters, StarlightConfig } from '../types';
99import { resolveCollectionPath } from '../utils/collection' ;
1010
1111const AnchorLinkIcon = h (
12- 'span' ,
13- { ariaHidden : 'true' , class : 'sl-anchor-icon' } ,
14- h (
15- 'svg' ,
16- { width : 16 , height : 16 , viewBox : '0 0 24 24' } ,
17- h ( 'path' , {
18- fill : 'currentcolor' ,
19- 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' ,
20- } )
21- )
12+ 'span' ,
13+ { ariaHidden : 'true' , class : 'sl-anchor-icon' } ,
14+ h (
15+ 'svg' ,
16+ { width : 16 , height : 16 , viewBox : '0 0 24 24' } ,
17+ h ( 'path' , {
18+ fill : 'currentcolor' ,
19+ 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' ,
20+ } )
21+ )
2222) ;
2323
2424/**
2525 * Add anchor links to headings.
2626 */
2727export default function rehypeAutolinkHeadings (
28- docsCollectionPath : string ,
29- useTranslationsForLang : AutolinkHeadingsOptions [ 'useTranslations' ] ,
30- absolutePathToLang : AutolinkHeadingsOptions [ 'absolutePathToLang' ]
28+ docsCollectionPath : string ,
29+ useTranslationsForLang : AutolinkHeadingsOptions [ 'useTranslations' ] ,
30+ absolutePathToLang : AutolinkHeadingsOptions [ 'absolutePathToLang' ]
3131) {
32- const transformer : Transformer < Root > = ( tree , file ) => {
33- // If the document is not part of the Starlight docs collection , skip it.
34- if ( ! normalizePath ( file . path ) . startsWith ( docsCollectionPath ) ) return ;
32+ const transformer : Transformer < Root > = ( tree , file ) => {
33+ // If the content is remote Markdown , skip it.
34+ if ( ! file ? .path ) return ;
3535
36- const pageLang = absolutePathToLang ( file . path ) ;
37- const t = useTranslationsForLang ( pageLang ) ;
36+ // If the document is not part of the Starlight docs collection, skip it.
37+ if ( ! normalizePath ( file . path ) . startsWith ( docsCollectionPath ) ) return ;
3838
39- visit ( tree , 'element' , function ( node , index , parent ) {
40- if ( ! headingRank ( node ) || ! node . properties . id || typeof index !== 'number' || ! parent ) {
41- return ;
42- }
39+ const pageLang = absolutePathToLang ( file . path ) ;
40+ const t = useTranslationsForLang ( pageLang ) ;
4341
44- const accessibleLabel = t ( 'heading.anchorLabel' , {
45- title : toString ( node ) ,
46- interpolation : { escapeValue : false } ,
47- } ) ;
42+ visit ( tree , 'element' , function ( node , index , parent ) {
43+ if ( ! headingRank ( node ) || ! node . properties . id || typeof index !== 'number' || ! parent ) {
44+ return ;
45+ }
4846
49- // Wrap the heading in a div and append the anchor link.
50- parent . children [ index ] = h (
51- 'div' ,
52- { class : `sl-heading-wrapper level-${ node . tagName } ` } ,
53- // Heading
54- node ,
55- // Anchor link
56- {
57- type : 'element' ,
58- tagName : 'a' ,
59- properties : { class : 'sl-anchor-link' , href : '#' + node . properties . id } ,
60- children : [ AnchorLinkIcon , h ( 'span' , { class : 'sr-only' } , accessibleLabel ) ] ,
61- }
62- ) ;
47+ const accessibleLabel = t ( 'heading.anchorLabel' , {
48+ title : toString ( node ) ,
49+ interpolation : { escapeValue : false } ,
50+ } ) ;
6351
64- return SKIP ;
65- } ) ;
66- } ;
52+ // Wrap the heading in a div and append the anchor link.
53+ parent . children [ index ] = h (
54+ 'div' ,
55+ { class : `sl-heading-wrapper level-${ node . tagName } ` } ,
56+ // Heading
57+ node ,
58+ // Anchor link
59+ {
60+ type : 'element' ,
61+ tagName : 'a' ,
62+ properties : { class : 'sl-anchor-link' , href : '#' + node . properties . id } ,
63+ children : [ AnchorLinkIcon , h ( 'span' , { class : 'sr-only' } , accessibleLabel ) ] ,
64+ }
65+ ) ;
6766
68- return function attacher ( ) {
69- return transformer ;
70- } ;
67+ return SKIP ;
68+ } ) ;
69+ } ;
70+
71+ return function attacher ( ) {
72+ return transformer ;
73+ } ;
7174}
7275
7376interface AutolinkHeadingsOptions {
74- starlightConfig : Pick < StarlightConfig , 'markdown' > ;
75- astroConfig : Pick < AstroConfig , 'srcDir' > & {
76- experimental : Pick < AstroConfig [ 'experimental' ] , 'headingIdCompat' > ;
77- } ;
78- useTranslations : HookParameters < 'config:setup' > [ 'useTranslations' ] ;
79- absolutePathToLang : HookParameters < 'config:setup' > [ 'absolutePathToLang' ] ;
77+ starlightConfig : Pick < StarlightConfig , 'markdown' > ;
78+ astroConfig : Pick < AstroConfig , 'srcDir' > & {
79+ experimental : Pick < AstroConfig [ 'experimental' ] , 'headingIdCompat' > ;
80+ } ;
81+ useTranslations : HookParameters < 'config:setup' > [ 'useTranslations' ] ;
82+ absolutePathToLang : HookParameters < 'config:setup' > [ 'absolutePathToLang' ] ;
8083}
8184type RehypePlugins = NonNullable < NonNullable < AstroUserConfig [ 'markdown' ] > [ 'rehypePlugins' ] > ;
8285
8386export const starlightAutolinkHeadings = ( {
84- starlightConfig,
85- astroConfig,
86- useTranslations,
87- absolutePathToLang,
87+ starlightConfig,
88+ astroConfig,
89+ useTranslations,
90+ absolutePathToLang,
8891} : AutolinkHeadingsOptions ) : RehypePlugins =>
89- starlightConfig . markdown . headingLinks
90- ? [
91- [
92- rehypeHeadingIds ,
93- { experimentalHeadingIdCompat : astroConfig . experimental ?. headingIdCompat } ,
94- ] ,
95- rehypeAutolinkHeadings (
96- normalizePath ( resolveCollectionPath ( 'docs' , astroConfig . srcDir ) ) ,
97- useTranslations ,
98- absolutePathToLang
99- ) ,
100- ]
101- : [ ] ;
92+ starlightConfig . markdown . headingLinks
93+ ? [
94+ [
95+ rehypeHeadingIds ,
96+ { experimentalHeadingIdCompat : astroConfig . experimental ?. headingIdCompat } ,
97+ ] ,
98+ rehypeAutolinkHeadings (
99+ normalizePath ( resolveCollectionPath ( 'docs' , astroConfig . srcDir ) ) ,
100+ useTranslations ,
101+ absolutePathToLang
102+ ) ,
103+ ]
104+ : [ ] ;
102105
103106/**
104107 * File path separators seems to be inconsistent on Windows when the rehype plugin is used on
@@ -107,7 +110,7 @@ export const starlightAutolinkHeadings = ({
107110 */
108111const backSlashRegex = / \\ / g;
109112function normalizePath ( path : string ) {
110- return path . replace ( backSlashRegex , '/' ) ;
113+ return path . replace ( backSlashRegex , '/' ) ;
111114}
112115
113116// This utility is inlined from https://github.com/syntax-tree/hast-util-heading-rank
@@ -119,7 +122,7 @@ function normalizePath(path: string) {
119122 * @returns Rank of the heading or `undefined` if not a heading.
120123 */
121124function headingRank ( node : Nodes ) : number | undefined {
122- const name = node . type === 'element' ? node . tagName . toLowerCase ( ) : '' ;
123- const code = name . length === 2 && name . charCodeAt ( 0 ) === 104 /* `h` */ ? name . charCodeAt ( 1 ) : 0 ;
124- return code > 48 /* `0` */ && code < 55 /* `7` */ ? code - 48 /* `0` */ : undefined ;
125+ const name = node . type === 'element' ? node . tagName . toLowerCase ( ) : '' ;
126+ const code = name . length === 2 && name . charCodeAt ( 0 ) === 104 /* `h` */ ? name . charCodeAt ( 1 ) : 0 ;
127+ return code > 48 /* `0` */ && code < 55 /* `7` */ ? code - 48 /* `0` */ : undefined ;
125128}
0 commit comments